Enable method chaining or fluent expressions for any value and method.

Overview

PHP Pipe Operator

Latest stable release Software license Build status Total downloads Total stars

Read my blog View my other packages and projects Follow @sebastiaanluca on Twitter Share this package on Twitter

A (hopefully) temporary solution to implement the pipe operator in PHP.

Table of contents

Requirements

  • PHP 8 or higher

How to install

Via Composer:

composer require sebastiaanluca/php-pipe-operator

How to use

The basics

The basic gist of the package is that it takes a value and performs one or more actions on it. A simple example:

use SebastiaanLuca\PipeOperator\Pipe;

Pipe::from('hello')->strtoupper()->get();

// "HELLO"

A few alternatives to write the same:

strtoupper()->get(); // "HELLO" ">
take('hello')->strtoupper()->get();

// "HELLO"

pipe('hello')->strtoupper()->get();

// "HELLO"

Of course that's not very useful since you could've just used strtoupper('hello') and be done with it, but the goal is to make multi-method calls on a value easier to read and write:

$subdomain = Pipe::from('https://blog.sebastiaanluca.com')
    ->parse_url()
    ->end()
    ->explode('.', PIPED_VALUE)
    ->reset()
    ->get();

// "blog"

Note that in comparison to the original RFC, there's no need to pass the initial value to methods that receive the value as first parameter and have no other required parameters. The previous value is always passed as first parameter. In effect, both of the following examples will work:

strtoupper(PIPED_VALUE)->get(); // "HELLO" ">
Pipe::from('hello')->strtoupper()->get();

// "HELLO"

Pipe::from('hello')->strtoupper(PIPED_VALUE)->get();

// "HELLO"

In contrast, if a method takes e.g. a setting before the previous value, we need to set it manually using the replacement identifier (the globally available PIPED_VALUE constant). This identifier can be placed anywhere in the method call, it will simply be replaced by the previous value.

Pipe::from(['key' => 'value'])
    ->array_search('value', PIPED_VALUE)
    ->get();

// "key"

Using closures

Sometimes standard methods don't cut it and you need to perform a custom operation on a value in the process. You can do so using a closure:

Pipe::from('string')
    ->pipe(fn(string $value): string => 'prefixed-' . $value)
    ->get();

// "prefixed-string"

Using class methods

The same is possible using a class method (regardless of visibility):

class MyClass
{
    public function __construct()
    {
        Pipe::from('HELLO')
            ->pipe($this)->lowercase()
            ->get();

        // "hello"
    }

    /**
     * @param string $value
     *
     * @return string
     */
    private function lowercase(string $value) : string
    {
        return mb_strtolower($value);
    }
}

Class method alternatives

If you don't want to use the internal pipe proxy and pass $this, there are two other ways you can use class methods.

Using an array (for public methods only):

class MyClass
{
    public function __construct()
    {
        Pipe::from('HELLO')
            ->pipe([$this, 'lowercase'])
            ->get();

        // "hello"
    }

    /**
     * @param string $value
     *
     * @return string
     */
    public function lowercase(string $value) : string
    {
        return mb_strtolower($value);
    }
}

By parsing the callable method to a closure:

use Closure;

class MyClass
{
    public function __construct()
    {
        Pipe::from('HELLO')
            ->pipe(Closure::fromCallable([$this, 'lowercase']))
            ->get();

        // "hello"
    }

    /**
     * @param string $value
     *
     * @return string
     */
    private function lowercase(string $value) : string
    {
        return mb_strtolower($value);
    }
}

What does it solve?

This package is based on the pipe operator RFC by Sara Golemon (2016), who explains the problem as:

A common PHP OOP pattern is the use of method chaining, or what is also known as “Fluent Expressions”. […] This works well enough for OOP classes which were designed for fluent calling, however it is impossible, or at least unnecessarily arduous, to adapt non-fluent classes to this usage style, harder still for functional interfaces.

Coming across the proposal, I also blogged about it.

A simple example

Say you want to get the subdomain from a URL, you end up with something like this:

$subdomain = 'https://blog.sebastiaanluca.com/';
$subdomain = parse_url($subdomain, PHP_URL_HOST);
$subdomain = explode('.', $subdomain);
$subdomain = reset($subdomain);

// "blog"

This works, of course, but it's quite verbose and repetitive.

Another way of writing

Same result, different style:

$subdomain = explode('.', parse_url('https://blog.sebastiaanluca.com/', PHP_URL_HOST))[0];

// "blog"

This might be the worst of all solutions, as it requires you to start reading from the center, work your way towards the outer methods, and keep switching back and forth. The more methods and variants, the more difficult to get a sense of what's going on.

More examples of the issue at hand

See Sara's RFC for more complex and real-world examples.

Notes

While this packages makes a good attempt at bringing the pipe operator to PHP, it unfortunately does not offer autocompletion on chained methods. For that to work we need the real deal, so make some noise and get the people in charge to vote for Sara's RFC!

License

This package operates under the MIT License (MIT). Please see LICENSE for more information.

