Matthew Hodge
Full Stack Developer

Reference: PHP 8.5 Pipe Operator

⚠️ The pipe operator (|>) is only available in PHP 8.5 and above. Attempting to use it in earlier versions will cause a syntax error.

Introduction

PHP 8.5 introduces the long-awaited pipe operator (|>), making it easier to chain function calls and write cleaner, more readable code.

How Does It Work?

  • The ... (spread operator) is the placeholder for the value passed from the previous step.
  • Each function in the chain receives the output of the previous function as its input.
  • You can combine as many actions as you like, making your transformations clear and concise.

Let's see how you might manipulate an array of fruits in PHP, first using traditional code, then with the new pipe operator.

Without the Pipe Operator

Here's how you might perform a series of transformations on an array in classic PHP:

$fruits = ["apple", "banana", "cherry"];
$fruits = array_map(fn($f) => strtoupper($f), $fruits);
$fruits = array_filter($fruits, fn($f) => str_starts_with($f, 'A'));
$fruits = array_values($fruits);

print_r($fruits); // ["APPLE"]

With the Pipe Operator

With PHP 8.5's pipe operator, you can chain these operations more elegantly:

$fruits = ["apple", "banana", "cherry"]
    |> array_map(fn($f) => strtoupper($f), ...)
    |> array_filter(..., fn($f) => str_starts_with($f, 'A'))
    |> array_values(...);

print_r($fruits); // ["APPLE"]

Each function receives the result of the previous one, making your code more readable and expressive.

Limitations & Gotchas

  • Only One Required Parameter: All callables in the pipe chain must accept the piped value as their first (and only required) parameter. You cannot change the position.
  • Callable Flexibility: You can use user functions, built-in functions, static methods, anonymous functions, arrow functions, objects with __invoke, and first-class callables.
  • Type Coercion: The pipe operator follows PHP’s normal type coercion rules. If strict_types is enabled, type mismatches will throw errors.
  • Void Return Types: Functions with void return types can be piped, but the value becomes null for the rest of the chain. Typically, use void functions last.
  • By-Reference Limitation: Functions requiring by-reference parameters (e.g., array_pop) cannot be used in a pipe chain, except for a few special cases like array_multisort and extract.

Final Thoughts

The PHP 8.5 pipe operator is a game-changer for writing expressive, readable code. Whether you’re transforming arrays, objects, or other data, it’s a powerful new tool in your PHP arsenal.


PHP: Yield vs. Traditional Loop

When working with large datasets, such as calculating prime numbers, efficiency is key. In PHP, the yield keyword offers a powerful way to optimize both performance and memory usage. Let’s explore how it compares to traditional loops.

The Problem: Calculating Prime Numbers

Prime numbers are fundamental in mathematics and computing. However, generating a large list of primes can be resource-intensive. Let’s compare two approaches in PHP:

1. Traditional Loop (Without yield)

function getPrimesWithoutYield($limit) {
    $primes = [];
    for ($i = 2; $i < $limit; $i++) {
        $isPrime = true;
        for ($j = 2; $j <= sqrt($i); $j++) {
            if ($i % $j == 0) {
                $isPrime = false;
                break;
            }
        }
        if ($isPrime) {
            $primes[] = $i;
        }
    }
    return $primes;
}

2. Using yield

function getPrimesWithYield($limit) {
    for ($i = 2; $i < $limit; $i++) {
        $isPrime = true;
        for ($j = 2; $j <= sqrt($i); $j++) {
            if ($i % $j == 0) {
                $isPrime = false;
                break;
            }
        }
        if ($isPrime) {
            yield $i;
        }
    }
}

Performance Comparison

Testing both methods to calculate primes up to 10,000,000. Here are the results:

  • Without yield:
    Time: ~25 seconds, Memory: ~42 MB
  • With yield:
    Time: ~23 seconds, Memory: ~0 MB

Why Use yield?

  1. Memory Efficiency: yield generates values on-the-fly, avoiding the need to store the entire list in memory.
  2. Lazy Evaluation: Values are computed only when needed, reducing unnecessary computation.
  3. Scalability: Ideal for processing large datasets without running into memory limits.

Conclusion

For tasks like calculating primes, yield is a game-changer. It offers significant memory savings and maintains comparable speed, making it the better choice for large-scale computations.


PHP 8 introduced a new feature called match expression, which is similar to the switch statement but with some key differences and improvements. This post will explain the differences between switch and match and provide examples to illustrate their usage.

For more details, you can refer to the official PHP documentation.

Differences between switch and match

  1. Return Value: The match expression returns a value, whereas the switch statement does not.
  2. Strict Comparison: match uses strict comparison (===), while switch uses loose comparison (==).
  3. No Fall-through: match does not allow fall-through, which means each case must be unique and there is no need for break statements.
  4. Expression-based: match is an expression and can be used in assignments, while switch is a statement.

Example 1: Basic Usage

Using switch

$input = 2;
$result = '';

switch ($input) {
    case 1:
        $result = 'One';
        break;
    case 2:
        $result = 'Two';
        break;
    case 3:
        $result = 'Three';
        break;
    default:
        $result = 'Unknown';
        break;
}

echo $result; // Outputs: Two

Using match

$input = 2;

$result = match ($input) {
    1 => 'One',
    2 => 'Two',
    3 => 'Three',
    default => 'Unknown',
};

echo $result; // Outputs: Two

Example 2: Type Safety

Using switch

<?php
$input = '2';
$result = '';

switch ($input) {
    case 1:
        $result = 'One';
        break;
    case 2:
        $result = 'Two';
        break;
    case 3:
        $result = 'Three';
        break;
    default:
        $result = 'Unknown';
        break;
}