Change log

Please see CHANGELOG for more information what has changed recently.

Testing

composer install
composer test

Contributing

Please see CONTRIBUTING and CONDUCT for details.

Security

If you discover any security related issues, please email [email protected] instead of using the issue tracker.

Credits

About

My name is Sebastiaan and I'm a freelance Laravel developer specializing in building custom Laravel applications. Check out my portfolio for more information, my blog for the latest tips and tricks, and my other packages to kick-start your next project.

Have a project that could use some guidance? Send me an e-mail at [email protected]!

Comments
  • Fluent interface

    Fluent interface

    I do love the fluent code but wouldn't it be better to write a fluent interface? It would eliminate the need for PIPED_VALUE.

    For example see the fluent strings in Laravel: https://laravel.com/docs/8.x/helpers#fluent-strings

    You could do the same for arrays and others.

    Am I missing something?

    opened by PiNotEqual3 3
  • Add proxy for private instance methods

    Add proxy for private instance methods

    PR Type

    What kind of pull request is this?

    • [ ] Bug fix (non-breaking change which fixes an issue)
    • [x] New feature (non-breaking change which adds functionality)
    • [ ] Extend feature (non-breaking change which extends existing functionality)
    • [ ] Change feature (non-breaking change which either changes or refactors existing functionality)
    • [ ] Breaking change (fix or feature that would cause existing functionality to change)

    What does it change?

    You can now more easily pipe calls to private methods on your class:

    take($value)
        ->pipe($this)->instanceMethod()
        ->get();
    

    Why this PR?

    While[$this, 'method'] & Closure::fromCallable do the trick, they're more verbose, and don't look like a method call. Using a proxy to pipe method calls onto the class looks much nicer.

    How has this been tested?

    I added a test in the MethodTest class.

    opened by JosephSilber 3
  • Why not a pipe() function?

    Why not a pipe() function?

    Is there a specific reason you went for a pipe class instead of a simple pipe function, like:

    function pipe() {
        $args = func_get_args();
        $result = $args[0];
        $functions = array_slice($args, 1);
        foreach ($functions as $function) {
            $result = $function($result);
        }
        return $result;
    }
    

    Just curious. ^^

    opened by olleharstedt 1
  • Seems like an abandoned project

    Seems like an abandoned project

    It seems this is an abandoned project that does not support the latest PHP version. People looking for a good replacement can try boostphp/pipe-operator.

    opened by buismaarten 1
  • Change the '$$' identifier to a constant

    Change the '$$' identifier to a constant

    PR Type

    • [ ] Bug fix (non-breaking change which fixes an issue)
    • [x] New feature (non-breaking change which adds functionality)
    • [ ] Extend feature (non-breaking change which extends existing functionality)
    • [ ] Change feature (non-breaking change which either changes or refactors existing functionality)
    • [x] Breaking change (fix or feature that would cause existing functionality to change)

    What does it change?

    Proposes a constant that is named appropriately so that any developer reading it in use can tell what it represents, and it uses an ID generated by uniqid() so as to not conflict with user input.

    Why this PR?

    The '$$' identifier has a few problems, and replacing it with a constant is an attempt to alleviate those:

    • '$$' is not a language construct like the RFC proposes, so people looking into the code will not be familiar with it and what it represents
    • When processing user input, '$$' could be a perfectly valid string and is now something that the developer must explicitly check for

    How has this been tested?

    Existing tests have been modified to account for this change.

    opened by imliam 1
  • Test enhancement

    Test enhancement

    PR Type

    What kind of pull request is this? Put an x in all the boxes that apply:

    • [X] Test enhancement (enhance type hint, and adding required extension check)

    What does it change?

    • Adding the ext-mbstring extension check inside required-dev block in composer.json.
    • The ...$values parameters in MethodsTest::join method means multiple arguments and every argument should be the string type, not array type. Using the string type hint and comment annotation instead.

    Why this PR?

    • Enhancement work.

    How has this been tested?

    • It doesn't have the test
    opened by peter279k 0
  • strict_types=1 makes pipe-operator less agile [question]

    strict_types=1 makes pipe-operator less agile [question]

    Hi

    It is very nice piece of work, I am using pipe-operator in my projects. It really rocks, especially in combination with symfony expression language.

    When I upgraded to 5.x within php8 migration I noticed that some of my expressions need to be fix because of strict_types=1.

    I am wondering is declaring strict_types=1 is good for pipe-operator? What were the arguments for doing this?

    As I understand the purpose of the pipe-operator it to make the code flow :), get it more compact and agile and so does the php coercive mode which is good thing in specific cases, changing it to strict_types in my opinion takes pipe-operator one step back in terms of agile.

    For example:

    class A 
    {
      public function __toString(): string
      {
        return '[]';
      }
    }
    take(new A())->json_decode()->get();
    

    On version 3-4.x it executes with no errors, on version 5.x it ends with:

    PHP Fatal error:  Uncaught TypeError: json_decode(): Argument #1 ($json) must be of type string, A given in /home/psuw/xtm/projekty/portal/portal/vendor/sebastiaanluca/php-pipe-operator/src/Pipe.php:37
    

    Of course it will work but it is not so compact:

    take((string) new A())->json_decode()->get();
    

    symfony expression language:

    take(a.__toString()).json_decode().get()
    
    opened by PawelSuwinski 3
Releases(5.0.0)
Owner
Sebastiaan Luca
Crafting web apps that empower businesses and their customers. Let's chat — [email protected]
Sebastiaan Luca
Share value objects that contain the same primitive value as a singleton

sharable-value-objects Share value objects that contain the same primitive value as a singleton. Singletons are kept under WeakReference objects. Inst

mpyw 5 Nov 14, 2021
High performance view templating API for PHP applications using tags & expressions inspired by Java JSTL and C compiler

View Language API Table of contents: About Expressions Tags Configuration Compilation Installation Unit Tests Examples Reference Guide About This API

Lucian Gabriel Popescu 0 Jan 9, 2022
PHP Regular expressions made easy

PHPVerbalExpressions ported from VerbalExpressions VerbalExpressions is a PHP library that helps to construct hard regular expressions. Installation T

null 2.4k Jan 3, 2023
🦉 human-readable regular expressions for PHP

RegExpBuilder integrates regular expressions into the programming language, thereby making them easy to read and maintain. Regular Expressions are created by using chained methods and variables such as arrays or strings.

Max Girkens 907 Dec 30, 2022
PHP library that helps to map any input into a strongly-typed value object structure.

Valinor • PHP object mapper with strong type support Valinor is a PHP library that helps to map any input into a strongly-typed value object structure

Team CuyZ 873 Jan 7, 2023
Talkino allows you to integrate multi social messengers and contact into your website and enable your users to contact you using multi social messengers' accounts.

Talkino Welcome to our GitHub Repository Talkino is a click to chat plugin to show your agents’ multiple social messengers, phone and emails on the ch

Traxconn 2 Sep 21, 2022
Enable ray(), dd() and dump() in all PHP files on your system

Enable ray(), dd() and dump() in all PHP files on your system Ray is a wonderful desktop application that can help you debug applications faster. It c

Spatie 190 Dec 13, 2022
Enable Facebook Instant Articles on your WordPress site.

Instant Articles for WP Enable Instant Articles for Facebook on your WordPress site. Plugin activity Description This plugin adds support for Instant

Automattic 633 Nov 21, 2022
The Laravel eCommerce USPS Shipping module allows the store owners to enable United States Postal Servies for the shipment of orders.

Introduction Bagisto Usps Shipping add-on provides Usps Shipping methods for shipping the product. By using this, you can provide Usps (United States

Bagisto 2 May 31, 2022
Track any ip address with IP-Tracer. IP-Tracer is developed for Linux and Termux. you can retrieve any ip address information using IP-Tracer.

IP-Tracer is used to track an ip address. IP-Tracer is developed for Termux and Linux based systems. you can easily retrieve ip address information using IP-Tracer. IP-Tracer use ip-api to track ip address.

Rajkumar Dusad 1.2k Jan 4, 2023
salah eddine bendyab 18 Aug 17, 2021
Immutable value object for IPv4 and IPv6 addresses, including helper methods and Doctrine support.

IP is an immutable value object for (both version 4 and 6) IP addresses. Several helper methods are provided for ranges, broadcast and network address

Darsyn 224 Dec 28, 2022
A simple, standalone, modern PHP class inspector and mapper library, wrapping PHPs native reflection in a fluent interface

A simple, standalone, modern PHP class inspector and mapper library, wrapping PHPs native reflection in a fluent interface.

smpl 9 Sep 1, 2022
Parse DSN strings into value objects to make them easier to use, pass around and manipulate

DSN parser Parse DSN strings into value objects to make them easier to use, pass around and manipulate. Install Via Composer composer require nyholm/d

Tobias Nyholm 77 Dec 13, 2022
LendCash is a cash lending service that lets you take loans against your stocks portfolio value and pay back on a prorated basis.

LendCash is a cash lending service that lets you take loans against your stocks portfolio value and pay back on a prorated basis.

Teniola Fatunmbi 2 Aug 22, 2022
This package implements 0-1 Knapsack Problem algorithm i.e. allows to find the best way to fill a knapsack of a specified volume with items of a certain volume and value.

This package implements "0-1 Knapsack Problem" algorithm i.e. allows to find the best way to fill a knapsack of a specified volume with items of a certain volume and value.

Alexander Makarov 9 Sep 8, 2022
This package provides a simple and intuitive way to work on the Youtube Data API. It provides fluent interface to Youtube features.

Laravel Youtube Client This package provides a simple and intuitive way to work on the Youtube Data API. It provides fluent interface to Youtube featu

Tilson Mateus 6 May 31, 2023
Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

Introduction Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services. It handles almost all of the boilerpl

The Laravel Framework 2.2k Dec 31, 2022