echo $result; // Outputs: Two (loose comparison)

Using match

<?php
$input = '2';

$result = match ($input) {
    1 => 'One',
    2 => 'Two',
    3 => 'Three',
    default => 'Unknown',
};

echo $result; // Outputs: Unknown (strict comparison)

As you can see, the match expression provides a more concise and safer way to handle conditional logic compared to the switch statement. It ensures strict type comparisons and eliminates the risk of fall-through errors.

By understanding and utilizing the match expression, you can write cleaner and more predictable code in PHP 8 and beyond.


I was recently tasked with implementing a SOAP service in order to interact with an accounting system and an ecommerce store, to update products based on the data returned from the service i.e pricing and stock updates scheduled to run on a schedule.

I use a great WsdlToPhp/PackageGenerator on GitHub to generate PHP classes from a WSDL, I thought to myself why not write about it in the hopes of it being of use for other developers.

Lets take a look at this example calculator wsdl which will allow us to use the functions thereof to perform some calculations. I'm sure your WSDL will be more complex and have a better use case, but this is just to get you started

directory structure
- calculator.php
- index.php
- composer.json
sample soap service
http://www.dneonline.com/calculator.asmx?wsdl
composer.json
{
    "name": "mhodge/playground",
    "autoload": {
        "psr-4": {
            "Calculator\\": "Calculator/src/Calculator"
        }
    },
    "require-dev": {
        "wsdltophp/packagegenerator": "^4.1"
    }
}

calculator.php
<?php
require_once 'vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$options = GeneratorOptions::instance();
$options
    ->setOrigin('http://www.dneonline.com/calculator.asmx?wsdl')
    ->setDestination('./Calculator')
    ->setComposerName('calculator')
    ->setNamespace('Calculator');

    // Generator instantiation
$generator = new Generator($options);

// Package generation
$generator->generatePackage();
index.php
<?php
require_once 'vendor/autoload.php';

$options = [
    WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_URL => 'http://www.dneonline.com/calculator.asmx?wsdl',
    WsdlToPhp\PackageBase\AbstractSoapClientBase::WSDL_CLASSMAP => \Calculator\ClassMap::get(),
];
/**
 * Samples for Add ServiceType
 */
$add = new \Calculator\ServiceType\Add($options);
/**
 * Sample call for Add operation/method
 */
if ($add->Add(new \Calculator\StructType\Add(668,669)) !== false) {
    print_r($add->getResult());
    print_r("<br />");
} else {
    print_r($add->getLastError());
    print_r("<br />");
}

Why 668 + 669 ? well thats just 1337!

There are many other options you can set and use for your project other than the ones I used above, I highly recommend going through all of them to get familiar so you can apply to your use case.

One of the options I find really usefull is overriding the xsd_types of the wsdl you are generating this Weather WSDL for example uses multiple XSD types string, decimal, datetime etc. It's sometimes usefull to extend the default xsd_types.yml and specify the type you want returned for your WSDL.

i.e you could update the index.php and set the setXsdTypesPath directive

calculator.php - updated
require_once 'vendor/autoload.php';

use WsdlToPhp\PackageGenerator\ConfigurationReader\GeneratorOptions;
use WsdlToPhp\PackageGenerator\Generator\Generator;

$options = GeneratorOptions::instance();
$options
    ->setOrigin('http://www.dneonline.com/calculator.asmx?wsdl')
    ->setDestination('./Calculator')
    ->setComposerName('calculator')
    ->setNamespace('Calculator')
    ->setXsdTypesPath('xsd_types.yml');

    // Generator instantiation
$generator = new Generator($options);

// Package generation
$generator->generatePackage();
xsd_types.yml
# List of XSD types that can be defined as a type hint parameter.

xsd_types:
    anonymous: "string"
    anySimpleType: "string"
    anyType: "string"
    anyURI: "string"
    base64Binary: "string"
    bool: "bool"
    boolean: "bool"
    byte: "string"
    date: "string"
    dateTime: "string"
    decimal: "float"
    double: "float"
    DayOfWeekType: "string"
    DOMDocument: "string"
    duration: "string"
    ENTITY: "string"
    ENTITIES: "string"
    float: "float"
    gDay: "string"
    gMonth: "string"
    gMonthDay: "int"
    gYear: "string"
    gYearMonth: "int"
    hexBinary: "string"
    int: "int"
    integer: "int"
    ID: "string"
    IDREF: "string"
    IDREFS: "string"
    language: "string"
    long: "int"
    Name: "string"
    negativeInteger: "int"
    nonNegativeInteger: "int"
    nonPositiveInteger: "int"
    normalizedString: "string"
    NCName: "string"
    NMTOKEN: "string"
    NMTOKENS: "string"
    NOTATION: "string"
    positiveInteger: "int"
    QName: "string"
    short: "int"
    string: "string"
    timestamp: "int"
    timeStamp: "int"
    time: "string"
    token: "string"
    unsignedByte: "string"
    unsignedInt: "int"
    unsignedLong: "int"
    unsignedShort: "int"
    UID: "string"
    UNKNOWN: "string"

Lets say you have the following class, how do you output the contents of car variable?

class Car{
    public $make;

    public function __construct($make = null){
        $this->make = $make;
    }
}
$car = new Car('VW');

In order to debug the variable and output it to the screen you can use the following:

print_r($car)

You could also store the output into a variable which could be written later rather than output the contents to screen for example you would write it to a file:

$results = print_r($car, true); // $results now contains output from print_r