Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Welcome to the PHP tips and trick hidden chest!

PHP tips and tricks is a treasure trove of PHP pieces of code. It collects all those quick one-liners, unexpected behaviors, hidden features and strange things that are concealed in the dark corners of PHP.

This encyclopedia has several goals:

  • entertain, with special situations and unexpected but valid usage of the PHP syntax
  • provoke thoughts, by bringing edge cases into the light. They might be bugs, tolerated behaviors or simply collateral damages
  • challenge PHP users with their knowledge of the language

This encyclopedia is for the advanced PHP users. Do not go there without a solid experience of the language, and a good sense of humour.

Contributions

Contributions are welcomed, by submitting PR to the repository.

  • code images are made with carbon. They shall be in the ‘images’ folder and be in png format.
  • id is the name of the file, and by convention, the name of the image.
  • title is the name of the tip. It should be short.
  • description is the longer description of the code snippet.
  • Keep the code simple and short. It should also provide some display, even if it is an error.
  • Deprecation is the version where a deprecation message is emitted by PHP (if any)
  • phpVersion is the first version where the code is valid
  • related[] are other PHP tips and tricks that are close but different.
  • seeAlso[] is a list of links to complements that details this tips even more: PHP manual, blogs, video, applications, etc.
  • alternative is an array of other features that can do the same.
  • phpError list the PHP error messages displayed by PHP
  • keywords[] is a tag system.
  • author is your name. Leave blank for anonymous.
  • contact is a link to contact you, if you want. Mail, blog, website, mastodon handle, twitter, instagram… you choose.

Obvious or documented features of PHP will be reviewed before being accepted. Removed features might be sidelined to avoid doing archeology.

Check the skeleton.ini file, and fill its entries. You can be credited for it.

Top 15 Most Popular Tips (July 2026)

Based on visit data collected from php-tips.readthedocs.io.

  1. Less Arguments In Child Method
  2. Function Name In A Method
  3. Every Anonymous Class Is Different
  4. strtr() Does Only One Pass
  5. basename() As A str_replace()
  6. What Array Is A List
  7. Missing Methods Are Fatal
  8. Self In An Interface
  9. stdClass Object
  10. Typed Variables
  11. Constant Redefinition
  12. explode() negative third argument
  13. Interface Implemented Too Low
  14. Closure::getCurrent() To Reach The Closure
  15. array_find() And array_find_keys()

Author index

Error Message index

Available translations

PHP tips and tricks is available in the following languages:

Translations are community-contributed and may lag behind the English edition. If you spot a mistake or would like to help translate, submit a PR to the repository.

Tips

All 368 PHP tips and tricks, most recently added first.

Hijacked version_compare()

<?php

// Chapter numeroration
var_dump(version_compare('2.10', '2.9'));   // 1 => 2.10 after 2.9 (correct !)
var_dump(version_compare('1.2.10.3', '1.2.9.3')); // Nested as needed
print PHP_EOL;

// IPs 
var_dump(version_compare('192.168.10.2', '192.168.9.100')); // 1
print PHP_EOL;

// Dates are ok, 
var_dump(version_compare('2026.10.11', '2026.09.30')); // 1
var_dump(version_compare('2026/3/11', '2026/09/30')); // -1
print PHP_EOL;

// 3D coordinates
var_dump(version_compare('(3,5,2)', '(3,5,10)')); 
// This one is stretching it though...

version_compare() compares versions, by splitting the string with . the dot, and applying a natural sort, so that 10 > 9. This can be reused for other domains.

version_compare() works well on automatic numbering of book chapters, as long as they are numeric.

version_compare() works on IP addresses, in particular when working on prefixes.

version_compare() works on dates, because it handles the natural sort, so november, aka 11, doesn’t end up after May 5, or any variation of this.

version_compare() also works on 3D coordinates, as it skips common elements in the string. So, you may leave the parenthesis, and the , comma ends being used as delimiter. But now, this is stretching it, as it makes little sens to sort points in space.

Finally, consider that version_compare() does not trim strings, so a leading space may wreak havoc in the sorting.

See Also

PHP Features

Last updated: 11 October 2026

version_compare_beta_full

By Scott Keck-Warren

<?php

// beta is newer than full version
var_dump(version_compare('8.6.0beta', '8.6.0', '>='));
var_dump(version_compare('8.6.0-beta', '8.6.0', '>='));
var_dump(version_compare('8.6.0-beta', '8.6.0.0', '>='));

// beta is newer than alpha
var_dump(version_compare('8.6.0beta', '8.6.0-alpha', '>='));

// elephpant is better than alphabetical order
var_dump(version_compare('8.6.0-elephant', '8.6.0-a', '>='));

On the PHP 8.6 beta, version_compare() is false:

`version_compare(PHP_VERSION, ‘8.6.0’, ‘>=’); // ‘8.6.0beta2’ < ‘8.6.0’

version_compare() ranks beta below the release. Gate on the integer instead:

if (PHP_VERSION_ID >= 80600) {

$page = clamp($page, 1, $totalPages);

}.

See Also

PHP Features

Last updated: 11 October 2026

array_is_list() Can Fool You

<?php

$data = [
    'red',
    'green',
    'blue',
];

var_dump(array_is_list($data)); // true

unset($data[1]);

var_dump(array_is_list($data)); // false

$data[] = 'yellow';

var_dump(array_is_list($data)); // false
var_dump(array_keys($data));    // [0, 2, 3]

Here’s a special case that can save you from a surprising mistake: assuming that an array is a list just because it contains consecutive-looking values.

The nuance is that unset() does not reindex an array. Appending a new value uses the next integer key, but it doesn’t fill the gap.

The savior is array_values(). Or a reusage of the removed value.

See Also

PHP Features

Last updated: 11 October 2026

Date Format Reading

<?php

// 2006
echo (new Datetime(    '6-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;
echo (new Datetime(   '06-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;

// 2026
echo (new Datetime(   '26-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;
echo (new Datetime(  '026-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;
echo (new Datetime( '2026-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;

// 9996
echo (new Datetime( '9996-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;

// This drops the first 1
echo (new Datetime('12027-11-06'))->format('Y-m-d').PHP_EOL;

// This fails on error
echo (new Datetime('12026-11-07T00:00:00'))->format('Y-m-d').PHP_EOL;

Reading dates from strings is difficult, as the values may become ambiguous. For example, 01-02-03 may end up in 2001 or 2003, depending on the local formatting.

These ambiguities appear in PHP’s Datetime, as it must decide upon a format. This leads to some surprises, like this ones. Note that there are no reported error.

This tip was inspired by Marco Acorte.

See Also

PHP Features

Last updated: 11 October 2026

This Won’t Execute

<?php

$a = &&$b;

$a = & & $b;

This won’t Execute, but it actually compiles. PHP reports no error when writing this expression. Yet, it fails executing, as it should.

This could have been a reference on a reference on a variable: this does not make much sense, and probably should be forbidden by the engine. Yet, this passes.

On the other hand, adding a space between the two & operators, to create a reference on a reference, does yield a linting error, as expected.

In the end, it is probably a typo, but the behavior is quite strange.

See Also

PHP Features

Last updated: 11 October 2026

Bcrypt Is Limited To 72 Characters

<?php

$string = str_repeat('a', 72);

$hash = password_hash($string, PASSWORD_BCRYPT);
var_dump(password_verify($string, $hash));


$hash = password_hash($string . 'bbbb', PASSWORD_BCRYPT);
var_dump(password_verify($string, $hash));

Do not use passwords longer than 72 chars, when using the Bcrypt algorithm. The engine cuts the password at that length, and anything longer is lost, for the hash, and later, for the comparison.

Admittedly, this is a rather large limit, so humans probably won’t hit that limit.

On the other hand, it may happen with automated password tools, or when trying to test a login page.

See Also

PHP Features

Last updated: 11 October 2026

Parsing PHP From Within

<?php

const A = 1;

token_get_all('<?php const A = ;');
print "Tokenized\n";

try {
    token_get_all('<?php const A = ;', TOKEN_PARSE);
    print "Parsed\n";
} catch (Error $e) {
    print "Not parsed\n";
}

eval('const A = 1;');

There is an equivalent to php -l with PHP itself. It is not token_get_all(), which only tokenize the code, that is, turn the string into small pieces of PHP syntax.

It is token_get_all(), with the TOKEN_PARSE flag, which also makes sure the tokenized code makes PHP sense.

The next step is to use it with eval(), which now adds various checks, such as definitions and naming.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

eval() Kills It

<?php

$code = <<<'PHP'
class X {}
class X {

PHP;

try {
    eval($code);
} catch (Throwable $e) {
    print $e->getMessage().PHP_EOL;
}

// Fatal error
try {
    eval($code.'}');
} catch (Throwable $e) {
    print 'Caught exception';
}

eval() should be put into a try/catch structure to catch parsing errors. Any invalid PHP code raise an Error, which is catchable.

Once the code is parsable, eval() goes one step further, and executes it. In that case, the code is merged with the current execution scope. In particular, definitions, such as constants, functions, classes, etc… are added to the current list, and, in case of naming conflict, it yields an uncatchable fatal error.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

A Dynamic Constant

<?php

function foo() {} 

const FOO = strtolower(...);
const FOO2 = FOO(...);
const FOO3 = (FOO)(...);

Generally speaking, parenthesis are allowed in static constant expression: this is the case in constant values. Yet, here, the PHP engine returns Cannot use dynamic function name in constant expression.

The problem comes from the last constant definition: it builds a closure with the ellipsis as the arguments. But the name of the function is the constant FOO. As a constant, it should be valid.

Now, that constant is actually not known at compilation time. The PHP engine needs to start and execute some code to have access to the constant value. Hence, it is dynamic.

A variable constant, if you want.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

list() Or array()

<?php

class X { public array $list = [2]; }
$list = new X ;

//list($a) = list($b) = $list->list;
//echo $a;
//echo $b;

[$a] = $c = [$b] = $list->list;

echo $a;
echo $b;
print_r( $c );

PHP 8.6 is deprecating the list() keyword, though not the features, which stays with the short syntax. So, the question now is: what happens when you chain list() and arrays?

To be a list, the [] as to be filled with variables, or any container, and on the left site of at least one assignation = operation. That way, it will be assigned the values from the rightmost array available. You can chain several list at the same time.

Any full assignation in the middle of them will do a full assignation too.

See Also

PHP Features

Last updated: 11 October 2026

No Coercion On Unserialize

By Gina Peter Banyard

<?php

class C {
    public int $a = 5;
    public string $b = "10";
}

$o = new C();
var_dump(serialize($o));

$o2 = unserialize('O:1:"C":2:{s:1:"a";s:2:"10";s:1:"b";i:5;}');
var_dump($o2);

I am genuinely surprised that PHP actually rejects invalid types during de-serialization for typed properties even if they are coercible, kinda goes against my intuition about engine behaviour. Cannot complain however, it eliminates bugs by rejecting dodgy payloads and simplifies the implementation.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Static And Non Static Property

<?php

class x {
   private $p = ' in X';
   
   public function foo() {
       return $this->p . ' ' . static::$p;
   }
}

class y extends x {
    function __construct() {}
    
    public static $p = ' in Y';
}

$y = new y;
echo $y->foo();

After the great static and non static method, here is the property version: it is possible to make the same property static and non-static.

Well, the same named property, obviously, as the linter won’t accept twice the same property name in a class. So, we need a parent and a child class, and one private property: that way, both are separated, and may have distinct definitions.

Later, when we need to have them both in the same method, we can write the method in the parent, so that the private property is accessible. And then, use static to reach the property in the child class.

As usual, this is quite ugly code, and I expect every static analyser to mark that for removal.

See Also

PHP Features

Last updated: 11 October 2026

strripos() Searches From The End

<?php
namespace A {
    use B\C{ 
        const BAR AS FOO 
    };
}
?>

Today, I got bitten by someone (sic) who wrote as in uppercase AS and it made me use PHP strripos() for the first time ever.

Did you know there are 4 strpos() you can rely on?

strpos(), stripos() is case-insensitive, strrpos() search from the end, strripos searches from the end and is case insenstive.

All of them are affected by the strpos() syndrom.

Also, don’t use AS with constants, just because const should be uppercase.

See Also

PHP Features

Last updated: 11 October 2026

Function Call As Default

<?php

class Foo {
    const  BAR = strlen(...);

    function f($arg = strtolower(...)) { 
        return $arg;
    }
}

?>

Constant static expressions are used for constant and default values for parameters and properties. They have strict rules, and must be processed statically, before PHP executes the code. This means that it is not possible to call a function, which belongs exclusively to the execution phase of PHP.

Yet, since PHP 8.4, it is possible to see function names appears in that location: strlen(...) seems to be a forbidden syntax.

In fact, closures may be used to build default value, and one of the way to build them is to use the first class callable structure: that is when the function call includes the ellipsis operator .... So, in the end, it is not a functioncall, but a closure.

See Also

PHP Features

Last updated: 11 October 2026

A Missing Subpattern

<?php

preg_match('/(a)(b)*(c)/', 'ac', $matches);
var_dump($matches);
preg_match('/(a)(b)*(c)/', 'ac', $matches, PREG_UNMATCHED_AS_NULL);
var_dump($matches);

When the final subpattern(s) are not found, PHP does not create their corresponding entry. Hence, the resulting array may have only two entries, while there are actually four subpatterns. To avoid this surprise, use the PREG_UNMATCHED_AS_NULL flag to make them always created, with NULL value when not found.

See Also

PHP Features

Last updated: 11 October 2026

Empty String Subpattern

<?php

preg_match('/(a)(b)?(c)?/', 'a', $matches);
var_dump($matches);
preg_match('/(a)(b)?(c)?/', 'a', $matches, PREG_UNMATCHED_AS_NULL);
var_dump($matches);

When a subpattern in a regex is not used, PHP reports it by default as an empty string. The entry in the result array is filled, and that empty string may be confused with an actual result. So, you might have to use the PREG_UNMATCHED_AS_NULL flag to make that clear.

See Also

PHP Features

Last updated: 11 October 2026

String To Int

<?php

// more than 18 is sufficient
$json = '12345123451234512345';

var_dump(json_decode($json));  // a float
var_dump(json_decode($json, flags: JSON_BIGINT_AS_STRING));
// a string
var_dump(json_encode(json_decode($json, flags: JSON_BIGINT_AS_STRING)));
// string(22) ""12345123451234512345""
// with extra ""

Converting a string to an integer is a job for (int), or intval().

One classic gotcha happens with JSON decoding: the integer in the JSON string might be bigger than the largest PHP unsigned integer.

For that, there is the JSON_BIGINT_AS_STRING option to json_decode(), which keeps the number as a string instead of casting it to a float, for future processing in PHP, or not. PHP might hand out this string to a database that is able to handle it. When that integer is handed again to another JSON, via json_encode(), it will stay typed as a string: there is no flags JSON_STRING_AS_BIGINT.

No error is emitted in this process, by default.

See Also

PHP Features

Last updated: 11 October 2026

strpos() Fights Back

<?php

$string = 'abcdeafghi';

var_dump(strpos($string, 'a', 3) !== false);
var_dump(str_contains($string, 'a'));  // not from offset 1
var_dump(str_contains(substr($string, 3), 'a'));

str_contains() was designed to be a strpos() killer: at least, killer of the classic bug if (strpos('abc', 'a') == false) where the character found at position 0 is confused with its absence.

There are two situations where str_contains() cannot live up to the power of strpos(). The first is the third argument of strpos(), which is the starting offset of the search for the character. str_contains() always starts at 0, strpos() may start where it wants.

The second situation is that strpos() returns a position, suitable for usage with substr() or similar, while str_contains() only returns a boolean.

See Also

PHP Features

Last updated: 11 October 2026

Define Variable By Reference

<?php

function foo($a) {}
// 2 * undefined variable
foo($b);
var_dump($b); // NULL

// no error
function foo2(&$a) {}
// undefined variable
foo($c);
// Now, $c exists
var_dump($c); // NULL

Assigning a value is not the only way to assign a variable in PHP: passing an undefined variable to a method, with a reference, creates automatically that variable. When it is not set inside the method, it still is valued null in the calling context.

In the end, the main difference is only one warning is emitted, instead of two.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Named Constructor And Attribute

<?php

#[Attribute]
class Y {
    public static function get(string $path): self {
        return new self($path, 'GET');
    }

    // non public constructor
    private function __construct(
        public string $path,
        public string $method
    ) {}
}

#[Y]  // Actually, Y::get('/home')
class X {}

$reflectionClass = new ReflectionClass(X::class);
$attributes = $reflectionClass->getAttributes(Y::class);

foreach ($attributes as $attribute) {
    $routeData = $attribute->getArguments();
    $route = Y::get('mypath'); // Manually call the named constructor
}

PHP does not allow the use of named constructors, aka static factory methods, directly as attributes. However, you can work around this limitation by using reflection to access the attribute and then call the static method manually. It is just not the classic manual example, but, just don’t be too manual.

See Also

PHP Features

Last updated: 11 October 2026

Not An Attribute

<?php

class MyNotAnAttributeClass {
    public function __construct(public string $value) {}
}

#[MyNotAnAttributeClass('test')]
class MyClass {}

You can use any class as an attribute, even if it is not explicitly marked with the #[Attribute] meta-attribute. While it is recommended to use #[Attribute] to make a class explicitly an attribute, it is not strictly required. PHP will still treat the class as an attribute if you use it as one, as long as you do not try to instantiate it via reflection. This means you can define a class without the #[Attribute] meta-attribute and still use it as an attribute in your code.

See Also

PHP Features

Last updated: 11 October 2026

printf() With Locale

<?php

setlocale(LC_ALL, 'de_DE');
printf("%.2F ", 1000 + M_PI);  // 3.14
printf("%.2f ", 1000 + M_PI);  // 3,14

The use of locale in PHP code has lowered since PHP 8.0, where casting decimal numbers dropped the support of the numeric locale.

When that behavior is still needed, there is one place where it still exists: the print() family of functions where the f and F formats provide support for the locale-sensitive and the locale-insensitive display of numbers.

Of course, there is also the intl extension to handle these in a modern way.

print() is the last family of function where the locale are used. Before PHP 8.0, locale were used anytime a decimal to string conversion was performed by PHP, silently or not.

See Also

PHP Features

Last updated: 11 October 2026

Replace The n-th In A String

<?php

$string = "An elephant is an elephant";

print str_replace('ph', 'php', $string);
print PHP_EOL;
$closure = function() {
    static $count = 0;
    ++$count;
    
    return $count === 2 ? 'php' : 'ph';
};
print preg_replace_callback('/ph/', $closure, $string);
print PHP_EOL;
$pos1 = strpos($string, 'ph');
$pos2 = strpos($string, 'ph', $pos1 + 3);
print substr_replace($string, 'php', $pos2, strlen('ph'));

Replacing the n-th string in a longer string is trickier than for the first. In particular, the explode() trick won’t work, and so will preg_replace(), with its limit.

This time, we need to use preg_replace_callback(), which allows a closure to be executed to calculate the replacement string. Here, with a counter, we can replace the desired string.

Another solution is to locate the position, and use substr_replace(), which accepts a limit and an offset.

See Also

PHP Features

Last updated: 11 October 2026

Replace One In A String

<?php

$string = "An elephant is an elephant";

print str_replace('ph', 'php', $string);
print PHP_EOL;
print preg_replace('/ph/', 'php', $string);
print PHP_EOL;
print preg_replace('/ph/', 'php', $string, limit: 1);
print PHP_EOL;
$d = explode('ph', $string, 2);
print implode('php', $d);

str_replace() replaces every occurrence of a string by another one. What if you only want the first one replaced?

The easiest solution is to switch to preg_replace(): it works with a static regex, and by using the limit parameter.

A bit further is using explode() with its own limit, in collaboration with implode(). This is tricky.

Later, you may want to use strpos(), to locate the piece of string, and replace it.

Single characters changes may look into strtr().

And finally, what do you do if you need to replace one of the strings, at the nth position?

See Also

PHP Features

Last updated: 11 October 2026

When $café != $café

<?php

$v1 = "café";
$v2 = "cafe"."\u{0301}";
print "are '$v1' and '$v2' identical? \n";
var_dump($v1 == $v2);  //returns false

$café = 1;
$café = 21;

var_dump($café === $café);
var_dump(array_keys(compact(['café', 'café'])));

Here is a classic feature of unicode: it is possible to build the French e acute in two ways: as one char, or as two: an e and a … acute.

In the end, the two unicode strings are different, though they are mostly dislayed the same way. This applies to names: here it happens with variables, but it is applicable to anything that has a name in PHP. Just note that it is never used in the wild.

It also happens in strings, because they are also written in unicode format. Now, it becomes an input validation challenge. It may be used for by-passing security and validations, but also, it may be a valid user input, depending on the way it was entered in their system.

See Also

PHP Features

Last updated: 11 October 2026

Leading Nor Trailing

By Lincoln Russell

<?php
  
 $dst = 'a=b';
 // $dst has no = leading or trailing or missing
 if (!in_array(strpos($dst, '='), [0, false, strlen($dst) - 1])) {
    print $dst; 
 }


if (str_contains($dst, '=') && !str_starts_with($dst, '=')  && !str_ends_with($dst, '=')) {
    print $dst;
}

Occasionally, it’s a drawback to be fluent in the old ways. Wrote this too-clever PHP code and died a little inside:

if (!in_array(strpos($dst, '='), [0, false, strlen($dst) - 1]))

Then a little voice inside me said “didn’t PHP 8 add some string functions…”

It did! Thus:

if (str_contains($dst, '=') && !str_starts_with($dst, '=') && !str_ends_with($dst, '='))

Yay for code that doesn’t require a PhD in wtf were you thinking 😂.

See Also

PHP Features

Last updated: 11 October 2026

Parsing INI With PHP Options

<?php

var_dump(parse_ini_string('A = true'));
var_dump(parse_ini_string('A = true',  scanner_mode: INI_SCANNER_TYPED));
var_dump(parse_ini_string('A = true',  scanner_mode: INI_SCANNER_RAW));

There are multiple configuration options for the native PHP INI parser: the default one reads the case-insensitive string ‘true’ as an integer; the typed parsing, with INI_SCANNER_TYPED produces a boolean, PHP-style; the raw analysis, with INI_SCANNER_RAW does not change anything, and provides a string.

See Also

PHP Features

Last updated: 11 October 2026

null For The Rest

<?php

$string = 'abcdef';

echo substr($string, 2, 3).PHP_EOL; // cde
echo substr($string, 2, 0).PHP_EOL; // (nothing)

echo substr($string, 2, 10).PHP_EOL; // the rest
echo substr($string, 2, null).PHP_EOL; // the rest
echo substr($string, 2).PHP_EOL; // the rest

The third argument of substr() is the length of the string that needs to be included. It counts the number of characters.

This value may be a positive integer, for an actual length, a negative integer for the length of the string minus that value, or 0 for nothing. That last one may be strange by itself, but makes sense for continuity of the feature.

Finally, a very long length captures the rest of the string, just like omitting the value altogether, or, even, passing null. In that case, null and 0 are not the same feature.

This does not apply to the second argument of substr(), which is non nullable, and must be an integer.

All this applies, consistently, to other functions that manage ranges, such as array_slice().

See Also

PHP Features

Last updated: 11 October 2026

array_map() with NULL

<?php

$a = range('a', 'd');
$b = range(13, 16);
$c = ['Orange', 'Blue', 'Red', 'White'];

$e = array_map(null, $a, $b, $c);
print_r($e);

array_map() calls a closure on a selection of arrays. It is lesser known that the closure may be null. In that case, what can be the meaning of mapping arrays with … nothing?.

It turns out that PHP applies a zip operation on the array: it takes the first element of each array, pack it in another array, and set it in the result. Then, does this again.

In the end, it is the opposite to array_column().

See Also

PHP Features

Last updated: 11 October 2026

Check When A String Is An Integer

<?php

$string = str_repeat('123', 8); // Beyond PHP_INT_MAX

var_dump(is_numeric($string)); // true 
var_dump(is_int($string)); // false it is a string 
var_dump(is_int(intval($string))); // true
var_dump((int) $string); // PHP_INT_MAX
var_dump((int) $string == (float) $string); // PHP_INT_MAX

Converting a string to an integer is a classic operation. It is still quite suprising.

is_numeric() checks if the the string contains only digits, decimal separator, exponent and +/-. It might be an integer, but also a float.

Then, is_int() checks if the type of the variable is already an integer: it does not check the content, unlike is_numeric().

Then, it is possible to cast the string directly to integer: any integer beyond PHP_INT_MAX is actually capped at PHP_INT_MAX, without a warning. This operation never fails, and might go on with an unexpected value.

Finally, one approach is to cast to integer, and compare to PHP_INT_MAX or to the float value. At least, here, the overflow of floats leads to INF.

See Also

PHP Features

Last updated: 11 October 2026

$_FILES’ type Is Not Safe

By Alexandre Daubois

<?php

// curl -F "avatar=
@shell
.php;type=image/png"
if (isset($_FILE['upload']['type'])) {
    print $_FILE['upload']['type']; 
}

$_FILES[‘avatar’][‘type’] in PHP is a string the attacker typed.🔐

main/rfc1867.c reads the Content-Type header of the multipart part, cuts it at the first “;” and hands it to you. No sniffing. No validation. NOTHING reads the bytes on disk.

curl -F “avatar=

@shell

.php;type=image/png“ and your check sees image/png.

finfo_file() on tmp_name is the one that actually opens the file.

See Also

PHP Features

Last updated: 11 October 2026

Infinite Recursion

<?php

$a = [1,2,3];
$a[] = &$a;

print_r($a);

function foo($a) {
    foreach($a as $b) {
        if (is_array($b)) {
            foo($b);
        } else {
            print $b.PHP_EOL;
        }
    }
}

// Infinite call
//foo($a);

Recursive functions must be protected against infinite recursion: the kind of loop that never stops.

In this particular case, injecting a reference onto the array itself is a valid PHP syntax. When in the loop, it is detected as an array, and starts again the call. This never stops.

The recursive function must be protected with a nesting level, to avoid such cases.

Native functions, such as print_r() and var_dump() have been protected since PHP 7, at least.

The same problem may be created with an object, and a link to itself, although they are less prone to be used in recursive loop, unlike arrays.

See Also

PHP Features

Last updated: 11 October 2026

An Enum With An Interface

<?php

interface i {}

enum A1 implements i {
    case A;
    case B;
}

enum A2 implements i {
    case C;
    case D;
}

function foo(i $e) {
    var_dump($e);
}

foo(A1::A);
foo(A2::C);

It is possible for an enumeration to implement an interface, even a marker interface. That way, it may group several enumerations together, to behave as one.

Obviously, conditions apply to do it, and, in the end, it is merely a short version of an union type. Readability may apply.

See Also

PHP Features

Last updated: 11 October 2026

Reference In Array

By True Async

<?php
$original = array("data" => "original value");

$element_reference = &$original["data"];
//unset($element_reference);

$copy = $original;
$copy["data"] = "value set in copy";
$copy["foo"] = "another value added in copy";

echo "Original(?): "; var_dump($original);
echo "Copy(?): "; var_dump($copy);

And what happens if we uncomment unset($r)?

In this code, the value of

$b[‘x’] depends on whether unset($r) is executed. In one case,

$b[‘x’] becomes 3; in the other, it remains 1. The important thing is not to mix them up.

Why?

Because $r = &$a[‘x’]; modifies the array by creating a hidden IS_REFERENCE wrapper. From that point on, copies of the array inherit that reference.

However, once the reference’s refcount drops to one, it is lazily dereferenced. After that, copies of the array behave as they normally would.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Duplicate Keys In JSON

<?php

// JSON is decoded
print_r(json_decode('{"a":1,"b":3,"a":2}'));

// with an error
print json_last_error_msg();

// it is actually an error
print_r(json_decode('{"a":1,"b":3,a":2}', flags: JSON_THROW_ON_ERROR));

?>

JSON tolerates duplicate keys in an object, although it is not recommended.

json_decode() decodes it, and keeps the last key available in the object in the result. With default configuration, this works without feedback.

One need to call json_last_error_msg() to actually get the feedback.

Using ‘flags’ option to throw error is stricter, and treats the warning as an error.

See Also

PHP Features

Last updated: 11 October 2026

Binary And Back

<?php

$a = 0b11011_11111;

printf('%b', $a);
print PHP_EOL;
print decbin($a);
print PHP_EOL;

PHP supports a binary format to write integers in the code. The format starts with 0b, followed by 0 and 1, and the optional _ for readability, like any other integer.

To produce back binary numbers, one may use decbin() or printf().

In both cases, the original formatting, binary and underscores, are lost after the linting phase, and cannot be regenerated.

See Also

PHP Features

Last updated: 11 October 2026

echo And print

By Alister Bulman

<?php echo print(1) + 2, 'b';

Thanks, I hate it….

Some explainations: print does not need parenthesis, but accepts them, and they may be part of a larger expression. In this case, the parenthesis look like belonging to print as a function, but it is actually a no operation: this leaves 1 + 2 as the only argument to print, leading to the 3 in the result.

The character in the result is 1, because print returns true after execution. Echo collects it as its first argument, but print has already displayed its result.

Finally, echo also does not need parenthesis, and it also accepts multiple arguments, separated by commas. So the final b is added at the end.

Of course, in practice, no one writes such code.

See Also

PHP Features

Last updated: 11 October 2026

Less Arguments In Child Method

<?php

class x {
    function foo(int $a, string $c, string ...$b) {}    
}

// OK
class y extends x {
    function foo(int $a, string ...$b) {}    
}

It is possible to have less arguments in the child method than in the parent one. This is thanks to variadic.

In general, the number of arguments must grow, and, at best, the extra arguments have to be made optional.

That does not apply when there is a variadic argument, and the previous arguments are of the same type. In that case, the child method can have less named arguments than in the parent class. Technically, it goes from ‘one or more’ string argument, down to ‘zero or more’.

All is in the compatibility between the parent and the child class.

Note that it is not possible to apply this if the previous argument is not of the same type as the variadic one. Nor it is possible to add a new argument, even if it looks extracted from the variadic. Both are not compatible with the parent class.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

strtr() Does Only One Pass

<?php

$map = [
    'foo' => 'bar',
    'foobar' => 'foo',
];

$input = 'foobar and foo';

echo str_replace(array_keys($map), array_values($map), $input);
// Output: 'bar bar and bar'

echo strtr($input, $map);
// Output: 'baz and bar'

strtr() replaces string inside a string, just like str_replace().

The two functions work both with string and arrays of strings as argument, so it is convenient when you need to replace several distinct and non-overlapping strings.

In terms of readability, strtr() accepts a hash with original => replacement strings, while str_replace() needs them split and aligned.

Finally, strtr() only does one pass on the string, and replaces the longest available string. No need to sort the original strings by size, or end up with the replaced string used multiple times. It is actually more efficient and more predictable.

See Also

PHP Features

Last updated: 11 October 2026

basename() As A str_replace()

<?php

print basename('I made a catastrophe', 'astrophe');
// pfious ... disaster avoided ? 

?>

The useless tip of the day is to use basename on any string, to remove unwanted last characters.

The official use is to remove the extension on the file name, but since paths are actually strings, it works. Just avoid strings with / character in them.

See Also

PHP Features

Last updated: 11 October 2026

explode() negative third argument

<?php

$string = 'a,b,c,d,e';

print_r(explode(',', $string, 3));
print_r(explode(',', $string, 0));
print_r(explode(',', $string, -2));

The third argument of explode() is a hidden gem.

When passed as a positive integer, it limits the number of part in the returned array. When it reaches the limit, it saves everything else in the last returned string. It saves on memory and processing time, when there are many separators in a long string.

When passed 0, explode() does nothing. This is not too useful, but it makes sense.

When passed a negative number, explode() limits the number of returned parts to that number, starting from the end. It also means that the last returned strings do not contain the separator anymore, unlike when passing a positive integer.

See Also

PHP Features

Last updated: 11 October 2026

Interface Implemented Too Low

<?php

interface i {
    function foo($a); 
}

class x {
    function foo() {}
}

class y extends x implements i {
    function foo($a = 1) { echo __METHOD__; }
}

An interface’s signatures applies only to the current class and its children. It means that the parent do not have to use the same signature.

Although, the parent and the child must use a compatible signature, independently from the interface. And, in the end, the child signature must be compatible with both the parent and the interface. Note how the parent and the interface methods are quite different, yet are compatible.

While it may be convenient to use the interface on a single child and not on its siblings, it might be less surprising to use the interface on the parent directly. Unless it does not have such a method.

See Also

PHP Features

Last updated: 11 October 2026

What Array Is A List

<?php

$array = [1 => 'a', 0 => 'b'];
var_dump(array_is_list($array));

ksort($array);
var_dump(array_is_list($array));

$array = [1 => 'b'];
$array[0] = 'a';
var_dump(array_is_list($array));

asort($array); // lucky sort
var_dump(array_is_list($array));

array_is_list() was introduced in PHP 8.1, and tells if an array is a list: an array is considered a list if its keys consist of consecutive numbers from 0 to count($array)-1.

There are some edge cases: first, array_is_list() is sensible to insert order: give the right indices in the wrong order, say 1 is inserted before 0, and the resulting array is not a list.

You can then turn that array into an actual list by using ksort(), asort() or sort(), which checks the keys while sorting the array.

Finally, the best way to make the array a list is array_values().

See Also

PHP Features

Last updated: 11 October 2026

Closure::getCurrent() To Reach The Closure

By Tim Macdonald

<?php

    $fibonnacci = function (int $n, $previous2 = 0, $previous1 = 1) {
    if ($n <= 1) {
        return $previous2 + $previous1;
    }
    
    $closure = Closure::getCurrent();
    return $closure($n - 1, $previous1, $previous1 + $previous2);
};
print $fibonnacci(4);

I was happy to see PHP add Closure::getCurrent().

Now I’m using it, I can’t help but think we should add a Closure::callCurrent(/* ... */).

I don’t want to assign a variable, I don’t need the thing. Invoking the return of a function inline also feels needlessly clunky.

See Also

PHP Features

Last updated: 11 October 2026

A Non-transitive Comparison

By Gina Peter Banyard

<?php

var_dump('0' == 0);

var_dump('0' == false);
var_dump(0 == false);

// Non transifive
var_dump('0' == null);
var_dump(0 == null);

Sometimes I truly believe that I have grasped all of the weird semantics of PHP, and then I discover something so simple that breaks an assumption I had about the language.

I knew that NAN == X can return true.

I knew that the comparison operators are not transitive.

I obviously knew that "0" == 0 and "0" == false return true.

And yet it was foolish of me to believe that all instance of X == false would give the same answer as X == null.

As TIL "0" == null is false.

See Also

PHP Features

Last updated: 11 October 2026

Chained Assignation Skips __get()

<?php

class x {
    private string $p = 'a' {
        get => 'c';
        set => $value;
    }
}

$x = new x;
$b = $x->f = 'b';

print_r($x->f);
print_r($b);

Chained assignation skip the get part and assign the same value to all elements in the chain.

When chained assignations are done with variables, the value in the variable is the same as the value out of it: the engine might optimise the work and use the same rightmost value everywhere.

Yet, PHP has dynamic properties, since… a long time ago, and again, with property hooks in PHP 8.4: there, the incoming value may be changed between the set function and the get one. Here, the get is skipped.

In the end, it might be logical, but may also surprise the coder too.

See Also

PHP Features

Last updated: 11 October 2026

eval() And FILE

<?php

eval(' echo "__FILE__ is not `'.__FILE__.'` in `" .  __FILE__."`"; ');

FILE is a magic constant that holds the current file full path. This is not true when using it inside eval(), which comes with an extra suffix that tell it is executed in eval(). In fact, eval() has its own FILE, since it is executed in a sandboxed and temporary area.

See Also

PHP Features

Last updated: 11 October 2026

Fiber Throws Where The Fiber Is

<?php

// Illustrates: $fiber->throw($exception) resumes the fiber and raises the
// exception exactly where Fiber::suspend() paused it, not in the caller's scope.

$fiber = new Fiber(function (): void {
    echo "fiber: starting
";

    try {
        $value = Fiber::suspend('paused, waiting for input');
        echo "fiber: resumed normally with '{$value}'
";
    } catch (RuntimeException $e) {
        // This ordinary try/catch, wrapped around suspend(), catches the
        // exception injected by Fiber::throw() -- no special API needed.
        echo "fiber: caught injected exception: '{$e->getMessage()}'
";
    }

    echo "fiber: finishing
";
});

echo "main: getCurrent() outside any fiber is: ";
var_dump(Fiber::getCurrent());

$suspendedValue = $fiber->start();
echo "main: fiber suspended with '{$suspendedValue}'
";

// This does NOT throw here, in main's scope. It resumes the fiber and the
// exception surfaces at the suspend() call above, inside the fiber's body.
$fiber->throw(new RuntimeException('something went wrong'));

echo "main: fiber finished? " . ($fiber->isTerminated() ? 'yes' : 'no') . "
";

// Starting an already-started (and now terminated) fiber is not a silent
// no-op: it throws a FiberError.
try {
    $fiber->start();
} catch (FiberError $e) {
    echo "main: caught FiberError: '{$e->getMessage()}'
";
}

Fiber is the generator’s sibling that nobody writes tips about, so here is one.

$fiber->throw($exception) does not throw in the caller’s own scope. It resumes the suspended fiber and raises the exception exactly at the Fiber::suspend() call that paused it, as if that call itself had thrown.

That means an ordinary try/catch written around the suspend() call inside the fiber’s body will catch it, with no special API needed on the fiber’s side.

Also note that Fiber::getCurrent() returns null outside of any fiber, and that starting an already-started fiber throws a FiberError, not a silent no-op.

See Also

PHP Features

Last updated: 11 October 2026

Infinite Operations

<?php

var_dump( 2 * PHP_INT_MAX === 3 * PHP_INT_MAX);          // false
var_dump( PHP_FLOAT_MAX + 1 === 3 * PHP_FLOAT_MAX);      // false
var_dump( PHP_FLOAT_MAX + 1E+308 === 3 * PHP_FLOAT_MAX); // false
var_dump( 2 * PHP_FLOAT_MAX === 3 * PHP_FLOAT_MAX);      // true
var_dump( 2 * INF === 3 * INF);                          // true
var_dump( INF ** INF === INF);                           // true

?>

Here are some border line calculations with large numbers in PHP: calculating beyond PHP_INT_MAX is possible, as long as the resulting number is still… a number: here, it is a float. The same calculation is not possible with floats, because they are capped by infinite. Infinite is around 1E308.

In particular, adding extra elements to PHP_FLOAT_MAX yields strange comparisons: if the addition is small enough, it is rounded to 0, and ignored. This is not the case with multiplication, where the numbers are all going beyond PHP_FLOAT_MAX, and are turned into INF, which, by the way, is an float type.

Finally, using power ** on INF is still capped by INF, so it is valid calculation, without overflow.

See Also

PHP Features

Last updated: 11 October 2026

array_find() And array_find_keys()

<?php

$bucket = [1, 2, 'A', 'z', INF, '🐘'];

print array_find($bucket, fn($x) => $x > '🐗' );

print array_find_key($bucket, fn($x) => $x > '🐗' );

PHP 8.4 introduced array_find(), which finds a value in an array, or not. It also introduced array_find_keys(), which finds the related keys, rather than the value itself, which, for some reason, we already have before searching for it.

Note that these two functions are prone to the strpos() syndrome, where a returned null value may be both a valid found value, a non existent value or an empty array: it is probably not wise to look for null.

Also, for no reason, an elephpant is superior to a wild boar.

See Also

PHP Features

Last updated: 11 October 2026

Readonly Class Must Match Its Parent

<?php

readonly class x {
    public string $p;
}

class y extends x {
    readonly public string $p;
}

PHP 8.2’s readonly class looks like sugar for marking every property readonly at once, but it comes with the same strict rule than plain readonly properties: the whole inheritance chain must agree.

This is true even if there are no properties in any class.

A non-readonly class cannot extend a readonly class, since that would let the child sneak in a mutable property.

The reverse also fails: a readonly class cannot extend a non-readonly one, since the parent might already have a mutable property.

It is not possible to mix and match: making all properties readonly does not prevent the engine to ask for the class to be also readonly because of the parent.

There is no partial opt-in: readonly-ness is a property of the whole class hierarchy, not of one class in it.

The most flexible way is to keep the class mutable, and make properties readonly piecemeal.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

The undefined Keyword

By Hammed Oyedele

<?php
  
 declare(strict_types = 1);

if (! class_exists('undefined')) {
   class undefined {}
}

if (! defined('undefined')) {
   define('undefined',  new undefined());
}

class X {
  function foo( 
      SomeType|undefined $object = undefined,
      bool|undefined $option = undefined,
  ) {
     return match($object) {
       undefined => new DefaultBehavior(),
       default => $object->someMethod()
     };
  }
}

This is valid PHP code.

Well, I just discovered you can use an object instance as a constant and also have a class with the same name in PHP.

Laravel have the same concept for missing values except they didn’t actually create a global constant for it.

This would upset some people for sure, but proof of concepts are not a crime!

See Also

PHP Features

Last updated: 11 October 2026

SQL In Memory

<?php
// Create an in-memory SQLite database
$db = new PDO('sqlite::memory:', null, null, [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

$count = 10;

// Measure time to insert $count integers
$startInsert = microtime(true);
$db->exec("CREATE TABLE numbers (id INTEGER PRIMARY KEY AUTOINCREMENT, value INTEGER NOT NULL)");

for ($i = 1; $i <= $count; $i++) {
    $db->exec("INSERT INTO numbers (value) VALUES ($i)");
}
$insertTime = microtime(true) - $startInsert;

$stmt = $db->query("SELECT sum(id) FROM numbers");
$records = $stmt->fetchAll(PDO::FETCH_COLUMN);
print_r($records); // 55

There is no need to set up a whole SQL server to run queries. When the dataset is small enough, but the processing is complex enough, it may be worth starting a SQLITE database in PHP memory, and run the queries there.

There is also a temporary database, which is created with no filename: PHP create a file for this, and removes it at the end of the execution.

Performances are usually much lower than using arrays, but complexity and standard usage may be worth the extra work.

See Also

PHP Features

Last updated: 11 October 2026

The Unreachable Method

<?php

abstract class A {
    private function foo() {
        print __CLASS__;
    }
}

abstract class B extends A {
    public function foo() {
        print __CLASS__;
    }
}

class C extends B {
    public function foo() {
        print __CLASS__;
    }
    
    public function goo() {
        parent::foo();
        a::foo();
    }
}

($c = new C)->foo();
$c->goo();

How can one call the A::foo() method? A::foo() is public, but it is also part of an abstract class. It is not possible to call it directly, as no such object may be created.

It is also the case for B, so we have to call C. Now, between A and C, there is B::foo(), which intercepts the call to A::foo(): it has priority. So C::goo() can reach B::foo() but not A::foo().

The solution is not to use parent but to directly use the name of the class, and the static operator. Even as the target method is not static, it may be called as such from within a child class, and the $this variable will be provided.

This trick works on public and protected methods, but not on private: this is the visibility talking.

See Also

PHP Features

Last updated: 11 October 2026

min() And max() For Interval

By Povilas Korop

<?php

$value = 0.7;

echo min(1, max($value, 0)) ;
print PHP_EOL;
echo max(0, min($value, 1)) ;

PHP trick: min-max a number in one line.

Need a value inside a range between 0 and 1?

Just nest min() and max(): max(0, min($value, 1)).

It pins all below 0 up to 0 and all above 1 down to 1.

Of course, if-else statement may look more readable, if you prefer :)

Addendum: future PHP 8.6 will have clamp() function for that purpose.

Addendum 2: min and max may be swapped, along with their literal, with the same effect.

Addendum 3: it also works on strings.

See Also

PHP Features

Last updated: 11 October 2026

Extra ::class

<?php

class A {
    const B = C;
}

class C {}

const C = new C;

const A = 'Z'; // unused

echo A::class::B::class;
echo (A::class)::B::class;
echo A::B::class;

It is possible to use ::class when reaching for static elements with the :: operator: the ::class returns the full name of the class, which is later used to look for the static. In the end, it does not add anything to the syntax and may be dropped.

Make sure to differentiate the initial name of in the syntax, which is a identifier: it is not confused by PHP with a constant of the same name. On the other hand, after a :: operator, ::class cannot be used on a string and it must be used on another object.

See Also

PHP Features

Last updated: 11 October 2026

Other Types For A Property

<?php

class X {
    
    public string $p {
        set(string|A|null $value)  {
            $this->p = (string) $value;
        }
        
        get => $this-p . ' more';
    }
}

class A {
    function __toString() {
        return 'abc';
    }
}

$x = new X;
$x->p = new A;

It is possible to make a typed property accept more than its definition type.

Thanks to hooked property, the type of the incoming value may be different from the final type: indeed, the property hook has to convert that value to the actual type.

One constraint is that the supported type must be wider than the underlying property type. It is easily achieved by using a union type of the property type and all other needed types.

On the other hand, the get hook can only return its exact type: no covariance, no contravariance.

See Also

PHP Features

Last updated: 11 October 2026

Larger Than Int

<?php

printf('%u', -1);
print PHP_EOL;
print PHP_INT_MAX;

printf() displays an integer as a string. The %u format treats the integer as an unsigned integer, while PHP only process signed integer. Here, -1 is actually 0xffffffffffffff, and the resulting string is actually twice as large as the largest PHP integer PHP_INT_MAX.

Since it is returned as a string, it looks like it is larger, but PHP converts its to float as soon as it needs to process it.

See Also

PHP Features

Last updated: 11 October 2026

Duplicate Type

<?php

class_alias('a', 'c');
use A as B;

// duplicate hidden type
function foo(array | iterable $b) {}

// d8plicate type
function foo2(a | b $b) {}

// a and c are always considered distinct
function foo3(a | c $b) {}

// PHP is smarter than this poor usage of DNF
function foo4((a & b) | (b & a) $b) {}

?>

Union types check for duplicate types, so array|array is not possible, nor if a class is aliased with a use expression: on the other hand, it is valid with a class alias, created with class_alias().

The last case is the union type that actually hides the type: here, iterable includes array, so PHP complains about a duplicate, which is not visible at first sight.

Note that intersectional types also check for duplicate types.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Yield Or Return

<?php

function foo() {
    yield 1;
    yield 2;
    yield 3;
    
    return ['a', 'b', 'c'];
}

function foo2() {
    return ['a', 'b', 'c'];
}

function goo($x, $y, $z) {
    print "$x $y $z\n";
}

goo(...foo());
goo(...foo2());

It is possible to spread an array as dynamic arguments: this applies to returned arrays, after a function call.

The function call may be actually of two types: function or a generator.

The first type is a generator: then, the yielded values are used as argument and the returned array is ignored.

In the second case, the array is returned and used.

See Also

PHP Features

Last updated: 11 October 2026

Multiple Method In Interface

<?php

interface i {
//    function foo($k, $j, $l);
    function foo();
    function hoo();
}

interface j {
    
    function goo();     
    function foo();
}

class x implements  i, j  {
    function foo() {}
    function goo() {}
    function hoo() {}
}

new x;

It is possible for a class to implements two interfaces that are defining the same method. The main condition is to have the same signature for these methods.

When the methods have different signature, with different argument counts, types or return types…, there is no conflict resolution: PHP checks the class’s method signature with the interfaces in order of definition, and it will stop at the first one that fails. This means that fixing the signature for the first interface leads to an error in the second one, but fixing the signature for the second interface leads to an errors for the first one. You’ll have to figure the cycle yourself to solve this.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Operator Overloading

<?php 

use BcMath\Number;

$a = new Number('1.005691112');
$b = new Number('2.567891112');

$result = $a * $b; 

echo $result->round(2);
echo PHP_EOL;
echo $result;
echo PHP_EOL;
echo 1.005691112 * 2.567891112;
echo PHP_EOL;
echo 'precision is '.ini_get('precision');

PHP has no operator overloading in the userland, but it may be achieved by using an extension.

Bcmath has taken advantage of it since PHP 8.3, where it introduced the Number class.

With feature, it is possible to go beyond the 14 decimal digits of standard PHP configuration.

On the other hand, the underlying library’s limitation are also applied, so using the ** operator on decimal Number yield a exponent cannot have a fractional part, which does not appear in the standard PHP operation.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Three Return Errors

<?php

interface I {
    function foo(): A; 
}

class X implements i {

    function foo() : A {
        // return type is not useful here
        throw new Exception('');
    
        // PHP complains that a value must be returned
        return;
    
        // PHP won't complain until execution time
        return 1;
    }
}

There are three errors in this code.

The first is the first return: PHP linter complains that A function with return type must return a value. That is true, but with the initial throw, the returns won’t be ever reached. And removing empty returns is sufficient to appease the linter.

Indeed, the second return pass linting phase, and it is bound to fail at execution time. It is obvious that returning 1 instead of A will fail, but the code could have been returning a variable, whose type could not have been checked. So, PHP left all the checks aside.

Finally, the last wrong part of this code is the return type: the exception makes it irrelevant, and, even in the case of a method, it could have been replaced by the never type, which is covariant with every other type. At that point, PHP complains that the method cannot return.

Definitely, this code doesn’t want to work.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

array_unique() Is Slow

<?php

$array = array_merge(range(0, 100), array());
shuffle($array);

const LOOPS = 1000;

$b = hrtime(true);
for ($i = 0; $i < LOOPS; ++$i) {
    $c = $array;
    $c = array_unique($c);
}
$e = hrtime(true);
print number_format( ($e - $b) / 1_000_000, 2). " ms
";

$b = hrtime(true);
for ($i = 0; $i < LOOPS; ++$i) {
    $c = $array;
    $d = array_keys(array_count_values($c));
}
$e = hrtime(true);
print number_format(($e - $b) / 1_000_000, 2). " ms
";

Why is array_unique() still slower than using a combination of array_count_values() and array_keys? And by an order of magnitude.

Limitations includes that the array can only use integers and strings, so no objects or other arrays (no one should anyway), or floats, boolean and nulls (harder to weed out).

And, of course, the alternative is not actually readable.

See Also

PHP Features

Last updated: 11 October 2026

Cloning Static Variable

<?php

// First class callable
function a() { static $c = 0; return $c++; }
$c = a(...);

$c = function () { static $c = 0; return $c++; };

print $c();
print $c();
$d = clone $c;
print $d();

?>

It is not possible to clone a static variable, but it is well possible to duplicate a closure which contains a static variable. Then, the variable is duplicated with its content at the time of cloning. So, it might be the default value, or it might be an subsequent value if the closure has already been used.

When the original closure is a first class callable, the moment of cloning does not matter.

See Also

PHP Features

Last updated: 11 October 2026

Datetime And Leap Second

<?php

// Leap second on 2016-12-31
$date = DateTimeImmutable::createFromFormat(
    'j-M-Y H:i:s', '31-Dec-2016 23:59:60'
    );
echo $date->format('Y-m-d H:i:s');
// 2017-01-01 00:00:00 

    
$date = DateTimeImmutable::createFromFormat(
    'j-M-Y H:i:s', '99-Dec-2016 99:99:99'
);
echo $date->format('Y-m-d H:i:s');
// 2017-03-13 04:40:39

$date = DateTimeImmutable::createFromFormat(
    'j-M-Y H:i:s', '99-Dec-2016 99:99:100'
);
var_dump($date); // false

The last leap second was added on 2016, Dec 31rst. On that day, 23:59:60 existed, and was followed by 00:00:00 on the first of January. The date time do not handle this, and rather convert the 60 seconds into the next day, silently.

In fact, hours, minutes, seconds and day of the month, all support 2 digits, and accept values up to 99: they are all converted silently to their equivalent date, as if time of that duration passed.

Leap years, on the other hand, are all well supported.

See Also

PHP Features

Last updated: 11 October 2026

Silent Type Cast

<?php

//declare(strict_types=1);

function foo(float $x): int {
    var_dump($x);
    
    return $x;
}

var_dump(foo(1));
//var_dump(foo(1.1));

An integer is a float, but the opposite is not always true. For PHP, pushing an integer in a function that types float its argument, makes that integer an float. The reverse conversion from float to integer is also possible, at return time. This happens when strict_types is not activated, and when the float has no decimal part, as is the case here.

Otherwise, it is a Type error, or an warning about loss of precision.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Objects As Keys In Foreach

<?php

// Weakmaps accepts objects and arrays as keys
$source = new Weakmap();
$key = (object) ['a' => 2];
$source[$key] = 1;

// Yield may emit objects and arrays as keys
$source = function () {
    yield (object) ['a' => 3] => 1;
};
// This is just for illustration
$source = $source();

// An iterator may return objects and arrays as keys(mixed, in fact)
class myIterator implements Iterator {
    private int $position = 0;

    public function rewind(): void { $this->position = 0; }
    public function current(): mixed { return 1; }
    public function key(): mixed {
        return (object) ['a' => 4];
    }
    public function next(): void { ++$this->position; }
    public function valid(): bool { return $this->position == 0; }
}
$source = new myIterator();

foreach($source as $key => $value) {
    print get_class($key); // Stdclass
    print $value;        // 1
}

foreach() usually works on arrays, where the keys are either integer or strings. Not null, boolean anymore, but, more importantly, no array or objects. Yet, there are three solutions to make an object appear as the key, instead of the value (or also as the value).

The first option is to use a Weakmap object, which uses the array syntax, and accepts anything as keys.

Then, you can use a generator, yielding any type on the => operators, left and right. Note that it won’t work with yield from which requires an array.

Finally, you can use an iterator, which has a dedicated method key, with a return type of mixed.

While it is not common in PHP, there are other languages which accepts such structures, and may hand it to PHP for further processing.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

isset() On Uninitialized Properties

<?php

//declare(strict_types=1);

function foo(float $x): int {
    var_dump($x);
    
    return $x;
}

var_dump(foo(1));
//var_dump(foo(1.1));

Typed properties cannot be read until they are written once. And many situations require to check if the property is set before writing it. Often, the type of the property is set to nullable, so it can have a default value of NULL, and then, be both checked and replaced.

isset() and the ?? operator are both able to check the existence of an uninitialized property without triggering the infamous fatal error. With that trick, it is possible to save a nullable type on the property, and help static analysis handling it cleanly.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Recycle Generator

<?php

$g = function (): Generator {
    yield from [1,2,3,4];
};

foreach($g() as $h) {
    print "h:$h - ";
    foreach($g() as $i) {
        echo "i:$i + ";
        
        break 1;
    }
}

After creating a generator, it is possible to nest it and run the same generator twice. It is the role of the functioncall $g(), which actually creates the data source for the loop. Hence, here, the generator is always fresh at the start of the nested loop.

It would be possible to create the datasource before the loop and nest it, though it breaks at the second loop.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Of Integers And Floats

<?php

  $one = 3 * 0.333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333;

    var_dump($one); // float(1)
    var_dump(1 === $one);
    
    $float = 1 / 3 * 3; 
    var_dump( $float );
    $int = 3 / 3; 
    var_dump( $int );
    
    print ini_get('precision').PHP_EOL;
    //   14 three   --------------
    $notOne = 3 * 0.33333333333333;
    print $notOne;

?>

First thing to unpack in this post: PHP accepts any number of decimals when it is hardcoded in the source. It will stop at 14, which is the precision level in the php.ini file. The rest is ignored, so it may be as long as you want, it does not matter, except for the time to read the file.

Then, such a number ends up being a decimal. It means that when multiplied by its inverse, it might end up as 1, but it is still a float, so there is no way to compare it to the integer 1, nor to any other decimal 1, as there might be some tiny difference.

Finally, the order of division is important: when the intermediate result is a decimal, the final result is also a decimal. But when PHP can keep the division within the integer values, like 3 / 3 or 6 / 3, then the final result is also an integer.

See Also

PHP Features

Last updated: 11 October 2026

max() On Strings

<?php

$a = ['shit', 'min', 'acces', 'max'];
echo min($a); // access

$a = ['shit', 0, 'acces', '%'];
echo min($a); // %

// Cute
$a = ['shit', .8 , 'acces', '.9'];
echo min($a); // 0.8 

// Multi-byte
$a = ['通','用','规','范','汉','字','表'];
echo min($a); 

// Multi-byte
$a = ['안','녕','하','세','요'];
echo min($a);

The min() and max() functions return the minimum or the maximum value of items in an array. This works on integers, but it also works on strings.

In the case of string, the comparison is made with the spaceship operator, so the letters are processed in alphabetical order.

The comparison is case-sensitive: upper-case letter have precedence over lower-case letter. That is usually the expected behavior.

Outside the alphabet range, the order of the strings is based on the ascii number of the characters in the string: then, digits are ranking lower than letters and will always appear first. And then, all even lower-ascii characters, such as %, will appear first. This also means that it is recommended to trim() the strings to avoid getting strings with a leading space first as minimum.

It also means that multi-bytes characters are sorted by ascii code of their bytes, not as a whole. Languages, like Chinese, usually do not sort text, so this feature may not be used anyway.

See Also

PHP Features

Last updated: 11 October 2026

e And Euler

<?php

    echo M_E; 
    echo PHP_EOL;
    echo M_EULER;

PHP has two distinct constants: M_E, which stands for the mathematical e exponential constant, and with a value of 2.718281828459.

And the lesser known M_EULER, which stand for the Euler constant. It is used to estimate the number of tests runs to execute when randomly running one of the tests. It is the classic coupon collector problem: E(n) ≈ n * ln(n) + γ * n.

See Also

PHP Features

Last updated: 11 October 2026

Closure On Constructor

<?php

class x {
    public $p = 3;
    
    function __construct() {
        $this->p = 4;    
    }
}

//Cannot create Closure for new expression
//$c = new X(...);

$x = new x();
echo $x->p;
$x->p = 5;
echo $x->p;
$f = $x->__construct(...);

$f();
echo $x->p;

PHP 8.1 introduced first class callable, a syntax to build a closure by using the ellipsis operator ... as argument. This works on all sorts of calls, methods, closure, functions.

The only call where it doesn’t work is the instantiation: PHP generates an error from that syntax.

On the other hand, it is possible to create a closure on the constructor method, like any other method. And there, it just calls again the constructor, on the object that was already created.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

++ Riddle

<?php

$a = 5;
$b = $a++-++$a;

echo $b; // -2
echo $a; // 7

// This, sadly, doesn't compile 
//$b = $a+++++$a;

$a = 5;
$b = $a++ + ++$a; // 12
echo $b; // 12
echo $a; // 7

PHP has no operator ++-++. This has to be read with spaces, as ++ - ++: there is one post increment operator, a subtraction then a pre-increment operator.

In this case, the first operator increment $a from 5 to 6, but returns the original 5. The next operator is the subtraction, which works on the result of the pre-increment operator: this one turns the 6 to 7. The result is then -2.

PHP cannot compile +++++, as it confuses addition and increment operators. Adding spaces makes the code valid, and the same order of operation applies as above, leading to 12.

The original post dates from 2011, and was tested in different languages: the result may surprise you. At least, PHP has been consistent with this since then.

See Also

PHP Features

Last updated: 11 October 2026

Oversigned Integers

<?php

$three = +-+3; // -3
$three = -+-3; //  3

$four  = -+-+4; // 4
$four  = -+-+4; // 4

var_dump(+0 === -0);

var_dump((int) '+-+3');

var_dump(is_numeric('+-+3'));

PHP has signed integers. They may be positive or negative, with + or -.

At parsing time, integers and their sign are separate entities. This means that - and 3 are process in two steps, and PHP allows the chaining of the sign operators. This means that + and - may be added as much as we want, in front of integers, as long as they alternate.

Note that is_numeric() is not fooled by the trick, and returns false on such strings, while the cast operator (int) turns the string into 0, as if there was an error.

Finally, there is one integer which is equal to itself when positive or negative: zero.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Local Variable Cost

<?php

ini_set('memory_limit', 2100000);

function foo($i = 0) {
    print $i.PHP_EOL;
    foo($i + 1);
    $a = 2;
}

foo();

Local variables in PHP methods cost something. Not much, but a bit. And, more importantly, they cost memory even when not used.

Check this simplistic proof of concept: a recursive function, with one local variable. Not, let’s run this script until it dies of memory exhaustion. For that to be a reasonable even, the memory_limit must be set as low as possible: 2.1Mb.

0 local variable: 10918

1 local variable: 8932

2 local variables: 7559

3 local variables: 6550

4 local variables: 5777

5 local variables: 5170

6 local variables: 4678

7 local variables: 4271

21 local variables: 2651

These values do not change with PHP versions: middle versions from 8.0 to 8.6 were tested (and 8.5 did seg fault on OSX)

Also, the numbers of iteration do not change with the default value, except when using an object as initial value.

The number of iteration did not change when a variable was initialized several times: $a = 1; $a = 1; $a = 1 counted as three distinct variable allocation, as if they were distinct.

In the end, it is a micro-optimisation, as it needs thousands of calls to eat up 2M of RAM. But it might be interesting to monitor the number of local variables in a methods, as the worst offenders use up to 350 of them… That must count.

See Also

PHP Features

Last updated: 11 October 2026

Array Items By Value

<?php

class X {
    public int $i;
}

$array = [new x, new x, new x];
$array[0]->i = 0;
$array[1]->i = 1;
$array[2]->i = 2;

function foo(array $array) {
    
    $array[] = new x;
    $array[2]->i = 12;
    $array[3]->i = 3;
    print count($array)." elements in foo
";
}

print count($array)." elements before foo
";
foo($array);
print count($array)." elements after foo
";
print_r($array[2]);

In this code, an array is built with objects. The array is passed by value to the function. The function updates both the array and one of the elements. When the function is finished, the array in the calling context is still the same, but the object #2 has changed.

In this case, PHP applies copy on write, or COW: the array is passed by value, and duplicated only when it is updated. But the copy applies to the objects, which are always passed by reference!

Here, the elements in the array are references, and their update is applied to the original object, in the calling context.

This is a similar situation than with readonly properties, and array_fill(): the reference to the object is unchanged, but the object itself may be updated.

In general, objects may be considered as global values.

This also applies to array_pad().

Since PHP 8.5, it is possible to use array_map(clone(...), $array) on the array to make all the objects distinct, since clone() is now a function.

See Also

PHP Features

Last updated: 11 October 2026

Not Set But Exists

<?php

class X {
    function __get($name) {
        return 'a';
    }
    
    // The important part is 
    // that function __isset() is missing
}

$x = new X();
var_dump(isset($x->a));    //  false
var_dump(!empty($x->a));   // false

// With

var_dump($x->a === null);  // false
var_dump($x->a == '');     // false

In this code, a custom class X is created. When checking the presence of a property a, isset() and empty() both say false. This is consistent with the definition of the class.

On the other hand, doing a comparison between the property and empty string, or null is also false: this looks paradoxical.

In fact, isset() and empty() both rely on the __isset() magic method. Since it is not created in the class, the default answer is false. Yet, the __get() method exists, and returns a non-empty value, so when reaching for the property directly with $x->a, a non-empty value is returned and compared to a falsy literal.

As a conclusion, it is important to provide __isset(), alongside the __get() method to be consistent when an object of the class is used.

This bug may also bite any code that relies on the convention to compare properties by value, instead of checking existence as a separate attribute.

See Also

PHP Features

Last updated: 11 October 2026

Sorting Enum

<?php

enum E {
    case A;
    case B;
    
    case D;
    case E;
}

print_r(E::cases());

enum E2 {
    case E;
    case D;
    case B;
    case A;
}

print_r(E2::cases());

enum E3: int {
    case A = 3;
    case B = 12;
    
    case D = 1;
    case E = 20;
}

$cases = E3::cases();
uasort($cases, fn ($a, $b) => $a->value <=> $b->value);
print_r($cases);

Enum cases are sorted, by default, with their definition order. The name of the case does not matter, just the position of definition within the enumeration.

When the order of definition cannot be changed, as per coding convention, the cases may be backed, and then, sorted at the last moment. Just know that the cases must be in a variable first.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Hypotenuse In Action

By Alexandre Daubois

<?php

// distance between two 2D points
$x1 = 3; $y1 = 4;
$x2 = 7; $y2 = 1;

$dist = hypot($x2 - $x1, $y2 - $y1); // 5.0

// why not just sqrt(($dx)**2 + ($dy)**2)?
// because hypot() avoids intermediate overflow

// QUICK TIP STRAIGHT FROM GAME DEV: sometimes you only want to *compare* distances, and
// square root is SLOW
// in this case, just compare squared values, it's equivalent!
$dx1 = $x2 - $x1;
$dy1 = $y2 - $y1;
$dist1Sq = $dx1 * $dx1 + $dy1 * $dy1;

// some other points in space...
$dist2Sq = $dx2 * $dx2 + $dy2 * $dy2;

if ($dist1Sq < $dist2Sq) { // squared value compared, way faster without square root!
    echo "Point 2 is closer to Point 1
";
} else {
    echo "Point 3 is closer to Point 1
";
}

PHP has a built-in Euclidean distance function, if you need to calculate a distance!

Sounds complex, but it’s just the hypotenus! Without the overflow risk of doing it manually.

If you ONLY need to compare distances, don’t miss the tip at the end of the code snippet!

See Also

PHP Features

Last updated: 11 October 2026

Spaceship Always False

By Jerome Tamarelle

<?php

class A {}
class B {}

$a = new A;
$b = new B;

var_dump($a <=> $b); // 1: A > B
var_dump($b <=> $a); // 1: B > A 
var_dump($b == $a);  // false

Compare A to B, and A is bigger than B.

Compare B to A, and B is bigger than A.

And, in the end, they are still different.

See Also

PHP Features

Last updated: 11 October 2026

Inverted Spaceship Results

<?php

class A {
    public string $a;
    public string $b;
    public function __construct(
        string $a,
        string $b,
    ){
        $this->a = $a;
        $this->b = $b;
    }
}

$a = new A('b', 'a');
$b = new A('a', 'b');

var_dump($a <=> $b);  // 1
var_dump($b <=> $a);  // -1

class B {
    public string $b;  // The property declarations are inverted
    public string $a;
    public function __construct(
        string $a,
        string $b,
    ){
        $this->a = $a;
        $this->b = $b;
    }
}

$a = new B('b', 'a');
$b = new B('a', 'b');

var_dump($a <=> $b);  // -1
var_dump($b <=> $a);  // 1

In the first class, the properties are declared $a, then $b. Comparing the values with spaceship leads to 1, -1.

In the first class, the properties are declared $b, then $a. The rest of the code is the same. Comparing the values with spaceship leads to -1, 1.

This phenomenon disappears when using promoted properties.

See Also

PHP Features

Last updated: 11 October 2026

isset(), empty() And the Magic Methods

<?php

class x {
    private array $config = array();
    
    function __isset($name) {
            print __METHOD__." ".$name.PHP_EOL;
        return isset($this->config[$name]);
    }

        function __get($name) {
            print __METHOD__." ".$name.PHP_EOL;
            return 'a';
    }

}


$x = new x;
var_dump(isset($x->a));
var_dump(empty($x->a));

The __isset() and __get() methods must go hand in hand. One checks that a property, virtual or not, is available, and the other one returns the actual value. Yet, both may be inconsistent one with the other.

In particular, __isset() is called when dealing with isset() and empty().

But __get() may be called directly, and return something, even if __isset() is saying it’s not set.

This might come as a surprise to pieces of code that compare a property with == '' (or equivalent), compared to using isset().

Beyond the strange illustration, it is probably a good practice to always provide __isset() on a class that has the magic method __get.

See Also

PHP Features

Last updated: 11 October 2026

Comparing Arrays And Object

<?php

    $a = array('green', 4 => '3', 'c' => 'yellow');
    $b = array('green', 'c' => 'yellow', '4' => 0x3);
    $c = array('green', 'c' => 'yellow', '4' => '3');

    var_dump($a == $b);  // true   identical, whatever the order
    var_dump($a === $b); // false  identical, but not the order
    
    var_dump($c == $b);  // true   identical, with some type juggling
    var_dump($c === $b); // false  identical, but not at the type level
    
?>

== and === apply different algorithms to compare arrays.

== compares keys without taking order in account, while === also takes into account the order.

== applies type juggling to values, and then compare them loosely, while === makes a identity comparison, with value and type. == and === compare keys the same way, as they can only be int or string, and no type-juggling is applied.

The same rules apply when comparing objects: the order of assignations of the properties is used by == but not by ===.

Finally, comparing an array and an object always fails: one of them has to be cast.

See Also

PHP Features

Last updated: 11 October 2026

Mixed Compatibility

<?php

class x           { function foo(int $a, int ...$args) {} }
class y extends x { function foo(        int ...$args) {} }  // OK! 
//class z extends x { function foo(int $a, int $b, int ...$args) {} } // KO 

class x2            { function foo() {} }
class y2 extends x2 { function foo(mixed ...$args) {} }  // OK! 
//class y22 extends x2 { function foo(mixed $a, mixed ...$args) {} }  // KO

class x4            { function foo($a, $b) {} }
class y4 extends x4 { function foo($b, $c, $d) {} }  // OK! 
//class y22 extends x2 { function foo(mixed $a, mixed ...$args) {} }  // KO

class x3            { function foo(mixed ...$args) {} }
//class y3 extends x3 { function foo() {} }  // KO
//class y32 extends x3 { function foo(mixed $a) {} }  // KOx

class x4            { function foo(int $a, int $b) {} }
class y4 extends x4 { function foo(int $b, int $a) {} }  // KO

Method compatibility means that eponymous methods must have the same number of arguments, and compatible types.

One special case is using variadic: with it, it is allowed to remove arguments, as long as they are the same type, and next to the variadic.

With mixed variadic, it is also possible to go from no arguments to one argument. The opposite is not possible: one cannot drop a mixed ...$args once it was added.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Where Did The Comma Go?

<?php

echo 1,34 . ' euros';

It took me way too long to figure out this one. In certain cultures, the comma , is the decimal separator. And here, it looks all good, in particular since there is a dot . afterwards.

echo does not need the parenthesis, as it is a language construct. In fact, the parenthesis would have prevented the error here, since there cannot be commas inside them.

See Also

PHP Features

Last updated: 11 October 2026

Argument Renaming

<?php

class x           { function foo(int $a, int $b) {} }
class y extends x { function foo(int $b, int $a) {} }

class x2            { function foo(int $a, string $b) {} }
class y2 extends x2 { function foo(string $b, int $a) {} }

Method compatibility does not allow adding or removing arguments between a parent and a child, and types must be compatible.

Yet, it is possible to rename arguments. When the argument types are distinct, method compatibility emits an error, but when both types are identical, this typo may go undetected.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

When htmlemtities() Is Failing

<?php

$string = "Café & 'quotes' and € euro";

// ISO-8859-1 mode
var_dump( htmlentities($string, ENT_QUOTES, "big5"));
// empty string

var_dump( htmlentities($string, ENT_QUOTES, "xxx"));
// default to utf8
// Caf&eacute; &amp; &#039;quotes&#039; and &euro; euro

By default, htmlentities() uses UTF-8. The third argument of that function is the actual encoding, so it is a parameter. When using a non-existing encoding, such as xxx, PHP detects it, and default to UTF-8 (here it works well).

On the other hand, when using a valid encoding, but that is not supported, PHP default to returning an empty string.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Static Arrow Function

<?php

class x {
    private $p = 5;

    function foo() {
        $b = 2;
        $f = fn($a) => $a + $b + $this->p;
        echo $f(1);         // 8 = 1 + 2 + 5 

        $t = $this;
        $f = static fn($a) => $a + $b + $t->p;
        echo $f(1);         // 8 = 1 + 2 + 5 

        $f = static fn($a) => $a + $b + $this->p;
        // Only an error if the code is executed
        //echo $f(1);        
    }
}

new x()->foo();

It is possible to add the static option to an arrow function. In that case, PHP doesn’t allow usage of the local object context, via $this.

On the other hand, it is still possible to access all the local variables of the context, and, if ever, $this was assigned to a local variable, it may be imported in the arrow function context.

Also, note that PHP accepts the presence of $this in the static arrow function, as long as… it is not executed! Since there is no way to modify the static status of the arrow function, it is a zombie function: it may be passed around, but not executed.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

defined() In Action

<?php

const A = 1;

class C {
    public const D = 1;
    private const E = 1;
}

use const A as B;

// global constants
var_dump(defined('A'));
var_dump(defined('\A'));
var_dump(defined('\\A')); // good joke

var_dump(defined('B'));   // false
var_dump(defined(A));     // checking the constant value

// class constants
var_dump(defined('C::D'));
var_dump(defined('C::E'));  // including visibility

The defined() function checks if a constant is defined or not.

It is possible to check the constant with its name, and with its namespace.

Beware of quotes: most often, when checking for the constant’s value, the result is false.

On the other hand, it is not possible to check a constant with its aliased name: extra names, created with use are not validated.

To check for a class constant, it is possible to use a string, with the class name and the :: separator. Then, the visibility of the class constant is taken into account: it might be defined, but not accessible from that part of the code.

See Also

PHP Features

Last updated: 11 October 2026

expm1() And log1p()

By Alexandre Daubois

<?php

$x = 1e-15;

// naive: catastrophic cancellation
$native = exp($x) - 1;
echo $native; // 1.1102230246252E-15, WRONG

// expm1: computed without cancellation
$precise = expm1($x);
echo $precise; // 1.00000000000000005E-15, CORRECT

// same problem in reverse
$y = 1e-15;

// naive
echo log(1 + $y); // 1.1102230246252E-15, WRONG
echo log1p($y);   // 1.00000000000000005E-15, CORRECT

// real-world use: compound interest on tiny rates.
$rate = 0.00001; // 0.001% daily rate
$days = 365;

// naive continuous compounding
$wrong = exp($rate * $days) - 1;

// precise
$right = expm1($rate * $days);

In PHP, expm1() computes exp(x) - 1.

log1p() computes log(1 + x).

“Why not just write exp($x) - 1” you may ask…

Because when x is close to 0, floating point eats your precision alive. Think about it if you deal with finance!

These two functions exist solely to save your math from IEEE 754.

See Also

PHP Features

Last updated: 11 October 2026

Is A Class Constant Set?

<?php

class B {
    const A = 1;
}
$c = "b";


// can't use ??, it is a fatal error 
echo B::{$c} ?? '';

// can't use isset() because it is an expression
if (isset(B::{$c})) { echo B::{$c}; }

// Must use defined() as it is a constant
// and then, use the string syntax
if (defined("B::$c")) { echo B::{$c}; }

// This still yields a fatal error, or will check the class constant content
if (defined(B::{$c})) { echo B::{$c}; }

When using a dynamic class constant, it is important to check if the constant is actually defined.

It is not possible to use the coalesce ?? operator, as a non-existent class constant yields a fatal error.

Then, it is not possible to use isset(), because a dynamic class constant is actually an expression. The error message is actually misleading, as the offered solution is not available.

The only solution is to use the defined() method, which is made to check constants. In the case of a class constant, one need to use the string syntax, and should not use the class constant syntax.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

preg_split() Magic

<?php

$sentence = 'hypertext language, programming';

$keywords = preg_split('/[\s,]+/', $sentence);
// ['hypertext', 'language', 'programming']

$separators = preg_split('/([\s,]+)/', $sentence, flags: PREG_SPLIT_DELIM_CAPTURE);
// ['hypertext', ' ', 'language', ', ', 'programming']

$separator2s = preg_split('/([\s,])([\s]*)/', $sentence, flags: PREG_SPLIT_DELIM_CAPTURE);
// ['hypertext', ' ', '', 'language', ',', ' ', 'programming']

$words = explode(' ', $sentence);
// ['hypertext', 'language,', 'programming']
// comma is still collected

?>

Most of the time, explode() is sufficient to split a string with a static separator. Otherwise, there is preg_split().

preg_split() uses a regex to split the string. This allows for multiple and complex separators to be used in the same call.

preg_split() accepts empty regex, to split strings with nothing: it turns a string into an array. It might require the PREG_SPLIT_NO_EMPTY option, to avoid trailing elements.

preg_split() has a PREG_SPLIT_DELIM_CAPTURE option, to collect the separators along the parsing. Since it might be complex, it is important to get them for further processing.

preg_split(), just like explode(), has a limit parameter, to stop processing the string, once a number of string has been found. This is perfect to prevent PHP from processing too much, as long as a number of expected strings can be predicted.

explode() is faster preg_split(), so use it for the simple and most common cases.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Comparing NAN

<?php

//echo acos(3);
var_dump(acos(3) === acos(3));
var_dump(acos(3) == acos(3));

// Note that NAN is a float and becomes 0
var_dump($array = [acos(3) => 3]);
var_dump(isset($array[acos(3)]));

// Idem pour INF, which is infinity 
var_dump($array = [INF => 3]);
var_dump(isset($array[INF]));

PHP does not compare NAN values: it always fails, even if the source is the same.

Depending on the context, NAN becomes the string 'NAN', or the integer 0.

Since PHP 8.1, the engine emits a warning to signal it: this is good, and helps spotting such mistakes.

And, in the end, no one uses NAN anyway.

See Also

PHP Features

Last updated: 11 October 2026

array_unique() And Enum

<?php
  
enum E: string {
  case A = 'a';
  case B = 'b'; 
  case C = 'c';
  case D = 'd';
}

var_dump(in_array(E::B, E::cases())); 

var_dump(array_unique(E::cases())); 
var_dump(array_unique(E::cases(), SORT_REGULAR)); 

?>

Enumerations works like normal values, most of the time. They may be used as values in arrays, but not as keys, may it be directly, or with casting.

An array of enumerations can be used with in_array(), and it works the same way, with or without the third strict parameter of the function.

On the other hand, array_unique() defaults to comparing values as strings, and enumerations can’t be converted silently to strings, even when backed as strings. To make that PHP native function work, it is necessary to use the SORT_REGULAR option as third argument, which acts as $strict in in_array().

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Display A Tree Natively

By Alexandre Daubois

<?php

$data = [
    'src' => [
        'Controller' => ['HomeController.php', 'ApiController.php'],
        'Entity' => ['User.php', 'Product.php'],
        'Kernel.php',
    ],
    'tests' => ['AppTest.php'],
    'composer.json',
];

$tree = new RecursiveTreeIterator(
    new RecursiveArrayIterator($data)
);

foreach ($tree as $line) {
    echo $line . "\n";
}

// Output:
// |-src
// | |-Controller
// | | |-HomeController.php
// | | \-ApiController.php
// | |-Entity
// | | |-User.php
// | | \-Product.php
// | \-Kernel.php
// |-tests
// | \-AppTest.php
// \-composer.json

// (also works with RecursiveDirectoryIterator!)

PHP has a built-in ASCII tree renderer. In the SPL.

RecursiveTreeIterator takes any recursive structure (arrays, directories, XML…) and outputs a formatted indented tree.

tree command behavior. In PHP. Since 5.3.

Pretty sure I would have use it a few times if I knew it existed 😅.

See Also

PHP Features

Last updated: 11 October 2026

Speed Up CSV Write To Disk

<?php

// Speedy yet memory intensive version
$f = fopen('php://memory', 'w+');
foreach($data_source as $row) {
    // You may configure fputcsv as usual
    fputcsv($f, $row);
}
rewind($f); // Important
$fp = fopen('final.csv', 'w+');
fputs($fp, stream_get_contents($f));
fclose($fp);
fclose($f);

// Slower version
$fp = fopen('final.csv', 'w+');
foreach($data_source as $row) {
    // You may configure fputcsv as usual
    fputcsv($fp, $row);
}
fclose($fp);
?>

When writing CSV files with fputcsv() function, PHP flushes each row to the disk. To speed up the process, it is possible to open a file in memory, with the php://memory wrapper, and write the CSV there. Then, it is possible to write down from memory down to the disk in one batch, saving a lot of disks flushes.

The same trick may be used to write any kind of files: write it quickly, in memory, and then, down to the disk in one batch.

See Also

PHP Features

Last updated: 11 October 2026

php://memory And Maxmemory

By Alexandre Daubois

<?php

// build something entirely in memory...
$fp = fopen('php://memory', 'r+');
fputcsv($fp, ['name', 'email', 'role']);
fputcsv($fp, ['Alex', 'alex@example.com', 'CTO']);
fputcsv($fp, ['Jane', 'jane@example.com', 'Dev']);

rewind($fp);
$csv = stream_get_contents($fp);
fclose($fp);

// send as download
header('Content-Type: text/csv');
echo $csv;

// php://temp, same but spills to disk over 2MB... or more!
$fp = fopen('php://temp/maxmemory:8388608', 'r+');
// 8MB in RAM, then auto-swaps to a temp file

// no gc, no unlink(), no /tmp pollution
// the stream disappears when $fp is closed

php://memory is a stream that lives in RAM.

php://temp starts in RAM, spills to disk after 2MB (configurable, see below!).

No tmpfile(). No temp directory. No cleanup.

Perfect for building files in memory before sending in PHP!

Did you know the maxmemory trick?

See Also

PHP Features

Last updated: 11 October 2026

::parent Operator

<?php

trait t {
    const parent = parent::class;
    const grandparent = self::parent::class;
}

class x {
    use t;  // valid but just don't even think about using x::parent, 
}

class y extends x {
    use t;
}

class z extends y {
}

echo y::parent; // x
echo z::parent; // y

PHP provides the X::class operator to access a class’s fully qualified name. To get the parent’s class, one must use the get_parent_class() native function. Wouldn’t it be cool to have the same operator?

To build that operator, we’ll use a special trait t: it defines a constant, build with parent::class. This operator is the contrary to what we need, but it is the only situation where we use it. Note that parent is a valid constant, or method, name.

Note also that recent PHP versions do not accept a direct call to a trait, so it is not possible to call t::parent, which has no parent.

The ::parent will be case sensitive. The case insensitive versions of this operator is left as an exercise for the reader.

From there, any class that needs to use the ::parent needs to use t;. It is possible to use that trait in any class, but avoid calling the ::parent operator on classes that have no parents.

The trick here is that z::parent actually calls the trait class, which, in turns, uses the parent relative class. Even parent::parent works, as the first one is the keyword, and the second one is the constant.

Make sure that each class use t, as any unconfigured class will defer execution to its own parent, and return… a wrong value.

After ::parent, the ::grandparent operator, based on the same strategy was tempting, and … tried! Sadly, const grandparent = self::parent::parent; yields a (expression)::class cannot be used in constant expressions, even when not used.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Stuttering PHP

<?php

const from = ['yield from'];

$a = fn () => yield yield from from;

foreach($a() as $b) {
    print $b;
}

Not a great joke, but it made me chuckle.

Keep in mind that the first yield actually produces a null, which is displayed as empty string, hence it is not visible.

May be, some PHP words may be forbidden, or, at least, avoided.

See Also

PHP Features

Last updated: 11 October 2026

Riddle: $this<-code

<?php

class X {
    private array $code = [];

    function foo() {
        return (string) $this<-code; 
    }   
}

var_dump((new x)->foo());

Can you make this code work?

The challenge is to only add more code to this snippet, not less.

This syntax is valid PHP code, but one may expect $this->code, instead of $this<-code. Now, PHP is able to parse such syntax, and it then understands it as $this < -code. For this to run, we need code to be a global constant. This can be defined before the class itself, as const code = 2;. Constants may use lowercase, but this is not usual nor tradition.

Then, comparing to $this is not possible, although the left side of the operation is (string) $this. One only needs to turn $this to a string, and then compare it to a number. So, we can add the magic method __toString(), which returns a number in a string.

The final result is true or false, depending on the return of __toString().

In the end, the private property $code is unused. It was a bait.

4 AI were tested on this riddle: three managed to pass, one to fail. Can you?

See Also

PHP Features

Last updated: 11 October 2026

Multiple Const Types

<?php

class x {
    const int|string A=1, B='abc';
	const int|string C = PHP_OS === 'win' ? 0 : 'not win';
}


echo strlen(x::B);

Class constants may be typed since PHP 8.3. Union types are possible, and they are valid with class constants.

In the first case, two constants are declared with different types. PHP complains if any of the types does not apply to the values, so the union type is needed to ensure that both constant are well declared. Obviously, they should be declared on distinct const expressions.

In the second case, the constant is defined with a condition. Now, rand() cannot be called in that part of the code, but using other constants, native such as PHP_OS, __FILE__, custom, global or define() -d may impact the expression, and lead to different results and types. This is a theoretical case, and it has never been seen in the wild.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Replacing PHP http Wrapper

By Alexandre Daubois

<?php

// based on Alexandre Daubois code
// Refactored to make it running here 

class MockHttp
{
    private string $data = '';
    private int $pos = 0;
    public $context;

    public function stream_open(
        string $path,
        string $mode,
        int $options,
        ?string &$opened_path = null
    ): bool {
        $this->data = json_encode([
            'mocked' => true,
            'url'    => $path,
        ]);

        $this->pos = 0;

        return true;
    }

    public function stream_read(int $count): string
    {
        $result = substr($this->data, $this->pos, $count);
        $this->pos += strlen($result);

        return $result;
    }

    public function stream_eof(): bool
    {
        return $this->pos >= strlen($this->data);
    }

    public function stream_stat(): array
    {
        return [
            'size' => strlen($this->data),
        ];
    }

    // Required when using STREAM_IS_URL in modern PHP versions
    public function url_stat(string $path, int $flags): array|false
    {
        return [
            'size' => strlen($this->data),
        ];
    }
}

stream_wrapper_unregister('http');
stream_wrapper_register('http', MockHttp::class, STREAM_IS_URL);

$data = file_get_contents('http://api.example.com/users');

var_dump($data);

stream_wrapper_restore('http');

Did you know you can override built-in protocols such as https://?

You can create mocks! I’d advise to use dedicated tools such as #Symfony MockHttpClient. But in the case of vanilla PHP…

I’ll let you tell me if it’s a good or a terrible idea 😉.

See Also

PHP Features

Last updated: 11 October 2026

Cast But Not A Cast

<?php

$string = '123';
$integer = 0 + $string;
$integer = $string - 0;
$integer = 1 * $string;
$integer = $string / 1;
$integer = $string ** 1;
$integer = + $string;
$integer = - - $string;

echo '123' ** 0;


?>

One frequent way to convert a string to an integer is to + 0 it. And, similarly, a frequent way to convert an integer into a string is to . '', aka concatenate the empty string.

The operators often proceed with a silent cast before running the actual operation. Hence, when using the neutral element of that operator, only the cast remains.

Note also that the silent cast is actually not silent when there is an issue with the conversion, while an actual cast silences these warnings: it is always better to use the cast.

Here are the other cases.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Hexadecimal Separator

<?php

echo 123_456;
echo 12.45_56;
echo 0x12_34;
echo 0b111_110_001;
echo 0b111_110_00_1;

echo mb_chr(0x1F_418); // OK
echo "\u{1F4_18}";   // KO

?>

PHP offers a number separator since PHP 8.0: _. It may be added inside any integer or float, to make long list of digits more readable.

In fact, this also applies to the other integer notations: hexadecimal, octal, binary, etc. Just don’t put it inside the header, like 0_x_1, but in the body of the integer.

Interestingly, the separator does not work in string sequences, like inside an hexadecimal unicode representation.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Call echo With Commas

<?php
  
echo 'a', 'b', 'c'; // abc
echo ('a' . 'b' . 'c'); // abc
print('a' . 'b' . 'c'); // abc

?>

The most efficient way to call echo is to use commas. Each argument of echo is then sent to the output. Echo is not a function, but a language construct, with this special ability.

echo is sometimes used with parenthesis: it makes it look like an actual function call. Yet, it also reduces the number of arguments from arbitrary to one: there can be only one element inside a parenthesis.

Hence, any list of several arguments passed to echo is concatenated into one. This is a useless concatenation, as it is immediately discarded. And echoing the arguments one after the other does the same job.

In the end, it is a micro-optimisation anyway.

See Also

PHP Features

Last updated: 11 October 2026

Reusing Decimals For Bananas

<?php

echo 'BA', sqrt(-1), 'A', PHP_EOL;

echo 'DIS', exp(1000), 'ECTION', PHP_EOL;

echo 'ANTI', log(0), 'LAMMATORY', PHP_EOL;

echo 'dis', strtolower(@implode('', [[]])), 'ment', PHP_EOL;

Besides decimal numbers such as 8.53 or 3.141592654, PHP has 2 extra decimal numbers: INF, for infinity and NAN for not a number. Both of them default to the eponymous string, when displayed. This might be useful.

This trick also works for Array, with some error suppression, or not-too-recent PHP version. Object generates a Fatal error, and Resource displays something that can’t be reused to build another human readable word: Resource id #1: bummer.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Where To Put The Cast?

<?php

$string = '123a';

$integer = -$string; // warning!

$integer = (int) $string; 
$integer = -$integer; // too long! 

$integer =  -(int)$string; // alright !
$integer = @-(int)$string; // also possible !
$integer = @-@(int)@$string; // too much?

Start from a string, and turn it into a negative number. It is actually as simple as adding the - sign in front of the string.

But that might generate a warning, upon non-numeric string. So, the safe way is to cast the string to (int), and then, take the opposite.

Now, this is safe, but too long. In fact, this can be shortened by adding the (int) between the string and the sign, to cast first, then negate.

And, of course, one may also add the noscream operator @.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Unpacking A Single Element Array

<?php

function foo() {
    print_r(func_get_args());
}

$array = [1,2,3];

// Not possible
// cannot use positional argument after unpacking
//foo(...$array, ...$array, 4);

foo(...$array, ...$array, ...[4]);

The ellipsis operator ... is used to unpack arrays as individual arguments in a method call. Its opposite is the [] array, which holds a random number of values.

Using both together is useless, as ellipsis neutralize the array.

Yet, when unpacking several arrays as arguments, it is not possible to use literal values after the unpacked arguments.

The solution is to put these arguments in another literal array, and unpack them, as the previous ones.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Abstract Or Not

<?php

abstract class a {
    abstract function __construct($a);
}

class b extends a {
    function __construct() {}
    
}

A parent method must have a compatible signature with the same method in any child.

The only exception are constructor, __construct, which are not required to keep the signature compatible between parent and child.

This exception to the rule may be explicitly enforced by making the constructor abstract, or by setting the constructor in an interface: in fact, methods in an interface are considered abstract by default.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

(int) And (ant)

<?php

const INT = static function($x) {return  (int)$x * (int)$x;};
echo (int)(" 123s ");
// fatal error
// echo ((int))(" 123s ");

const ant = static function($x) {return  (int)$x * (int)$x;};
echo (ant)(" 123s ");
echo ((ant))(" 123s ");

It is possible to store a Closure in a constant, since PHP 8.5. This means that the closure has to be called by storing the constant in parenthesis: this way, PHP first extracts the value of the constant, then, calls the closure.

There is something else that is between parenthesis: a cast. And cast has a very high priority. So, when a int constant is created, it still calls the cast, and ignores the constant.

May be constants should not be called as casts?

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Cast In Const

<?php

class X {
    const T = (int) 1.73;
    // Fatal error
    //const U = (object) [];
}

echo X::T; //

TIL that constant values accepts casting since PHP 8.5. It is possible to cast a value to another type when defining the global or class constant.

This might be most useful when a constant must be available in several flavor, rather than with literal values.

The only cast that is not supported is (object).

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

global Overwrites

<?php

$argv = [1,2,3];
$_POST = [1,2,3];

function foo(?array $argv = null) {
    if ($argv === null) {
        global $argv;
    }
    
    print_r($argv);
}

foo();
foo([4,5,6]);

The global keyword is actually enough to overwrite a local variable.

In this illustration, the default value is used to trigger fetching the global variable $argv. The instruction is alone in the block, but it actually replaces the current variable, aka the parameter, by the global one. No assignation in sight, merely a surprise.

This won’t work with superglobal, such as $_POST, which can’t be overwritten by a parameter.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Property Hooks And Default Values

By Jan Nedbal

<?php declare(strict_types = 1);

class Example1
{
    public string $foo = 'UPPERCASE' {
        set => strtolower($value);
    }
}

class Example2
{
    public function __construct(
        public string $foo = 'UPPERCASE' {
            set => strtolower($value);
        }
    ) {
    }
}

echo new Example1()->foo;
echo new Example2()->foo;

PHP property hooks

Would you expect equal behaviour?

[Added answer] The default value on the property hook is used when the hook is called. At creation time, no hook is called to initialize the property. So, there is no default value for properties with a property hook, unless the set hook is configured to return something.

See Also

PHP Features

Last updated: 11 October 2026

Static And Not Static Method

<?php

class X {
    function __call($name, $arg) {
        print "normal
";
    }    

    static function __callStatic($name, $arg) {
        print "static
";
    }    
}

X::foo();
(new x)->foo();

Could PHP have both a static and a non-static method, with the same name?

PHP accepts to have several signatures for the same method, yet this has to be handled with different tools, in the body of the function.

On the other hand, since methods must have a unique name, it must be either static or non-static.

To support both static and non-static method calls, the first step is to… remove the methods with the target name: PHP always uses the available method first.

Then, replace them with two calls to magic methods: __call and __callStatic. These two magic methods are called depending on the style of the call: static or not static.

While this is a rather academic question, it might be a useful feature when migrating from a static class to a normal one, while keeping backward compatible syntax.

See Also

PHP Features

Last updated: 11 October 2026

intval() And Its Arguments

<?php
 
echo intval('1AZ')    .PHP_EOL; // 1 
echo intval('1AZ', 11).PHP_EOL; // 21
echo intval('1AZ', 21).PHP_EOL; // 31
echo intval('1AZ', 36).PHP_EOL; // 1691
echo intval('1AZ', 37).PHP_EOL; // 0

echo intval( 123, 11).PHP_EOL;  // 123
echo intval('123', 11).PHP_EOL; // 246

What is happening here? Multiple things to unfold, actually.

The first line is a classic intval() call, which tries to convert a string (or almost anything else), into its decimal equivalent. Here, the conversion stops at A, because it is not a number.

The second line is a less classic intval: the second argument is the numeric base for conversion. Here, with 11, it means that the next number is A, or a. So, the result is 10 + 1 * 11 = 21.

The third line raise the numeric base. While A is still 10, the 1 is now 21, so total is 10 + 1 * 21 = 31. Of course, hexadecimal is supported, with the base 16.

We can raise the numeric base until the last letter, Z : the second argument maxes out at 10 + 26 = 36. There, the string is converted to 1691.

Beyond 36, there is no more available letters in the alphabet, so PHP just silently stops.

Finally, note that providing an integer to intval() has the same value returned as a result, whatever the numeric base. As a string, it is converted.

See Also

PHP Features

Last updated: 11 October 2026

Easy Map

<?php
  
  class X {
     public function __construct(
	     public $id,
    	 public $name,
     ) {}
  }

  $list = [];
  foreach(range(0, 10) as $i) {
     $list[] = new X(rand(0, 1000_000), 'name '.$i);
  }
  
  $map = array_column($list, null, 'id');

  print_r($map)  ;
  
?>

array_column() extracts a property or an index in an array of objects or arrays. It also accepts null as second argument: this represents the original object or array, as a whole.

Combined with the third argument, it makes a convenient one-liner to build a map, based on a list of objects or arrays.

See Also

PHP Features

Last updated: 11 October 2026

Returning Void

By Frederic Bouchery

<?php

function foo(): void {
    return;
    return null;
}

A very large majority think that it doesn’t cause an error and yet, it does. PHP raises a fatal error during the compilation phase when the return type is void but the function contains return statements with values.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Casting An Enum Case

<?php

enum E: string {
    case A = "ab";
}

(string) E::A;

TIL that enumeration cases cannot be cast. They can’t be to a boolean, because they are an object.

They can’t be cast to their scalar value (int or string), even though it looks reasonable to do.

Of course, they can be cast to array (they are simply put inside an array), or to an object (nothing happens).

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Dynamic Class Constant Edge Cases

<?php

class X {
    const A = null;
}

// ?? works with class constants
echo X::A ?? 3;

// parse error, invalid syntax
//echo X::{A} ?? 3;

// quotes are needed, or a global constant
const A = 'A';
echo X::{A};

// @ does not hide undefined constants
echo @X::{'B'};

PHP 8.3 introduces a new syntax to access dynamically class constants: class::{constant-name}.

Class constants works with the ?? coalesce operator, as long as the class constant itself has the value null. On the other hand, the ?? does not work with the dynamic class constant, and yields a parse error.

The dynamic class constant syntax works with unquoted constant names, but then, it requires the related global constant to be existing, and holding the actual name of the constant.

Finally, the no scream operator @ does not continue execution in case of a non existing class constant: it masks the error message, and dies.

See Also

PHP Features

Last updated: 11 October 2026

Dynamic Enum Cases

<?php

enum E: string {
    case A = 'abc';
    const B = 'def';
}

$name = 'E::A';
echo constant($name)->value;
$name = 'E::B';
echo constant($name);

PHP offers the constant() function to reach the value of a constant, when knowing its name.

This also works for class constant, using the class::constant name. This is an alternative to the class::{$name} syntax, that was introduced in PHP 8.3.

And the constant() call also works on enumeration cases, which are, eventually, also class constants.

See Also

PHP Features

Last updated: 11 October 2026

Yield And Keys

By Jerome Tamarelle

<?php

function test() {
    yield from [1, 2, 3];
    yield 4;
}

var_dump(iterator_to_array(test()));

Guess the output.

See Also

PHP Features

Last updated: 11 October 2026

Array Callback Index

<?php

class x {
    static function foo() { echo __METHOD__.PHP_EOL;}
}

['x', 'foo']();
[0 => 'x', 1 => 'foo']();
//Array callback has to contain indices 0 and 1
//[10 => 'x', 11 => 'foo']();
//Array callback must have exactly two elements
//[10 => 'x', 11 => 'foo', 0 =>0, 1=>1]();

// OK! 
[1 => 'foo', 0 => 'x']();

An array callback must be built with two elements, and their index must be 0, for the class or object, and 1 for the method name. PHP enforces the number of elements in the array, and the actual used index.

On the other hand, one may build the array in the wrong order but the correct index, and get the expected result. Nice!

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Time Of Closure Creation

<?php

class x {
   /*    
    function foo() {
        echo __METHOD__.PHP_EOL;
    }
    */
}

$object = new x();
$method = 'foo';

$callable = [$object, $method];
//$callable(1); // Call to undefined method stdClass::foo()

// No check until execution
$closure = fn (...$args) => $object->$method(...$args);
//$closure(1); // Call to undefined method stdClass::foo()

//Call to undefined method stdClass::foo()
$closure = [$object, $method](...);

//Failed to create closure from callable: class stdClass does not have a method "foo" 
Closure::fromCallable([$object, $method]);

Here are four ways to create a closure : a callable array, an arrow function, a first class callable, and a static method. All of them produce the same feature. Here, we focus on the time of checks.

The callable array only collect the variables: the check on these values happen at call time.

The arrow function also collects the values, and report an error only at execution time.

The first class callable checks for the values, reports a failing call to the non-existing method: all this, at definition time. The values must be consistent then.

The static method Closure::fromCallable() also checks the values at its execution time (not the resulting closure’s), and yield a more sensible error message: ailed to create closure from callable: class stdClass does not have a method "foo".

Which one is your favorite?

See Also

PHP Features

Last updated: 11 October 2026

Coalesce And The Fatal Error

<?php

$a = 1;
$b = $a['c'] ?? 'b';
$b = $a->c ?? 'b';

$a = [];
$b = $a->f ?? 'b';

$a = new stdclass();
$b = $a['f'] ?? 'b';

The coalesce operator is based on the isset() feature of PHP. As such, it is prone to the same issues. This is the case with a sneaky fatal error.

Usually, coalesce hide any existence check on the left operand: that way, the default value may be used without a noisy warning. This does not apply when trying to check an array index on an object: it actually yields a fatal error.

Such situation is usually rare, as the structure of the variable is usually known to some extend. On the other hand, using ?? on the result of an unstructured dataset, such as from a JSON or YAML (or else), it becomes far more hazardous.

Note that json_decode() has an option to generate an object or an array. In such situation, it is safer to generate an array, which does not yield fatal errors. It might also be faster to run.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Weakmap And Enums

By Dmitri Goosens

<?php

enum WeekDay: string
{
    case Monday = 'monday';
    case Tuesday = 'tuesday';
    case Wednesday = 'wednesday';
    case Thursday = 'thursday';
    case Friday = 'friday';
    case Saturday = 'saturday';
    case Sunday = 'sunday';
}

class Foo
{
    public function hello()
    {
        return "Hello world" . PHP_EOL;
    }
}

$array = [];
$array[WeekDay::Monday->value] = new Foo();
// etc

echo $array[WeekDay::Monday->value]->hello();

$map = new WeakMap();
$map[WeekDay::Monday] = new Foo();
// etc

echo $map[WeekDay::Monday]->hello();

Have been reminded by a fellow PHP dev that, instead of a simple array, one could also use WeakMap…

Nice thing with this is that it is a real dictionary… and that it accepts objects as keys… and this includes Enum::case.

See Also

PHP Features

Last updated: 11 October 2026

parent In Function

<?php
  
class W {}
class X extends W {}
$x = new X;

$f = function    ()  { return parent::class; };
print $f->call($x);

//function foo(parent $p)  { return $this; }
  
?>

parent shall only be inside a class, as it refers to the parent class. This does not happen with a function, so the second line in this example produces a Fatal Error.

It does not produce the same error with a Closure, or Arrow function. This is valid code because it is possible to change the underlying object of the closure, and give it a valid class to run it with. This is done with the Closure::call() and Closure::bindTo() methods.

The main problem, in this situation, is that such code is invalid a writing location, and becomes valid later, after some reconfiguration. Sometimes, static analysis finds it hard to track.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

isset() And The Fatal Error

<?php

$a = [];
$a['b']['c']['d'] = 1;
$a['b']['c']['d2'] = new stdclass();
$a['b']['c']['d3'] = [];

var_dump(isset($a['b']['c']['d'])); // as expected, and within type
var_dump(isset($a['b']['c']['f'])); // as expected, and within type

var_dump(isset($a['b']['c']['d']['g']));   // as expected, because 'd' is 1
var_dump(isset($a['b']['c']['d']->g));     // as expected, because 'd' is 1

var_dump(isset($a['b']['c']['d3']->f));    // as expected, because 'd' is an array
var_dump(isset($a['b']['c']['d3']['f']));  // as expected, because 'd' is an array

var_dump(isset($a['b']['c']['d2']->f));   // as expected, because 'd' is an object
var_dump(isset($a['b']['c']['d2']['f']));   // fatal error!! 

var_dump(isset($a['b']['c']['D']['g']->f));   // as expected, because 'D' is actually null
var_dump(isset($a['b']['c']['D']['g']['f'])); // as expected, because 'D' is actually null

// isset($a['b']['c']['d']) is set so following are true and within type
var_dump(isset($a['b']['c']));
var_dump(isset($a['b']));

$i = 1;
echo $i[3]; // error,

isset() checks if a variable exists. By extension, it also checks array elements, object properties etc. As the check is performed, any attempt to access an undefined part of the expression is muted: this makes total sense.

For example, if one of the intermediate expression is an integer, it is not possible to access it with an array syntax, unlike an array or a string. Such access is a warning when used outside isset(), but is silent inside the isset().

This leads to a nice optimisation, where checking isset($a[1][2][3][4]) is sufficient to check isset($a), then isset($a[1]), isset($a[1][2]), isset($a[1][2][3]), and isset($a[1][2][3][4]). Nice.

The catch is when of the element inside the actual array is an object. PHP reports a fatal error when using an object with an array syntax (except may be for ArrayAccess objects). Then, isset() stops.

The reverse is not true: accessing an array with a object syntax yields a null and no warning. As it should be.

All this also applies to empty().

A change of behavior was suggested for PHP 8.5, but was down voted. May be in PHP 8.6?

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

All PHP Dereferencements

<?php

enum F: string {
    case PHP = "Rocks!";
}

const E = F::PHP;
const D = new D;

var_dump(x::C->{$p = 'p'}[1](...)()?->{$p . $c = 2}::PHP->value[$c]);

class X {
    const C = D;
}

class D {
    public array $p = [1 => 'foo'];
    public F $p2 = E;
}

function foo() {
    return new D;
}

This ugly expression holds a long list of operators to move from one value to the other and produces the simple letter ‘c’. Can you recognize them all?

First :: is a static constant, with its class on the left, and constant on the right.

The first -> works on a class constant, once the constant is an object. This takes a slight detour by a global constant, and it works. It allows to call a property. This property is reached with a dynamic property name: this introduces the curly braces notation. That dynamic property name is actually a single string, but it is also stored into a local variable: because, why not?

The next operator is the array syntax [1], which accesses the element numbered 1 in the previous array.

Then, this element turns out to be a callback. The callback may be called directly, with the parenthesis (), but it may also be called indirectly with the callable notation (...): this produces a closure, based on the previous callable. In this expression, it actually has no effect.

The result of the callable may, or may not, be an object. This is handled by ?->. If it is not, then the whole expression is actually null. If it is an object, then the evaluation continues with yet another property.

There, there is a second dynamic property, build on top of the first $p and another variable, which is also assigned while being concatenated. This is the p2 property.

Since the p2 property exists on the object of class D, the expression uses the static :: operator again. This time, it is not a constant, but a case from an enumeration. There is no way to distinguish cases and constant at call time.

The enumeration F is backed by a string, so this automatically introduces properties to the enumeration objects, name and value. Here, the second -> operator reaches to this implicitly declared property.

Finally, value produces a string, and strings accept the array syntax. [$c] is actually [2], as $c was assigned the value 2, and this was done at bit earlier in the expression.

At the end, there is one of the most convoluted literal c character.

See Also

PHP Features

Last updated: 11 October 2026

No Property Beyond Limits

<?php

class X {
    public int $p;
}

$x = new X;

$x->p = PHP_INT_MIN;
--$x->p;

?>

TIL that PHP reports attempts to go beyond the limits (upper and lower) of integer properties. Indeed, beyond these limits, the property is converted to a float, which violate the typing.

This error goes away by dropping the type: the property simply changes type.

This also means that no error is reported when doing the same on a variable.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Can’t Be Compatible With Less Arguments

<?php

interface i {
    function foo($a) ;
}

interface j extends i {
    function foo($a, $b = 3);
}

interface k extends j {
    function foo($a = 4, $b = 3);
}

interface l extends k {
    function foo();
}

A method is compatible with its parent’s version when it has at least the same number of arguments. This is valid with classes, but also with interfaces.

A child method is compatible with its parent when adding a new argument, as long as the argument as a default value.

Another child may also be compatible by adding a default value to the existing argument. At that point, calling the method without any argument is now a valid call.

Yet, it is not possible to rewrite the method without arguments because it would be incompatible with the method definition of the parent, even though it is always possible to call both versions the same way.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Arrow Functions Import Variables By Values

<?php

$a = 1;
$f1 = fn() => $a = 2;

$b = &$a;
$f2 = fn() => $b = 3;

echo $f1();
echo $a;
echo PHP_EOL;

echo $f2();
echo $a;
echo PHP_EOL;

Arrow functions automatically import the variables from the current context, to be used in its returned expression. It is still possible to assign a variable in such situation, by using an assignation in the return statement.

Yet, this does nothing, as the variable is imported by value, even when it is a reference in the first place. Hence, reading it access its value, but modifying it has no impact on the original context.

If the reference is important, the alternative is to use a closure, with a reference in the use(&$a) clause.

See Also

PHP Features

Last updated: 11 October 2026

Never Is The Lowest Ranking Type

<?php

class x {
    function foo() : int {}     
}

class y extends x {
    function foo() : never {}     
}

It is always possible to use never as a compatible type in a return type. It is the lowest ranking type, and is compatible with anything else.

Of course, to finish implementing it, the method would also have to never return.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

The Never Function Arrow

<?php

fn($a): never => die();

This is a rare occurrence: that syntax is not valid in PHP 8.1, but valid before and after.

In PHP 7.4 and 8.0, the syntax is valid, because never was not a reserved type. PHP considered it as a class name, but since die would prevent any return, it is not actually used.

Starting PHP 8.2, PHP recognizes the absence of return, due to the die, and accepts it.

Also, great edge case of a closure with a (implicit) return, but typed never. Actually, it doesn’t work with a regular return.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Rewrite For Exit

<?php

namespace A;

use function foo as exit;

function foo() {
    print "FOO!
";
    exit();
}

Since PHP 8.4, exit() is also a function, and may be called as such.

Yet, it is still a PHP keyword, so it cannot be used as a function name: this would allow creating functions with that name in namespaces. On the other hand, it is possible to use that name as a method name.

And it is also not possible to use exit in a use command: that would allow rewriting exit with another function.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Alias Conflicts

<?php

namespace A {

//        class xBefore {}
}

namespace A {
    use y as xAfter;
    use y as xBefore;
    class y {}
}


namespace A {
        class xAfter {}
}

When the class is defined before the alias, the alias fails as the class already uses the name. When the class is defined after the alias, the class fails, although only if the class is in the same physical namespace block as the use.

This illustration is good to show and tell, but in real life, it never happens. On the contrary, the classes and uses are in different files, and the order of loading is controller by code execution, via autoloading.

This means that reusing a namespace’s class name as an alias is prone to random failures, depending on the inclusions.

As a side note, the use command does not need the definition of the original, until actual usage.

See Also

PHP Features

Last updated: 11 October 2026

Not All Incremented Strings Are Deprecated

<?php

$a = 'aaa';
echo ++$a;


$a = 'aaa!';
echo ++$a;

News are that the famous PHP feature to increment strings is deprecated. This is not entirely true, as only the strings that can’t be incremented emit the warning since PHP 8.3.

Truth is, this will be corrected in PHP 8.5, so better start using str_increment() and str_decrement() anyway.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

get_class() Or ::class?

<?php

namespace abc;

class def {}

$x = new \ABC\DEF();
print get_class($x);
print PHP_EOL;
print $x::class;
print PHP_EOL;
print DEF::class;
print PHP_EOL;

Both get_class() and ::class do the same: they report the name of the class of the provided object. The nuances are in the details.

get_class() needs an object as argument, and emits a TypeError if not provided one. ::class works both on objects and class names. The latter are, basically, strings, so ::class work on strings.

get_class() needs the class to be available, while ::class merely formats the name of the class with the internal case: this works on an object, but not if the class name is hardcoded.

And who hardcodes its class names with strings?

See Also

PHP Features

Last updated: 11 October 2026

No Binding But With Original

<?php

class A {
    public function method() {    }
}

class B extends A  {
    public function method() {    }
}

class C {
    public function method() {    }
}

class D { }

$fn = Closure::fromCallable([new A, 'method']);
$fn->call(new B); // error
$fn->call(new C); // error
$fn->call(new D); // error

?>

It is possible to change the underlying object of a closure. The new closure will be executed with the new object, with the same method.

On the other hand, it is not possible to call that closure on another class, even if that class has the same method, nor even if the class is a child of the original one. It must be the same original class.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Infinite foreach()

<?php

$array = [0];

foreach ($array as &$value) {
    print $value.PHP_EOL;
    $array[] = $value + 1;
}

Foreach() is built against infinite loops: it works on a finite number of elements in arrays. Using an infinite generator is possible, but cheating with a second infinite loop.

So, the trick is to update the source array during the loop, to keep giving new keys to PHP, that it will check. Over, and over, and over.

See Also

PHP Features

Last updated: 11 October 2026

Who Modifies This Property?

<?php

class x {
        function __construct(
            int &$y { get => $this->y; },
        ) {}
}

$w = 4;
$x = new x($w);

Several observations in this code:

it is possible to define a property without visibility: the property hook makes it a promoted property.

It is also possible possible to create a reference on a property, as promoted properties are also an argument.

When all these features are together, it yields an error about accessing an uninitialized property.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

mixed Is Not No Type

<?php

class x {
    public $p;
    public mixed $q;
}

$x = new x;
var_dump($x->p);
var_dump($x->q);  // Fatal error!

When a property is not typed, nor has a default value, it still may be accessed freely: its value is null. On the other hand, a mixed typed property may also hold anything, but it cannot be accessed until it was written once.

On development, it might be a good strategy to type properties mixed to spot any uninitialized usage and set them.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

The Recursive array_merge()

<?php

$array1 = ['x' => 1, 3 => 4, 't' => 8];
$array2 = ['x' => 2, 5 => 6,];
$array3 = ['x' => [3, 4, 5]];
$array4 = ['x' => [[4]]];
$arrays = [$array1, $array2, $array3, $array4];

print_r(array_merge_recursive(...$arrays));

array_merge_recursive() merges several arrays together, like its cousin array_merge(). The important difference here is where the recursive applies.

The operation is recursive because the string keys will be merged together into an array, whenever the same key is found multiple times. A new array is created: scalar values are added to this array, and arrays are merged with it. Single keys are kept intact and integer keys are reindexed, starting at 0.

Note that to avoid the merge of sub-arrays, one need to put it inside yet another array (see array).

See Also

PHP Features

Last updated: 11 October 2026

$GLOBALS Is Not Defined

<?php

    print gettype($GLOBALS);
    var_dump($GLOBALS['GLOBALS']);

$GLOBALS is a PHP superglobal, which is always available, no matter the context. It is also not possible to write it, not to unset it.

Yet, it is possible to get that cryptic error message that it is not available.

This happens when trying to reach the context $GLOBALS via the variable that holds all globals: $GLOBALS. That particular entry is unreachable, as it may lead to circular references. Yet, the error message is the same as for any other global.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Cast Private To Array

By Paul Shryock

<?php

class MyClass {
    private $hi = 'Hi';
    protected $hello = 'Hello';
    public $goodday = 'Goodday';
}

$thing = new MyClass();
print json_encode($thing); // {"goodday":"Goodday"}

print json_encode((array) $thing); 
// {"MyClasshi":"Hi","*hello":"Hello","goodday":"Goodday"}

Today I learned that PHP exposes private properties when an object is cast to an array. 🤦.

See Also

PHP Features

Last updated: 11 October 2026

Properties In Interfaces

<?php

interface i { 
    public $p { get;}
}

class x implements i {
    public $p = 1 { get => $this->p;}
}

echo (new x)->p;

Since PHP 8.4, it is possible to have properties in an interface.

In fact, one must add a property to a property. Since this is an interface, that hook cannot have a body.

Hence, the hook has to be defined in every implementing class.

See Also

PHP Features

Last updated: 11 October 2026

Bitwise Operations On String

<?php


var_dump( 1 & 3);      // 1

var_dump( "b" & "a");  // `
var_dump( chr(ord("b") & ord("a")));

var_dump( "1" & "a");  // ! 
// type error
//var_dump( 1 & "a");

var_dump( "1" & "4"); // 0
var_dump( chr(ord("1") & ord("4")));  // 0

The bit operators &, | and ^ work on integers, and use the numbers as a bitfield: each bit in the numbers are compared with the operator, and set accordingly in the result.

The same operators works on strings, where the string is treated as a binary data.

Since PHP 8.0, it is an error to have different types on both sides of the bit operators. Other types are also yielding an error, like with arrays, objects, etc.

When in doubt, consider using the logical operators.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Typed Variables

<?php

class X {
    public static int $int = 1;
    public string $string = 'abc';
}


function foo() {
    $foo = new X();
    $varString = &$foo->string;
    $varString = [];
    //Cannot assign array to reference held by property X::$string of type string 


    $varInt = &X::$int;
    $varInt = 'abc';
    //Cannot assign string to reference held by property X::$int of type int
}

foo();

A local variable cannot be typed.

I mean, it may be typed at argument or return time, but not permanently, like a property.

To create a typed variable, one may use a reference to a property. The property may be typed, and this constraint is extended to the variable, with a dedicated message.

It works on static and normal properties. And it looks a bit cumbersome to use.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Type And Reference

<?php

function foo(int &$x) {
    $x = "foo!";
}

$x = 1;
foo($x);
var_dump($x);

Parameter’s type is check at calling time. Later, the type of the argument is not enforced anymore, and the argument behaves like a local variable: its type may change.

When typing a parameter and passing it by reference, the type is checked at call time, and then, never checked again. This means the variable may change its type, and, by consequence, the original value may also end up with a different type than expected.

If you want persistent typing, use a property: its type is checked at every step of its life.

See Also

PHP Features

Last updated: 11 October 2026

Chaining Spaceship

<?php


function foo($a, $b) {
    return $a[0] <=> $b[0] ?:
           $a[1] <=> $b[1] ?:
           $a[2] <=> $b[2] ?:
               0;
}


$array = [
    [1, 2, 3],
    [1, 3, 5],
    [1, 2, 4],
    [0, 2, 3],
    ];

usort($array, foo(...));
print_r($array);

It is possible to chain several spaceship operations by using the coalesce operator ?:. When the first spaceship operator returns 1 or -1, its value is immediately used. On the other hand, when the operation leads to 0, the coalesce ?: uses the else branch, where the second spaceship operator is used, to the same effect.

This expression here replaces a call to array_multi_sort().

And of course, literally chaining spaceships together is a very bad idea.

See Also

PHP Features

Last updated: 11 October 2026

parse_str() Edge Cases

<?php

parse_str('x=1&y.z=2&a b=3&c+d=4&e[=3', $x);
print_r($x);

parse_str() reads the variables in the URL’s query string, and convert them into an array. When the variable names contain a dot ., that dot is automagically converted to an underscore.

This happens with the dot, but also other characters, such as (space), + (which is a space too), and a single opening [ (when completed with a closing one, that variable becomes an array).

This is coming from previous versions of PHP, where these variables ended being converted into PHP variables. And the listed characters are actually forbidden in variable names. This is not a problem anymore, since PHP avoids creating variables that way, for security reasons. Yet, for backward compatibility reasons, these conversions are still in place.

See Also

PHP Features

Last updated: 11 October 2026

Cast A Closure To Array

By Saif Eddin Gmati

<?php

class X {
    private $p = 1;
    public $q = 2;
}

$x = new x;
$y = (array) $x;
print_r($y);

/*
Array
(
    [Xp] => 1
    [q] => 2
)
*/

$f = function() {};
print get_class($f);  /// Closure
var_dump((array) $f);

/*
Closure
array(1) {
  [0]=>
  object(Closure)#2 (0) {
  }
}
*/
?>

TIL: (array) $obj will result in array<string, mixed> ( properties key/value pairs ), with the exception if $obj is Closure, where the result would be [$obj].

See Also

PHP Features

Last updated: 11 October 2026

list() Does Not Type

<?php

list($a) = null;
list($a) = 1;
list($a) = 'a';
list($a) = 1.2;

function foo() { yield 3; }
// syntax error
//list($a) = ...foo();
list($a) = iterator_to_array(foo());

// fatal error
//list($a) = (object) ['a' => 1];

//Undefined array key 0
list($a) = [];

The list() command does not report that the type of the right operand is not correct. It is then possible to use list() with integers, string or boolean, and obtain null values for all variables.

On the other hand, list() does complain if the right hand array does not contain enough values to fit the number of variables. It also produces a Fatal error with an object, because an object cannot be used as an array.

Finally, list() does not compile with an ellipsis ... on the right side. To convert a Generator into a list of variables, one need to use iterator_to_array() first.

See Also

PHP Features

Last updated: 11 October 2026

Triple Plus Operator

<?php

$a = 1;
$b = $a+++ ++$a;
echo $b;

This is a white space trick, where the expression allows for ++ and + to follow each other. Note that it is not possible to make a quadruple (four times) or pentuple (five times), operator, as PHP does not parse it anymore.

See Also

PHP Features

Last updated: 11 October 2026

Function Name In A Method

<?php

class X {
    function foo() {
        print __METHOD__.PHP_EOL;
        print __CLASS__.PHP_EOL;
        print __FUNCTION__.PHP_EOL;
    }
}

(new x)->foo();

    function foo() {
        print __METHOD__.PHP_EOL;
        print __CLASS__.PHP_EOL;
        print __FUNCTION__.PHP_EOL;
    }

foo();

The magic constants give information on the context of execution of the code. __METHOD__ gives the name of the method, and its related class. __CLASS__ gives the name of the current class, whatever the method. And __FUNCTION__ gives the name of the current function, or, also, the name of the method, though without the name of the class.

Basically, __METHOD__ === __CLASS__ .'::'. __FUNCTION__.

Also, just for fun, you can also use __METHOD__ in a function, and you’ll get the same as __FUNCTION__. The opposite, as we can see, is not true.

See Also

PHP Features

Last updated: 11 October 2026

PHP Quine

<?php
$code = '<?php
$code = %c%s%c;
printf($code, 39, $code, 39);';
printf($code, 39, $code, 39);

A quine is a piece of code that produces itself. It takes not input, and outputs a text that is its own code. The code is run to produce itself.

One may consider a file without PHP tags as a Quine: PHP executes such code by outputting the raw text. It is considered valid, yet too easy to be a quine.

See Also

PHP Features

Last updated: 11 October 2026

Named Parameter In An Array

<?php

function greet($name, $title) {
    print "Hello, $title $name";
}

$args = ['title' => 'Mr.', 'name' => 'Doe'];
greet(...$args);

?>

It is possible to spread the arguments from an array to a functioncall. It is also possible to use the keys to name the arguments, and have them in any order in the array, just like named parameters.

It is possible since PHP 8.0, and was an unrelated error before.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

self Is A Valid Type On A Closure

<?php

class XABC {
    function foo() {
            // static does not apply to self
        $f = static function () : self {
            // __CLASS__ is preserved in the closure
            echo __CLASS__.PHP_EOL;
            return new self();
        };
        
        return $f;
    }
}

var_dump((new xabc)->foo()());

self is a valid type inside a class (trait, interface, enum) to reference the current class. self may also be used with a closure, since the closure use its location of definition. The class name may be exported that way, and instantiated outside the original class.

This applies to static and parent too. Try it wit static for extra fun.

This tips also applies when the closure is made static, although it might be surprising.

This tip applies to arrow functions.

This tip does not applies to functions created in a class (ugh…).

See Also

PHP Features

Last updated: 11 October 2026

Keep Interface New Argument Compatible

By Jerome Tamarelle

<?php

class X 
{
    /**
     * @param int $nbToken
     * 
     * @pslam-param int<1, max> $nbToken
     */
     public function mark(string $place, /*, int, $nbToken = 1 */): void 
     {
         $nbToken = 1 < unc_num_args() ? func_get_arg(1) : 1;
     }
 
}

A reminder for maintainers of PHP libraries: adding a parameter to a method of an interface causes a fatal error in all the classes that implement it. It’s a BC break that should be prohibited in a minor version.

In Symfony, we add the parameter virtually using phpdoc, and read it using func_get_arg().

See Also

PHP Features

Last updated: 11 October 2026

Undefined Constant A On Its Line Of Definition

<?php

class X {
    const abstract = A;

    function foo($a = A) {}
}

const A = new x();

This piece of code returns a puzzling error: ‘Undefined constant A’, on the line 9. This is the line where the constant is defined. And it is not the case of a recursively defined constant (aka, const A = A + 1;).

Since PHP 8.1, global constants can hold an object. This also means that at the time of definition, the class definition is loaded by PHP. This is the case in this code.

After inspection of the class, the constant A is used twice : once as class constant, and once as a default value. The latter is actually valid, as PHP only requires such values to be available when the method is called, without the related parameter.

The culprit is actually the class constant, which requires its value to be immediately set, for obvious consistency reasons. This is where the definition recursion happens.

In the end, the error message on line 9 is just very misleading.

See Also

PHP Features

Last updated: 11 October 2026

http_build_query() And Enumerations

<?php

enum e : string {
    case A = 'a';
}

print http_build_query(['a' => 'A', 'b' => e::A]);

// PHP 8.4 and more recent : a=A&b=a

// Pre PHP 8.4 : a=A&b%5Bname%5D=A&b%5Bvalue%5D=a  a=A&b[name]=A&b[value]='a'

When using a backed enum with http_build_query(), the PHP native function uses the value of the case, and not the case itself. This is very convenient to output a compatible value, instead of an internal object. This is valid since PHP 8.4.

See Also

PHP Features

Last updated: 11 October 2026

Cannot indirectly modify readonly

<?php

class X {
    readonly public string $property;

    public function __construct() {
        $this->property = 1;
    }
}

$object = new x();
$reference =& $object->property;
//Cannot modify readonly property X::$property 

class Y {
    readonly public string $property;

    public function __construct(&$a) {
        // first time assignation
        $this->property = &$a;
        //Cannot modify readonly property X::$property 
    }
}

$a = 'A';
$object = new Y($a);

The same error message ‘Cannot indirectly modify readonly property X::$property’ is used when trying to sneak a reference on a readonly property, and updated it later.

Strangely, this is also the same error message when the readonly property is initially assigned a reference, for later sneaky update.

The error is the same, indeed, yet may be these messages may be distinct.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Ellipsis And Coalesce

<?php

$a = [3];
foo(...$a ?? range(1,2));

$a = [null];
foo(...$a ?? range(1,2));

$a = null;
foo(...$a ?? range(1,2));


function foo() {
    print_r(func_get_args());
}

Ellipsis, aka ... three dots, applies only to arrays and Traversable objects. It also has a lower priority than the ?? coalesce operator, which means that ?? is applied before the ....

This way, when the variable is undefined, it is still possible to unpack it, after having given a default value.

See Also

PHP Features

Last updated: 11 October 2026

namespace namespace

<?php

namespace namespace {}

?>

The only forbidden name for a namespace is namespace. Not only is it confusing, but also, that keyword is used later for prefixing names. The correct names follow the usual PHP regex, with a leading letter or underscore, followed by a list of alpha-numeric characters; unicode beyond 255 are also used.

See Also

PHP Features

Last updated: 11 October 2026

Namespace, But Far In The Name

<?php

// Not allowed
namespace namespace {}

//syntax error, unexpected namespace-relative name "namespace\A", expecting "{" 
namespace namespace\A {}

// namespace may be used, but not in first position
// surely, not a good idea
namespace A\namespace {
    const B = 1;
}

namespace A {
    echo namespace\namespace\B;
}

?>

Namespaces can be named anything, except namespace. That keyword is used as a prefix, on a name (class, function, constant, etc.) to represent the current namespace, and allow for relative sub-namespaces.

Then, the namespace keyword cannot be used as the first part of a namespace, as it would conflict with the namespace relative part.

But it is possible to use namespace later in the name.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Hidden Nullable

<?php

function foo(string $a = null) {}


const A  = null;
function bar(string $a = A) {}

foo();  // Deprecated 
bar();  // Fatal error

?>

A hidden nullable is a, now deprecated, PHP features where typed parameters with a default value of null are automatically nullable.

Well, that is true only if PHP can spot the null at compilation time: when the null is hidden (sic) in a constant, then it is not recognized, and it leads to a Fatal Error.

This is due to PHP postponing the evaluation of the constant to execution phase.

See Also

PHP Features

Last updated: 11 October 2026

Remove Last Item In An Array

<?php

$array = range(0, 100);

array_pop($array);

unset($array[array_key_last($array)]);

$array = array_slice($array, 0, -1, preserve_keys: true);
  
?>

Three ways to remove the last item in an array.

array_pop() is the most adapted function, as it does actually that.

unset() is dedicated to removing elements, though it requires the calculation of the last key before. It is still the fastest of all three.

array_slice() works with the negative offset, just like a string. It is not too much slower, until one realize its result needs to be reassigned.

All in all, they need a good million iterations to see actual performances differences. It is a micro-optimisation.

See Also

PHP Features

Last updated: 11 October 2026

Cancelling Native Function

<?php

namespace A {
    function print_r($x) {
        print 'No print_r for you!'.PHP_EOL;
    }
}

namespace A {
    $x = [];
    print_r($x);
}

// It looks like it, but 
// A\B is not a child of A!! 
namespace A\B {
    // Just print_r for you, with fallback
    print_r($x);
}

?>

PHP native functions are part of the global scope. In a custom namespace, the local definition of a function has priority. And when this fails, PHP fallbacks to the global space. This is a backward compatibility process, that prevents developer from adding ALL native PHP functions as a use expression in every file. This would be long, boring and a performance boost.

To cancel a PHP native function, such as print_r or var_dump, the trick is to create a definition of that function in the same namespace. It is then used with precedence, and may be prevented from having any impact.

This would be different from using the disable_functions: then, the function undefined, and shall be defined again, in the global namespace, to be properly neutralized.

This trick does not work with classes.

See Also

PHP Features

Last updated: 11 October 2026

Classes Constant Disambiguation With Parenthesis

<?php

class A { const B = 'A::B is a constant'.PHP_EOL; }

class C { const B = 'C::B is a constant'.PHP_EOL; }

const C = new A;

echo C::B;    // C is the class
echo (C)::B;  // C is the constant

// C is a class here
var_dump(new C instanceof C);
// (C) is the constant, 
var_dump(new C instanceof (C));

// instanceof works with objects too
var_dump(new C instanceof (new C));

// but this is invalid syntax
//var_dump(new C instanceof new C);

?>

The :: (scope resolution) operator and the instanceof operator in PHP are strictly designed to work with class names, not with variables or constants holding object instances. Even when a constant exists with a name identical to a class, PHP will not automatically treat it as an object for these operators. The constant’s value, even if it holds an object, will not be used unless it is explicitly dereferenced. To dereference and force evaluation of the constant’s value as an object, you must enclose the constant in parentheses. This ensures PHP evaluates the constant and retrieves the object it contains before applying the operator.

See Also

PHP Features

Last updated: 11 October 2026

compact() Is Recursive

<?php

$a = ['b', ["c"]];
$a = ['b', [[[["c"]]]]];
$b = 1;
$c = 2;

$d = compact($a);
print_r($d);

?>

compact() accepts an array of strings… and arrays. It uses the available arguments recursively, yet produces an array with only one level: the deep nesting is lots at compacting type.

In fact, if the variables are indeed available at compact() time, compact flattens the array, and turns it into a simple list.

It also means that it is prone to recursive error.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Final Parameter

<?php

class X {
    function __construct(
        //Cannot use the final modifier on a parameter        
        private final $property
    ) {}
}

Final is reserved for class elements, such as property, methods and constants. They do not apply to parameters, unless the parameter is a promoted property, in the constructor signature.

In that case, the property cannot be declared in the constructor, and must be declared independently, or without the final key word.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Is Not A NAN

<?php
  
var_dump(NAN === NAN); // false

var_dump(is_nan(NAN)); // true
  
?>

NAN is a special PHP constant, which represents not-a-number: it is returned when a math function cannot calculate a value because it is out of its domain of application. For example, acos(3) returns NAN, because cos() produces values between -1 and 1.

One special situation with NAN is that it is always different from anything. So, it cannot even compare to itself.

Also, NAN is a float.

See Also

PHP Features

Last updated: 11 October 2026

Impossible Named Arguments

<?php
  
function foo(...$args) {
    echo $args['a b' ] ?? $args['a$b'] ?? $args['我'];
}

foo(...['a b' => 'c']);  // c
foo(...['a$b' => 'd']);  // d
foo(我: 3);              // 3
  
?>

Named parameters use the parameter name to allocate the argument. This implies that the provided parameter names are all compatible with a the variable name format. For example, it is not possible to use a space or a $ sign in a variable name.

On the other hand, using the spread ... operator on an array turns the string keys as named parameter. Any string may be an array index, so they are transmitted to the function. There, they are not validated against the variable name format, but simply checked against the list of existing parameters.

An edge case is the variadic parameter. That parameter accepts any named parameter, even if it does not check the variable format, as these values are stored in an array

And that’s how it is possible to use impossible named parameter.

See Also

PHP Features

Last updated: 11 October 2026

func_get_args() Skips Variadic

<?php

function foo($a, ...$arg) {
    print_r(func_get_args());
    print_r($arg);
    print_r($a);
}

function goo(...$arg) {
    print_r(func_get_args());
    print_r($arg);
}

foo(...[ 'b' => 2, 'a' => 1,'d' => 3]);
print PHP_EOL;
print PHP_EOL;
goo(...[ 'b' => 2, 'a' => 1,'d' => 3]);

func_get_args() does not provide the variadic argument, when there is one. It is simply omitted.

func_get_args() lists only the declared parameters, with their position, rather than their name.

func_get_args() is the traditional way to hand arbitrary number of arguments, and with this difference of behavior related to variadic, it provides a different set of features.

Nowadays, 23% of PHP applications use this feature.

See Also

PHP Features

Last updated: 11 October 2026

Ellipsis With Array Parameters

<?php

function foo($a, $b) { echo "$a $b
";}

foo(...['x', 'y']);                 // x y
// named parameters
foo(...['a' => 'x',  'b' => 'y']);  // x y
foo(...['b' => 'x',  'a' => 'y']);  // y x

// positional parameters: integer key not used
foo(...[0 => 'x',  1 => 'y']);      // x y
foo(...[120 => 'x',  11 => 'y']);   // x y
foo(...[11 => 'y', 120 => 'x']);    // y x

?>

It is possible to spread parameters from an array, when calling a function.

With string keys, the parameters are named and assigned their corresponding parameter.

With integer positions, the parameters are used depending of their position in the array, not their actual key.

It might require a call to ksort() or array_values() to make it clear.

See Also

PHP Features

Last updated: 11 October 2026

Named Parameter Inheritance

<?php
  
interface I { function foo(int $i); }

class X implements I { function foo(int $x) { echo $x; } }

(new X)->foo(x: 1);
(new X)->foo(i: 1);
  
?>

PHP enforces the methods compatibility with their types, names, and various options, but not with the parameter names. This means the parameter name existence is enforced, but the name might change. It might change between the parent and the child, but also, between the class and the interface.

It is recommended to keep all these parameter’s name identical, so as to keep consistence in the code, and simplify the usage of the method, with named parameters.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

__invoke() On New

<?php

class x {
    function __construct($i = 0) { echo __METHOD__.PHP_EOL;}
    
    function __invoke()          { echo __METHOD__.PHP_EOL;}
}

$x = new x;

$y = new $x()();
// identical to 
//$y = (new $x(0)) ()

var_dump($y);
// NULL 

?>

It is possible to create a new object by calling new on a previous object: PHP fetches the name of the class, then its constructor.

With the PHP 8.4 new syntax without parenthesis, it is also possible to chain the instantiation with a direct call. This calls the __invoke method.

This was not possible in previous versions.

See Also

PHP Features

Last updated: 11 October 2026

Method Or Property?

<?php

$object = new X();

$one =     $object->foo();   // X::foo

$two = new $object->foo();   // object Y {}

class X {
    public $foo = Y::class;
    
    function foo() {
        return __METHOD__;
    }
}
class Y {}
  
?>

The first line is a simple call to the method foo, on an object whose class is not show here: if this works, there is a method foo() in that class.

The second line is a simple call to the property foo, on an object whose class is not know (again): it has a property called foo. Then, the result of that property, hopefully a string with a class name, or another object, is instantiated, without any arguments.

The nuance is subtle.

See Also

PHP Features

Last updated: 11 October 2026

No More Class _

<?php

class _ {}

function _($_) { echo $_; }

const _ = 'hello PHP';

_    (_:_)     ;

?>

Naming a class _ has been deprecated in PHP 8.4.

Luckily, that name is still available for functions and constants, which gives this beauty.

There is already a PHP extension function called _, which is an alias of gettext(), from the gettext extension. If you try this at home, avoid loading that extension, disable the _() with disable_function, or call it __().

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Method Not Property Name Must Be String

<?php

class X {
    static public $p = 1;
    static function foo() {}


    function __toString() :  string {
        return 'foo';
    }    
}

$name = []; // try with different type : bool, int, float, object..
try {
    echo X::$name;
} catch (Error $e) {
    print $e->getMessage();
}

$name = [];
try {
    echo X::${$name}();
} catch (Error $e) {
    print $e->getMessage();
}

?>

A dynamic property name must be a string, and it must exist. It cannot be anything else, as not casting to string happens.

On the other hand, a dynamic property name is forcefully cast to a string before accessing its value.

See Also

PHP Features

Last updated: 11 October 2026

Static Properties

<?php

    class Test {
        public static $x = 0;
    }
    class Test2 extends Test {
        // $x is inherited
    }
    class Test3 extends Test {
        // $x is NOT inherited
        public static $x = 3;
    }
    
	$x = 1;
    Test2::$x = &$x;
    
    var_dump(Test::$x, Test2::$x, Test3::$x);
    // int(1), int(1), int(3)
?>

It is easy to read that the property $x is of type Test (via static), with an impossible default value of 0. In fact, this property is not typed, as using static is not a possible type for properties.

Secondly, static properties are inherited and shared with the parent static properties, unless they are declared again. In that case, they are distinct.

See Also

PHP Features

Last updated: 11 October 2026

The New And The Parenthesis

<?php

// pre-requisite
$a = 'a';
const ab = 'ab';
class ab {
  function __construct() { echo __METHOD__.PHP_EOL; }
}

// create classes
new ab;
new ab();
new (ab);
new ($a.'b');
new (ab::class);

// PHP 9, a wish!
//new ()ab; 

?>

Where does PHP accept parenthesis in a new call?

First, no parenthesis is possible, as long as the constructor has no parameter.

Secondly, also, parenthesis are possible, after the class name.

Then, the parenthesis may be around the class name, as long as there is a constant which holds the name of the class. Bonus point for the class and the constant with the same name.

After that, there is a dynamic call, where the full name of the class is build, then used.

And the last one is the constant version, with an explicit ::class operator.

Now, it would be great to have a syntax where the parenthesis are before the class name: no other language has that! So, for PHP 9, but more importantly: what for?

See Also

PHP Features

Last updated: 11 October 2026

Fluent Function

<?php

function WTF($arg) {
    // Payload: count the number of its own calls
    global $c;
    ++$c;
    
    // payload could be anything
    // use global to extract values
    global $collection;
    if (!isset($collection)) { $collection = []; }
    $collection[] = $arg;
    
    // also possible
    // $r = __FUNCTION__;
    // return $r(...)
    
    return __FUNCTION__;
}

WTF(1)(2)(4)(8)(16);

echo "WTF was called $c times
";
print_r($collection);

?>

A fluent interface allows the chaining of method calls. It is a bit harder to do with functions, as there is no supporting object, but it is possible.

Returning __FUNCTION__ allows the subsequent call of the same function.

Returning a closure of itself is also possible, yet a bit more cumbersome, as __FUNCTION__ is not recognized as a string for a function call.

Thanks to Andreas Heigl for the inspiration.

See Also

PHP Features

Last updated: 11 October 2026

Cannot Use Isset On Expression, So Use What?

<?php

var_dump(isset(NULL !== A));
    
// Fatal error: Cannot use Isset() on the result of an expression

isset() works on data containers, such as variables, properties, array elements, but not on literal or constant values.

For literal values, it is recommended to avoid using isset, but make a comparison to null, instead.

And of course, the error message is the same when the code uses both solutions at the same time. This may lead to confusion: the message suggests using null !== (NULL !== A), instead of isset(NULL !== A).

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Constant Redefinition

<?php

 const A = 1;
 const A  =1; // warning!
 
 class X {
     const B = 2;
     const B = 2; // fatal error
 }

It is possible to specify several identical global constant definition: PHP reports a warning, and ignores the second definitions.

On the other hand, a duplicate class constant definition is a fatal error, and fails the compilation phase.

The difference of behavior may be linked to the level of consistence that PHP can achieve: global constants needs an application wide check for name unicity, while class constants can be checked while compiling a class.

Yet, it might be worth looking into leveling both errors, to avoid hard to find bugs.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Class Constant In String

<?php

$a = new STDClass;

// Syntax error!!
// syntax error, unexpected token "}", expecting "->" or "?->" or "{" or "["
//print "{$a::class}";

print "{$a::class[0]}{$a::class[1]}{$a::class[2]}".
      "{$a::class[3]}{$a::class[4]}{$a::class[5]}".
      "{$a::class[6]}{$a::class[7]}";

// stdClass

?>

Interpolation is injecting variable values inside strings. For complex variables, such as objects, it requires the curly braces to work.

Yet, the simple $object::CONSTANT doesn’t compile. But, the error message mentions it could do something with a square bracket, so it is still possible to access the value of strings by using the array notation.

I wish there was a better way to inject that class name in the string, or, any class constant.

Also, note that the reported class name is the definition one, not the name that was used at instantiation.

So many surprises.

See Also

PHP Features

Last updated: 11 October 2026

Return Int Not Bool

<?php

$array = range(0, 10);

usort($array, withBool(...));
print(implode(', ', $array)).PHP_EOL;
// 9, 10, 7, 8, 5, 6, 4, 3, 2, 1, 0

usort($array, withInt(...));
print(implode(', ', $array)).PHP_EOL;
// 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10

function withBool($a, $b) : bool { return $a <=> $b; }

function withInt($a, $b) : int { return $a <=> $b; }

?>

There is a cute deprecation warning, when using a closure to sort an array: Deprecated: usort(): Returning bool from comparison function is deprecated, return an integer less than, equal to, or greater than zero.

And if this deprecation is ignored, the boolean is cast automatically to an integer. As PHP turns 0 to false, and everything else, including 1 and -1 to true, this leads to surprising sorting effect.

Just trust the deprecation notice.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

By Reference, Error Or Notice?

<?php

const A = 1;

function &foo(&$a) {
    return [];
}

$a = 1;
$a = foo($a);
// Notice: Only variable references should be returned by reference

foo(1);
// Fatal error: Uncaught Error: foo(): Argument #1 ($a) cannot be passed by reference

?>

Passing a literal by reference to a method causes a Fatal error, while returning a literal by reference only triggers a Notice. This difference in error level is puzzling, given how similar the operations are. It raises the question: why does one completely halt execution, while the other merely issues a warning and continues running?

See Also

PHP Features

Last updated: 11 October 2026

Call Function With Strings

<?php

function foo() { echo __FUNCTION__; }

foo();
'foo'();
b'foo'();
"foo"();

$x = "oo";
"f$x"();

Usually, a function is called with its name as first element of the syntax.

And it is also possible to use a string, either in a variable or directly as a literal. This means that adding single or double quotes to the name of a function is still a valid syntax.

Of course, there is still the left over binary string of PHP 6, so it is also possible to add an initial b to the string.

Then, the double quoted strings accepts interpolated variables, to make the call dynamic.

Finally, it is not possible to use the HereDoc syntax, as parenthesis are not allowed directly after the closing tag.

See Also

PHP Features

Last updated: 11 October 2026

Non Empty Match

<?php

switch ($a) {}

match($b) {};

?>

PHP allows the empty switch: that is, a switch command with an empty block of cases. The whole expression does nothing, and it is as useless as an if statement with an empty block.

The match statement, on the other hand, compiles just like the switch but it also enforces the default case, with an Uncaught UnhandledMatchError: Unhandled match case NULL exception. This one must be caught, or one must add a default case, with a value, to the match to keep it useless.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Named Sqlite3 In Memory

By archtechx

<?php

$foo = new PDO('sqlite:file:foo?mode=memory&cache=shared');
// add some tables and rows to foo

$bar = new PDO('sqlite:file:bar?mode=memory&cache=shared');
// $bar remains totally independent


$foo2 = new PDO('sqlite:file:foo?mode=memory&cache=shared');
// $foo2 contains the EXACT SAME database as $foo

Sqlite3 allows the creation of memory databases: the database is not stored on the disk, but in memory. This is good for temporary data, that requires some SQL features.

Then, by default, every instantiation of a Sqlite3 memory database provides the same database. Then, when using the command sqlite:file:foo:?mode=memory&cache=shared, the memory database gets a name, and it is now possible to have distinct sqlite3 instances.

See Also

PHP Features

Last updated: 11 October 2026

Variable Creation

<?php

$f = function () use (&$a) { $a = [1]; };
var_dump($a);

$x = new class ($c) { function __construct(&$d) { $d = []; } };
var_dump($c);

When defining a closure, the lexical variables (in the use clause) are created immediately, with the default value of null. The closure is executed later, and then, the values of the lexical variables are changed.

When creating an anonymous object, the constructor is immediately executed: hence, the argument with reference is created, then initialized with [] and immediately available.

See Also

PHP Features

Last updated: 11 October 2026

All Enum Cases

<?php

enum e: string {
    case A = 1;
    case B = 2;
    case C = 3;
}

print_r(e::cases());
print_r(array_column(e::cases(), 'value'));
/**
Array
(
    [0] => 1
    [1] => 2
    [2] => 3
)
*/
print_r(array_column(e::cases(), 'name'));
/**
Array
(
    [0] => A
    [1] => B
    [2] => C
)
*/
?>

One convenient aspect of enumerations is that they come with a full list of their cases. Call the ::cases() on any enumeration to get the full list of elements.

Then, convert this list into their scalar representation, if any, by calling array_column, with the public property value.

And it always works with the name property, to collect the names of the cases.

See Also

PHP Features

Last updated: 11 October 2026

Unicode Comments

By Aken Roberts

<?php

#️⃣️ This is a comment

echo 'No syntax errors here!';

?>

Besides //, #, /** */ and /* */, there is another way to make a comment in PHP: unicode #️⃣️ . This is possible as this unicode character is build on top of 3 codepoints: among those, the first one is #, which is an actual comment character. Then, PHP parses it as a single bytes, and interprets it as a comment: the rest of the line is omitted.

See Also

PHP Features

Last updated: 11 October 2026

Datetime Object, And Years

By Frederic Bouchery

<?php
for ($y = 1900; $y < 2100; $y++) {
  if (new DateTime($y)->format('Y') != $y) {
      echo $y, ' ', (new DateTime($y))->format('c'), "
";
      }
}

In this code, 2 years are given to Datetime, and handed back. Yet, they return the same value: 2024.

Under the hood, PHP attempts to decode the number. Instead of recognizing a year, it recognizes an hour. And, by default, it set the rest of the date to today. Later, when the date is formatted, the year becomes 2024.

One piece of advice is to format the date to PHP’s liking, or use the createFromFormat() method, which uses a provided format to decode the string.

See Also

PHP Features

Last updated: 11 October 2026

Escaping A Regex

<?php
$regex = 'test.';

// match
print preg_match('/^'.$regex.'$/i', 'tests') ? 'match' : 'nomatch';
print PHP_EOL;

// nomatch
print preg_match('/^'.preg_quote($regex).'$/i', 'tests') ? 'match' : 'nomatch';
print PHP_EOL;

// nomatch
print preg_match('/^\Q'.$regex.'\E$/i', 'tests') ? 'match' : 'nomatch';
print PHP_EOL;
?>

To use literal characters inside a regex, it is possible to use preg_quote(): it adds a backslash before every special character in the string. Don’t forget to use the second argument, which can hold any arbitrary delimiter.

Another option is to use the \Q and \E characters inside the regex: it turns all the special characters into a literal value.

See Also

PHP Features

Last updated: 11 October 2026

Every Anonymous Class Is Different

<?php

print get_class($c = new class() {});
//class@anonymousphp-wasm run script:3$0
$d = new class() {};

var_dump($c instanceof $d);
// false!

interface i {}

$e = new class() implements i {};
var_dump($e instanceof i);
// true

function foo() {
    return new class() {};
}
$x = foo();
var_dump(foo() instanceof $x);
// syntax error !
// var_dump($x instanceof foo());

?>

Anonymous classes don’t have an explicit name, yet PHP uses one internally. That name is build on top of the localisation of the class definition in the code.

This means that two anonymous class objects (sic), created with the same code, but at different places in the code, are considered of a different type by PHP.

On the other hand, when the same piece of code produces the anonymous class object, it is considered of the same class.

Note that the syntax foo() instanceof $x is valid, but $x instanceof foo() is not. The second operand of instanceof is a bit precious, and forbid many expressions: this is the case of a simple assignation.

See Also

PHP Features

Last updated: 11 October 2026

instanceof With Variables

<?php

$a = (object) ['b' => 1];
$b = new stdClass();

var_dump($a instanceof $b);
// true

?>

It is possible to compare the type of two objects with instanceof: PHP uses the class of the second operand, or the name of the class passed as a string.

Here, note that the cast operator (object) creates stdClass when applied to array.

See Also

PHP Features

Last updated: 11 October 2026

Undefined Variable

<?php

http://www.example.com/foo 
function foo($a) {
    // what does this do?
}

?>

The variable is undefined, and yet this is reported on the actual line where the variable is defined!

In fact, the assignation to $x appears to be the definition of the variable, because we, as human, encounter it first while reading.

Upon close inspection, the variable is actually used in the call to the function foo: PHP evaluates the arguments before calling the function, and finally, make the assignation. So the $x, used as second parameter in the foo call, is actually the first usage of the variable $x. Hence, the error message.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Compatible Signature

By Christian Lück

<?php

class CallbackWithIntersectionTypehintClass {
    
    #[PHP8] public function __invoke(Exception&Countable $e) : void /*
    // public function __invoke(bool $feelingDirty = true): void  // */
    { echo __METHOD__; }
}

(new CallbackWithIntersectionTypehintClass)();

This piece of code shows a signature that changes with the version of PHP.

Indeed, in PHP 7.x, the comment starts with # and goes to the end of the line. This also covers the /* so the multiline comment doesn’t start, and the __invoke signature is the second one.

In PHP 8.0, the attributes #[PHP8] ends with its closing square bracket, and the signature of __invoke starts. By the end of the line, a multi-line comments starts, and covers the whole second line.

In the end, this code stays unchanged between PHP versions, although it toggles different signatures.

See Also

PHP Features

Last updated: 11 October 2026

private Is Relaxed

<?php

// relaxed syntax is not possible with constants
// const PROTECTED = 'instanity';

class x {
    // relaxed syntax is possible with class constants
    private const string public = 'protected';
}

PHP has the relaxed syntax since early 7.0, which allows usage of PHP keywords for methods and class constant names.

private, protected and public were forgotten until PHP 8.2: this is now fixed, and this beauty is now possible.

As usual, use with caution.

See Also

PHP Features

Last updated: 11 October 2026

Class Implement No More

<?php

try {
    $x = initSomething();
    doSomething();        // may throw an exception
}

// no catch: exception keep their way

// finally is always executed
// with or without exception being caught
finally{
    $x->shutdown();
}

?>

Inheritance forces children classes to have only methods that are compatible with the parent’s methods.

The only exception is the constructor, which may have a totally different signature from generation to generation.

Interfaces apply the same constraints: their methods signature must be compatible with the host class. In this case, it also applies to the constructor.

So, in the case a parent class implements an interface with a constructor, and this class has a child class, the child may look compatible with the interface, and yet, be invalid at the constructor level.

Another good reason to avoid placing constructors in interfaces.

See Also

PHP Features

Last updated: 11 October 2026

Variadic Are Typed And Defaulted

<?php

function foo($a, ...$arg /* = [] */) {
    var_dump($arg);
    // array()
}

foo(1);

?>

When using the ... operator, the argument is automatically typed and defaulted: it is typed with array, and its default value is the empty array [].

There can be a type added to the argument, and, with it, element element of the argument will be of this type. Yet, the argument itself is actually array.

See Also

PHP Features

Last updated: 11 October 2026

array_column() To Hash

<?php

class X {
    function foo() {
        print __METHOD__.PHP_EOL;
        print __CLASS__.PHP_EOL;
        print __FUNCTION__.PHP_EOL;
    }
}

(new x)->foo();

    function foo() {
        print __METHOD__.PHP_EOL;
        print __CLASS__.PHP_EOL;
        print __FUNCTION__.PHP_EOL;
    }

foo();

array_column() is a very handy PHP native function, which extracts a index from a list of arrays, or a public property from a list of objects.

The third argument of array_column() is a key, which will be used to reference the value in the resulting array. It is very convenient to turn an array into a hash.

Note that duplicate keys will be overwritten.

Also, there is no reciprocal function for this: turn a hash into an array of arrays or objects.

See Also

PHP Features

Last updated: 11 October 2026

\array Was Customizable

<?php

$anonymous = new class () {};

class_alias($anonymous::class, \array::class);

function foo(\array $object) : \array {
    var_dump($object);

    return $object;
}

foo($anonymous);

/*
object(class@anonymous)#1 (0) {
}
*/
?>

This code gathers several PHP hacks together.

First, the naming of an anonymous class, and this time, it is renamed as \array.

array would not work, but the absolute naming make it pass.

Then, that \array is used as type, and it works.

Now, there is something that looks like an array, but is actually a random object.

This is a bug, since other PHP native types, like int disallow usage of their absolute name.

This was reported as a bug, and fixed in PHP 8.4. Thanks!.

See Also

PHP Features

Last updated: 11 October 2026

array Is Not An Array

<?php

function foo(\array $a) {}

foo([]);

// Fatal error: Uncaught TypeError: foo(): 
// Argument #1 ($a) must be of type array, array given

?>

PHP native types, such as the scalar int cannot be specified with a leading \. Such code get hit with a Type declaration \int must be unqualified.

This is the case for all of the PHP native types, except for array, and iterable.

That error message is really hilarious.

See Also

PHP Features

Last updated: 11 October 2026

array_map_assoc() With Keys

<?php

$hash = ['a' => 1, 'b' => 2];


print_r(array_map(function($v, $k) { return "$k: $v";},
                  $hash,
                  array_keys($hash)
                ));
                
?>

array_map() only provides the value of the array, not the key. To access the key, one must use the extra argument, and array_keys().

Beware that the order of the arguments is now value first, key second, not the usual $key => $value.

See Also

PHP Features

Last updated: 11 October 2026

array_map() Goes Full Throttle

<?php

$array = array_map(function($a, $b) { return 1; },
                    range(0, 10),
                    array_fill(0, 1, 2),
                    array_fill(0, 100, 3), // not even used 
);

print count($array); // 100

?>

array_map() applies a closure, its first argument, to every elements of an array, its second argument.

When the closure needs more parameters, they may be passed as third (and n-th) parameter, as an array. When there are less extra parameters than the closure needs, PHP emits a ArgumentCountError exception.

When there are too many extra parameters, PHP just omits them, silently.

A call to array_map() makes sense when all the provided arrays have the same number of elements.

When the numbers of elements differ, PHP runs the function for the largest array possible. It provides null, anytime an element is missing. This is also the case when the array is superfluous: here, the last array has 100 elements, and is not used in the closure, but forces the results to have 100 entries.

See Also

PHP Features

Last updated: 11 October 2026

Typed Array

By • Kartoffeljunge •

<?php

class User {}

function handle(User ...$userList) {
    print count($userList)." users
";
    /** ... */
}

$bunchOfUsers = [
    new User(),
    new User(),
    ];
    
echo handle(...$bunchOfUsers);

$bunchOfUsers[] = 1;
echo handle(...$bunchOfUsers); // error!$bunchOfUsers

It is possible to set the type of all elements in a variadic: this is equivalent of passing an argument of type array<User>. That way, all the elements in the array must have the expected type. Use union-type to make have several distinct types.

See Also

PHP Features

Last updated: 11 October 2026

Initialize Readonly On Child

<?php

class x {
    public readonly int $property;
}

class y extends x {
    function __construct() {
        $this->property = 5;
    }
}

$x = new x;
echo $x->property;

Readonly properties could only be initialized in the same class as their definition. That holds true, whatever the visibility of the property: private, protected, private.

In PHP 8.4, it is now possible to initialize the readonly properties from a child class, if the visibility allows. And the property is assigned only once.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Silent Optimisation Of Variables

<?php


$a;
// crii crii

3 || $b;
// ...

echo $c;
// warning: undefined variable $c;

?>

See PHP optimisation in action: undefined variables are only reported when they are used (sic).

The first one is omitted : there are no operation with it.

The second one is skipped: no need to execute 2nd term when the first one is already true.

The third one is reporting a warning, as expected.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Modify String With Array Syntax

<?php

$a = '1';
$a[1] = 2;
$a[10] = 3;
$a[-9] = 4;

print $a;
// 124       3

PHP strings allows access to individual characters using the array syntax: $string[$index].

It is also possible to modify the string with the same syntax: the index have to be integers.

The positive integers are offsets, starting at zero. Any missing characters between the end of the string and the requested index is set to space ' '. Negative index are also valid, starting from the end of the string.

Here, $a[1] is just after the end of the string, $a[10] is well beyond the end of the string, and $a[-9] is almost back to the beginning of the string.

See Also

PHP Features

Last updated: 11 October 2026

No Force Object To String

<?php

class A {
    function __toString() {
        return 'A';
    }
}

$a = new A();

$b = [$a => $a];

print_r($b);

PHP automatically converts numeric strings to integers when used as array indices, so ‘123’ becomes 123 (the integer). However, PHP doesn’t perform similar conversions for objects, when they are used as array keys. Instead, it immediately throws a fatal Illegal offset type error because objects are not automatically converted to valid array keys.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

__invoke() Method And Properties

<?php

class x {
    private X $y;
    
    function __invoke() { echo __METHOD__;}
    
    function foo() {
        $this->y = new X;
        $this->y();
        // better this way
        ($this->y)();
    }
}

(new X)->foo();

In the code here, the __invoke() makes the class X invokable : the object may be used as a function name and it will call the magic method __invoke.

Then, in the same class, there is a property $y, with the type of X. That property is, hence, holding an invokable object. So, may be, it is possible to call this property as a method, and invoke it?

Well, no. This call will yield an error, undefined method y. PHP doesn’t use the __invoke() here, as it would conflict with an existing y method. Also, that would make a case-sensitive method, as the property $Y doesn’t exists.

To invoke the object in the property y, one has to put parenthesis around the object, so that PHP can extract the object and then, invoke it safely.

See Also

PHP Features

Last updated: 11 October 2026

Constructor Needed

<?php


const A = 'b::c';

class B {
    static function C() { echo __METHOD__; }
}

//valid call to \B::C()
constant('A')();

// class A not found
//new A;

// Call to undefined function A()
A();

// parse error, this is not supported
//{A}();

By default, classes don’t need a constructor, and it may be omitted. It is also true in the case of child classes: PHP look for a local constructor, then a parent constructor, and it is valid to have none of them.

On the other hand, if any of the child makes an explicit call to parent::__construct, then, there must be at least an explicit constructor in the family. Or, PHP generates Cannot call constructor error message.

As a rule of thumb, it might be good to always include the constructor definition, to avoid adding it later, down the road.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Self Referencing Constants

<?php

class x {
    const A = x::A + 1;
    
    const B = x::C + 1;
    const C = x::B + 1;
    
}

It is not possible to define a recursive PHP class constant, although it is possible to write it. A self referencing constant may be with itself, or it may use a complex right of class definitions before failing.

There is a dedicated PHP error message for this situation: ‘Cannot declare self-referencing constant’.

One fun aspect is that the is not detected at compilation time, but at execution time, when the constant is actually used. The problem may lie dormant for a long time before revealing itself.

The error is also moved by other reported when several constants are compounded to build one, in a cyclic fashion.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Deanonymize Class

<?php

$x = new class() {
    function __construct() {
        echo __METHOD__.PHP_EOL;
    }
}

class_alias($x::class, B::class);

new B();

Anonymize class are classes without a name. That way, it provides directly an object, but doesn’t litter the namespace with an extra class name.

Later, if the code needs a second object of the same ‘class’, it has to go through the same code.

Or, it is possible to get access to the ‘hidden’ definition of the class within PHP, by simply creating a class alias to this class. Then, it can be used anywhere else.

The sane alternative might be to simply give a name to the class, or make a direct clone of the object.

And as usual, if that class is not under the code’s own control (say, external lib), it might be useful though hackish.

See Also

PHP Features

Last updated: 11 October 2026

Noscream On Ellipsis

<?php

function foo($a, $b) {}

foo(...$array); // Normal call

foo(...@$array); // Hide Undefined variable, but trips on null!

foo(@...$array); // @ doesn't work on operators, so not on ...

foo(@...@$array); // same as above, but it looks cool!

Noscream operator @ hides errors locally, in an expression. Variadic ... spreads the elements of an array.

What happens when the two are mixed?

@ works on a variable, but it will yield a NULL when the variable is undefined, which is a fatal error when used with ellipsis. So, this is legit, but also a dead end.

@ doesn’t work on operators, so it is not possible to put it before the ellipsis, without a compilation error.

Finally, the syntax @...@ would be definitely cool. Crazy, but cool. Don’t use it.

See Also

PHP Features

Last updated: 11 October 2026

Incompatible Promoted Readonly

<?php

class x {
    function __construct(protected readonly int $x) {
    }
}

class y extends x {
    function __construct(protected readonly int $x) {
        parent::__construct($x);
    }
}

new y(1);
// Fatal error: Uncaught Error: Cannot modify readonly property y::$x

class z extends x {
    function __construct(int $x) {
        parent::__construct($x);
    }
}

new z(1);
// OK

?>

Promoted properties allows the creation of properties directly from the constructor signature.

readonly properties are only written once, and cannot be written again.

When a promoted property is public or protected, it cannot be used in two constructor’s signature, as the first instance is immediately written, and cannot be changed again.

This doesn’t happen with private, which keeps the properties distinct in the class and its parent. This still may produce confusion.

It is then possible to only use one promoted property definition, and skip that parameter in the other constructor; or make the second a simple parameter, rather than a promoted property.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Where Is The Semicolon?

<?php

#[A (new B(4), 2, 3)]

// Parse error: syntax error, unexpected token ";" in /in/vNMQk on line 5
?>

This code is an attribute. A attribute always expects to be followed by a structure that it will characterize. Here, without anything else to parse, the parser displays a mysterious error about a semi colon, while none is in sight.

In fact, the closing PHP tag has a built-in semi colon, to end any expression that was still open.

This is similar to another tip, that relies on echo.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Self As Attribute

By Jan Nedbal

<?php

#[Attribute]
class MyAttribute {
    #[self]
    public function __construct() {}
}

Self, parent and static cannot be used as attributes. They are not recognized, and PHP will search for a class called \self instead, in vain.

On the other hand, it is possible to use an attribute onto its own methods, simply by using its regular name.

It is possible to use these names as attributes, as long as they are not instantiated.

It is also possible to use aliases with the attributes: they will automatically resolve to the original class, not the aliased name.

See Also

PHP Features

Last updated: 11 October 2026

Reading Micro-seconds

<?php

echo (new Datetime())->format('u');
print PHP_EOL;

echo microtime(true);
print PHP_EOL;

echo hrtime(true);
print PHP_EOL;

echo date('u');
print PHP_EOL;

There are different ways to access micro seconds in PHP : use the Datetime format of u, which works with Datetime. Or call the microtime() or hrtime() functions, with the true parameter.

But don’t use date(): although it has the same u formatting letter, it never provides it.

See Also

PHP Features

Last updated: 11 October 2026

Promoted Properties Compatibility

<?php

interface i {
    // no promoted property
    function __construct(int $a);
}

class x implements i {
    // promoted property
    function __construct(private int $a) {}
}

class y extends x {
    // valid extension of a promoted property
    function __construct(int $a) {}
}

?>

Promoted properties are defined in a constructor signature. They cannot be defined in an abstract method, so they are not allowed in an abstract constructor: this is true in an abstract class and in an interface.

On the other hand, it is allowed to extend promoted properties without the promotion, and vice versa.

See Also

PHP Features

Last updated: 11 October 2026

Enum Are Not Linted

<?php

enum E {
   case A = 'z';
   case B = 'z';
}

It is possible to give two identical values to different enumeration cases, but not to execute it.

In PHP 8.1, duplicate case values generated a compilation error.

Since PHP 8.2, it is now waiting for the execution stage and the first usage of the enumeration, to raise a fatal error.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Sorting With Minus

<?php

$array = ['a' => 1, 'b' => 2, 0, 'f' => 4, '-f' => 5];
ksort($array);

/**
Array
(
    [-f] => 5
    [0] => 0
    [a] => 1
    [b] => 2
    [f] => 4
)
*/

?>

Did you know that sorting f, 0 and -f actually ends up with -f first, then 0, then f. And this works with any string, including strings with duplicate initial minus signs.

This is the SORT_REGULAR, with which PHP compares normally different pieces of data. Any string whose first character has an ASCII code lower than 0 will be sorted first (in PHP 8.2+) : this means characters up to /. Then any starting character beyond 9, aka colon : and beyond, are sorted after the 0 (and any digit).

That normal sort is used in ksort() and krsort() since PHP 8.2. This means that these functions may behave differently, when the keys string start with a - since.

This sort will sort string starting with - first: given the ASCII table, it also will sort strings starting with + before the -. Use this feature with caution.

See Also

PHP Features

Last updated: 11 October 2026

Override With Interface

<?php

interface i {
    function foo();
}

class X implements i {
    #[Override]
    function foo() {}
}

var_dump(new x);

The override attribute checks that a method is actually overriding a parent definition of the same method: this implies that a class must have a parent to use the Override attribute.

There is a special case, which is a class without a parent, though implementing an interface. The Override attribute may be used on any of the interface method, though the class has no parent.

See Also

PHP Features

Last updated: 11 October 2026

Send An Argument To Generator

By Rahul Chavan

<?php

function checkEvenOrOdd() {
    $value = yield;
    
    yield ($value % 2 === 0) ? "Even: $value" : "Odd: $value";
}

$evenOddChecker = checkEvenOrOdd();

echo $evenOddChecker->send(7). PHP_EOL;

When calling the method send() on a PHP generator, the result of the first yield keyword is the argument value. Then, it can be used to yield a specific result.

Upon usage on a foreach(), that generator will generate 2 values.

Generator::send() only accepts one argument.

See Also

PHP Features

Last updated: 11 October 2026

PHP Integer Format

<?php

$number = 0x12A;

$string = '0x12A';

// direct decoding
echo hexdec($string);

// the reverse way
$newString = '0x'.dechex($number);

?>

PHP has several convenient integer format to write them in a proper way: 0x12F, 0b110101, 0o12367. This works well as part of the code

When such formats are provided as a string, it may be processed with the ad hoc native functions such as hexdec(), bindec() or octdec() : they convert a number between bases, and they also accept the native PHP formats.

For the reverse operation, the reverse functions are also useful, with an extra header for the format.

See Also

PHP Features

Last updated: 11 October 2026

Compatible Only Interface

<?php

class A {}
class B {}

interface i { 
    function foo(A $a) : A ;
}

class C implements i {
    function foo(A|B $a) : A&B {}
}

Interfaces are contracts that defines methods signatures, which the derived classes have to implements.

One lesser known fact is that the implementation only has to be compatible with the interface, there is no need to be identical. It is the same behavior than with class inheritance.

This explains that the following code is valid, both with argument contra-variance and return type variance.

Note that the names of the parameters are completely free to be changed at will, yet it is not recommended.

See Also

PHP Features

Last updated: 11 October 2026

No Cast In Constant Expressions

<?php

class x {
    const string A = '42';
    const int B = +self::A;
    // This, below, is a compile error
    const int C = (int) self::A;
    
}

Constant expressions do not accept the cast operator.

Constant expressions are expressions defining constants and default values. They allow a subset of PHP features, as they are available before the execution phase. For example, they accept operators, such as addition and concatenation. Yet, they do not accept the cast operators, even though they might be emulated in another way. Here, the + operator creates the integer, from another string.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Lots Of Silly Arguments

By Greg Korba

<?php

class Foo {
    public function __invoke(): self {
        return new self();
    }
    
    public function bar(): self {
        return $this();
    }
}

var_dump(new Foo()()()()->bar()()()()());

PHP 8.4 drops the requirement of parenthesis around the new operator: it is possible to directly call a method on it.

Then, it is also possible to use an object as a method, by creating the __invoke() magic method. In this case, it returns a new instance of the class.

Finally, it is possible to call a function whose name is the result of a previous call.

The final result is quite impressive.

See Also

PHP Features

Last updated: 11 October 2026

Child Of Attribute Is Not An Attribute

<?php

#[Attribute]
class x {}

class y extends x {}

$ref = new \ReflectionFunction(#[x, y] function () { });

try {
    $ref->getAttributes()[0]->newInstance();
    $ref->getAttributes()[1]->newInstance();
} catch (\Error $e) {
    var_dump('Error : ', $e->getMessage());
}

?>

A class attribute must be marked with an attribute called Attribute. It is compulsory to use Reflection and obtain an instance of the attribute.

That attribute is only for the class itself, and it doesn’t propagate automatically to the child classes.

On the other hand, the Attribute class cannot be extended: it is not possible to create a child that may carry some of the Attribute properties.

See Also

PHP Features

Last updated: 11 October 2026

Dynamic Class Constant

<?php

// echo A::A;
// Undefined constant "B"

define('B', 'yes');

class A {
    public const A = B;
}

echo A::A;
// yes

?>

It is not allowed to create dynamically a class constant. The only way is to create a global constant, using the define() function.

So, when there is a need for a dynamically created constant, the trick is to assign the class constant a global constant’s value, and then, create the global constant.

Of course, it is not possible to use that class constant until the global constant has been actually created.

See Also

PHP Features

Last updated: 11 October 2026

The Function Method

By Brent Roose

<?php

class x {
    function function() {
        echo __METHOD__;
    }
}

(new x)->function();
// x::function

?>

It is possible to name a method ‘function’. This is part of the relaxed keywords of PHP, which allows PHP keywords to be used as a method name.

Of course, it is also possible to create a function called method.

See Also

PHP Features

Last updated: 11 October 2026

Match() With Multiple Values

By Pascal Baljet

<?php

match([$clause, $this->isNested()]) {
    [Clause::IsSet, true]  => $query->has($this->relationship),
    [Clause::IsNotSet, true]  => $query->doesntHave($this->relationship),
    [Clause::IsSet, false]  => $query->whereNotNull($this->column),
    [Clause::IsNotSet, false]  => $query->whereNull($this->column),
};

This is an example of using match(), to match against two values at the same time. The trick is to collect the two, or more, values in an array, and then return an array with the same number of elements in the same order.

It would also be possible to make a match(true), and use boolean expressions for each arm of the match(). The first one that match is used.

See Also

PHP Features

Last updated: 11 October 2026

Interfaces Constants Conflicts

<?php

interface A {
    const FOO = 10;
}

interface b {
    const FOO = 10;
}

class test implements A, B {}

PHP reports duplicate constant definitions in different interfaces.

This is very unusual occurrence, reported at linting time. It may also lead to quite a set back when one has to untangle these definitions.

So, shall we always give unique names across interfaces? Or just avoid multiple interfaces with constants on the same class?

There is no ‘use’ expression to solve the conflict.

Also, multiple same methods in different interface are OK.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Foreach() Skips Uninitialized Properties

<?php

class z {
    public readonly int $p;
    public readonly int $q;

    public function __construct()
    {
        $this->p = 1;
    }
}

$x = new z;

// foreach skips undefined properties, readonly or not
foreach($x as $k => $v) {
    print $k . " " . $v . PHP_EOL;
}

echo $x->q;
?>

Foreach() reads naturally all public properties in an object. Protected and private are omitted, unless in the right context.

Foreach() also skips silently uninitialized properties: this prevents the generation of NULL values, but also, skips all readonly properties: any direct hit on such property would otherwise generate a Fatal error.

See Also

PHP Features

Last updated: 11 October 2026

Casting And Power

<?php

$a = 2.5;
$b = 2.5;

print (int) $a * (int) $b;  // 4

print PHP_EOL;

print (int) $a ** (int) $b; // 6

?>

When you know that 2*2 = 4, and 2**2 = 4 (too), and 2.5 ** 2.5 = 9.8821176880262, you have to ask yourself where is this 6 coming from.

The answer is in the precedence. Casting has higher precedence than multiplication, so cast is done first (2.5 -> 2) then multiplication. This gives the first 4.

Then, power, **, as higher precedence than casting, so the second expression gives first 2.5 ** 2, which is 6.25, and then, cast it again as an integer, hence 6.

It’s a good thing that power is so little used.

See Also

PHP Features

Last updated: 11 October 2026

Attribute Without Class

<?php

#[Attribute]
class y {
    function foo() { return __METHOD__; }
}

#[X, Y]
function f() { }

$ref = new \ReflectionFunction('\f');

try {
    if ($ref->getAttributes()[0]->getName() === 'X') {
        print 'Look mom! No hands' . PHP_EOL;
    }

    if ($ref->getAttributes()[1]->getName() === 'Y') {
        print $ref->getAttributes()[1]->newInstance()->foo();
    }
} catch (\Error $e) {
    # other errors
    var_dump($e->getMessage());
}

?>

PHP 8.0 has an attribute feature, where extra options may be attached to various structures. The option is defined as a class, and is accessible via the Reflection API, and the getAttribute() method.

The attribute class is not necessary, per se. As long at the attribute is not instantiated, it is possible to rely only on the name of the attribute to handle specific behaviors.

See Also

PHP Features

Last updated: 11 October 2026

Class Is A Valid Type

<?php

class foo {
    
    public function barbar(\class $a) {}
    // alright, not hing to see
    
    public function bar (\self $a) {}
    //Fatal error: '\self' is an invalid class name
}
?>

PHP allows to use \class as a class name in a type. This is also the case for interfaces, enums, traits… and it is also valid for some others keywords.

On the other hand, a good number of PHP keywords are forbidden, and yield an error : \self is an invalid class name.

Of course, once this is typed like that, it is not possible to satisfy the type specification, and such code is actually not usable.

See Also

PHP Features

Last updated: 11 October 2026

Float Precision

<?php

echo 1/7;
// 0.14285714285714

ini_set('precision', 2);
echo 1/7;
// 0.14

ini_set('precision', 200);
echo 1/7;
// 0.142857142857142849212692681248881854116916656494140625 (php)
// 0.142857142857142857142857142857142857142857142857142857 (real)
// garbage starts at 18 and stops at 54

?>

PHP has a precision directive that controls the number of decimals displayed by float values. This is used with echo (and co), but also with cast to string.

It default to 14, and peaks at 18, and gives up at 54. Use gmp or bcmath for large numbers.

When changing it, don’t forget to reset it to default, unless you like to puzzle people.

See Also

PHP Features

Last updated: 11 October 2026

Store Float As Index

<?php

$array  = [1 => 0,
           // Implicit conversion from float 1.2 to int loses precision
           1.2 => 1,
           // as string, it fits!
           '1.1' => 2,
          ];

With PHP, floats cannot be used directly as array indexes because array keys must be either integers or strings. If you attempt to use a float as an index, PHP will automatically cast it to an integer, potentially causing unexpected behavior. However, you can explicitly cast the float to a string to preserve its precision as an index. Later, due to PHP’s type juggling, you can still perform arithmetic with it seamlessly.

See Also

PHP Features

Last updated: 11 October 2026

Case Insensitive Cast

<?php

$string = '123';
$integer = (INT) $string;

PHP’s cast operators are case-insensitive, meaning variations like (int), (INT), and (iNt) all work the same way. However, the common convention is to use all lowercase, such as (int). Using other case styles is technically valid but extremely rare and generally discouraged for consistency and readability in code. Stick to lowercase for better code standards.

See Also

PHP Features

Last updated: 11 October 2026

$this $this

<?php

class x {
    private $p = 'Yes!';
    
    function foo() {
        // valid code
        echo $this->$this;
    }
    
    function __toString() {
        return 'p';
    }
}

(new x)->foo();

$this is the pseudo-variable that represents the current object. It is also a variable, like others, and may be used to describe a dynamic property.

Since a property must be described as a string, it is possible to use the magic method __toString() to convert the object into a string, and map an existing property.

See Also

PHP Features

Last updated: 11 October 2026

No Returntype But Still, Only String

By Frederic Bouchery

<?php

declare(strict_types=1);

class x {
    function __toString() {
        return 1;
    }
}

echo new x;

Without a return type (or mixed), a method may return any type of data. This is not the case for the magic method __toString, as it really should return a string. So, even without a return type, it complains about the type.

That trick requires strict_types though.

See Also

PHP Features

Last updated: 11 October 2026

No Unknown For array_merge()

<?php

function foo($x, ...$array) {
    print_r($x);
    print_r($array);
}

$a = ['w' => 'w', 'x' => 'x'];

foo(...$a);

print_r(array_merge(...$a));
// array_merge() does not accept unknown named parameters

A PHP variadic argument collects all the unused named parameters, along with their key. That way, it is possible to handle them with their name inside the method.

On the other hand, array_merge(), and some cousins, refuse them, and emits a Fatal error. There, it is important to use array_values() to avoid named parameters.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

No Returntype Enforced

<?php

function foo() : Generator {
    yield 1;

    return 'not a generator';
}

foreach($g = foo() as $b) {
    print $b;  // print 1
}

echo $g->getReturn(); // print 'not a generator'

?>

A return type on a method means that the method must return something of that type. This is true, unless for generators. Such methods contains yield or yield from, and must use the Generator returntype. Then, the actual return type is not checked, at all.

See Also

PHP Features

Last updated: 11 October 2026

As As As

<?php


trait t {
    function as() { echo 'as'; }
}

class x {
    use t { as as as ;}
}

(new x)->as();
// prints as
  1. it is possible to call a method ‘as’, because relaxed keywords PHP methods

  2. it is possible to import a method from a trait, and alias it to itself.

It is possible to write ’as as as ’ in PHP and make sense.

See Also

PHP Features

Last updated: 11 October 2026

No DNF For Catch

<?php

try {} catch (A&B $e) {}

try {} catch (A|(B&C) $e) {}

Catch clauses accept multiple types of exceptions, since PHP 7.1: they are listed with the ‘|’ separator.

This looks like the union type of PHP 8.0, and actually acts like it.

Yet, this is not a normal type, because the intersectional and the DNF format are not supported at parsing time. Thus, the two expressions below generate an error.

See Also

PHP Features

Last updated: 11 October 2026

new On Object

<?php

class X {}

$s = '\x'; new $s;       // a x object
$x = new x(); new $x();  // another x object

$s = x::class;
$a = new $s;            // yet another one

// syntax error, unexpected token "class",
// expecting variable or "$s"
$a = new x::class;

?>

It is possible to use new on an object, to get a new object of the same class. This might be useful with anonymous classes, which cannot be created with their name.

When using the parenthesis after the variable, the __invoke() magic method won’t be called, as those parenthesis are used to call the constructor. To get that result, it must be called outside the new command or within parenthesis, as new ($a()).

Under the hood, PHP collects the class name from the object to produce the new one. It is akin to using the class name in a string.

Lastly, the full class name is available in the x::class syntax, but PHP won’t recognize new x::class, and even emit a Fatal error: it recognizes new (x::class).

See Also

PHP Features

Last updated: 11 October 2026

Unfinished Class Doesn’t Compile

By Jan Nedbal

<?php

// this is FATAL ERROR

class A extends B {}

class B extends C {}

class C {}

PHP is not able to use a class before it is defined. Yet, when the second class is simple enough to be entirely defined, it would. When the class is not completely defined, aka it has the extends, implements, or use (for trait) keywords, PHP postpone the finalization of the class to execution phase, and consider it as non-defined.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Yield, Not Return By Reference

<?php

function &gen() {
    yield 'foo';
    
    return 'Literal';
}

$gen = gen();
var_dump($gen->current());


foreach($gen as $a) {
    print $a;
}

print $gen->getReturn();

?>

When a method returns a reference, there is a & before its name, in the signature. This means that the function must return a variable, or a property, and not a literal value.

When the same & is added on a generator, this now means that the yielded values are by reference. On the other hand, the returned value mat be a literal value, without generating a warning.

See Also

PHP Features

Last updated: 11 October 2026

class_exists() With Enums

<?php


enum MyEnum {}

class_exists(MyEnum::class); // true or false?

In PHP, there are dedicated functions to check the availability of structures:

  • class_exists()

  • trait_exists()

  • interface_exists()

  • enum_exists()

What would the code here produce?

This is displaying true. Enumerations are considered as classes, even as they have specific limitations and behavior, compared to normal classes.

See Also

PHP Features

Last updated: 11 October 2026

No Semicolon In Sight

<?php

echo 'a',

?>
//Parse error: syntax error, unexpected token ";" in /in/6WEF0 on line 5

This PHP code doesn’t compile: the echo must be complete to be closed with a PHP tag. Either the comma should be a semicolon, or an extra argument should be present.

In any case, the linting error message reports an unexpected semicolon, which is not visible in the code. Indeed, it is hidden in the PHP closing tag.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

glob() With Hidden Files

<?php

$path = __DIR__;

// list all files, with and without leading . 
$files = glob($path . '/{,.}[!.,!..]*', GLOB_BRACE);

// list all files, except . and ..
$files = glob($path . '/{,.}*', GLOB_BRACE);

By default, the glob() function lists only files that don’t start with a dot .. This means that the hidden files, on Linux, are not listed.

With the GLOB_BRACE option, it is possible to list all files, and even filter out the ever present ‘.’ and ‘..’.

See Also

PHP Features

Last updated: 11 October 2026

A Function Called file_append_contents()

<?php

// appends the text to the end of the end of the file
file_put_contents($file, $text, FILE_APPEND);

// appends the text to the end of the end of the file
$fp = fwrite($file, 'a');
fwrite($fp, $text);

file_put_contents() can append data to the end of the file, with the FILE_APPEND option.

file_put_contents() keeps the file opened as little as possible, and the operation is atomic. It is better then overwriting the whole file; and it is often better than fopen(), which keeps the file open until the end of the execution.

See Also

PHP Features

Last updated: 11 October 2026

array_reduce() To Filter And Alter

By Rahul Chavan

<?php
$users = [
    [
        "id" => 1,
        "name" => "rahul",
        "premium" => true,
        "purchases" => ["apple", "banana", "carrot"],
        "price" => 22,
        ],
    [        
        "id" => 2,
        "name" => "abhi",
        "premium" => false,
        "purchases" => ["apple", "banana", "carrot"],
        "price" => 22,
        ],
        [        
        "id" => 3,
        "name" => "kavita",
        "premium" => true,
        "purchases" => ["apple", "banana", "carrot", "orange"],
        "price" => 30,
        ],
    ];

$premiumUsers = array_reduce(
    $users,
    function ($carry, $user) {
        if ($user['premium']) {
            $carry[] = [
                "name" => $user["name"],
                "purchases" => $user["purchases"],
                "price" => $user["price"] - 2
                ];
        }
        return $carry;
    },
    []
);

print_r($premiumUsers);

/**
 * Array
(
    [0] => Array
        (
            [name] => rahul
            [purchases] => Array
                (
                    [0] => apple
                    [1] => banana
                    [2] => carrot
                )

            [price] => 20
        )

    [1] => Array
        (
            [name] => kavita
            [purchases] => Array
                (
                    [0] => apple
                    [1] => banana
                    [2] => carrot
                    [3] => orange
                )

            [price] => 28
        )

)
 */

Using array_reduce() to create custom data structures goes beyond simple filtering.

array_reduce() might save a second loop. Although, it might also be quite slow, in particular for large arrays (10 millions+).

array_reduce() doesn’t provide keys when processing them.

See Also

PHP Features

Last updated: 11 October 2026

$this Is Not Of The Current Class

<?php

class a {
    function foo() {
        echo __CLASS__;          // a
        print get_class($this);  // a or b
    }
}

class b extends a {}

(new b)->foo();

$this is a pseudo-variable, that represents the current object of the calling object (dixit the manual): it is often portrayed as representing an object of the current class. This is commonly the case.

Yet, it is possible of this object to be an instance of another class. This happens with a child of the class, in particular when that child hasn’t defined a method that is present in the parent. The fallback mechanism will use the parent class.

In the end, this is still compatible.

See Also

PHP Features

Last updated: 11 October 2026

Class Constants With Different Types

<?php

class A {
    protected const int|string|X C = 1;
}

class B extends A {
    protected const int|string|Y C = 'a';
}

With PHP, it is possible to have a class constant with different values, between a parent class, and its child class.

Quiz of the day : can you make a class constant with different types? Optionally different values.

Works on PHP 8.3. Application unknown.

The trick is to use union types, which allows adding new valid types without ascendant compatibility.

See Also

PHP Features

Last updated: 11 October 2026

No Change To $this

By Ondrej Mirtes

<?php

function foo(&$var) {
    $var = 2;
}

$a = 1;
foo($a);
var_dump($a); // 2

class A {
    public function do() {
        foo($this);
        var_dump($this); // still the same object
    }

    public function doo() {
        $a = new self();
        foo($a);
        var_dump($a); // 2
    }
}

Variable $this is passed and reassigned by reference is a noop, as opposed to all other variables. I’d expect an error to be honest.

See Also

PHP Features

Last updated: 11 October 2026

file_put_contents() With Array Argument

By Gina Peter Banyard

<?php

file_put_contents('/path/to/file.txt', [1,2,3]);

print file_get_contents('/path/to/file.txt');
// print 123

The second argument of file_put_contents() is typed as mixed. It may be a string, which is the most common case. It may be a stream, (aka, a resource), which pours a file into a file.

It also may be an array. PHP simply writes the elements of the array in the file, like an implode('', $array).

See Also

PHP Features

Last updated: 11 October 2026

Get The Generator

<?php

function foo() {
    yield 1;
    return "a";
}

function goo($a) {
    print $a . PHP_EOL;
}

goo(...($generator = foo()));

print $generator->getReturn();
// la

foreach (($generator = foo()) as $b) {
    print $b . PHP_EOL;
}
print $generator->getReturn();
// la

?>

It is possible to catch a generator when it is called, by putting it in a variable. Once the generator has been used, it is possible to call a method such as getReturn on it.

This works with ... and foreach.

See Also

PHP Features

Last updated: 11 October 2026

Redefine Native Functions

<?php

echo strtolower('ABC').PHP_EOL;

function b($a) {
    print "b: $a
";
}

use function b as strtolower;

echo strtolower('ABC').PHP_EOL;

It is possible to redefine PHP functions or constants, or even any defined of such, by using the use command.

This feature is interesting for testing purposes: it allows the replacement of a defined function with an alternative.

It is not possible to do this twice in the same file, nor to redefine classes.

See Also

PHP Features

Last updated: 11 October 2026

A Static Method Cannot Call A Non Static Method

<?php

class X {
    static function foo() {
        self::goo();
    }
    
    function goo() {    }
}

(new x)->foo();

It is known that a non-static method can call a static method. It is less known that a static method cannot call a non-static method, even if the static syntax is possible.

A non-static method needs a value for $this, which a static call doesn’t provide.

See Also

PHP Features

Last updated: 11 October 2026

Declared Before Or After

<?php

// defined before the parent class : no error
class y extends x {}

abstract class x {
    abstract function foo();
}

// defined after the parent class : error
class y2 extends x {}

?>

PHP takes the opportunity to validate everything it can when it is handy, and leave the rest to execution time. In the code below, the first class has no error reported, because it is linted before the parent class. The last class displays an error because it appears after the definition of the parent, and PHP can lazily check it.

The validation of the first class will be more thorough at execution time, and yield the error then.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Cast Is So Strong

<?php

const int = 3;
var_dump(int);

?>

The code below yields a Fatal Error, at linting time. PHP identifies too early the cast operator (int), and fail to recognize the function call to var_dump().

It is possible to create a constant called int, but it is not possible to put it between parenthesis, including with spaces. PHP always confuse it with the cast operator.

Later, bool, float, string, array, object all fail to parse for the same reasons. It is also the case for unset, although this operator is not supported anymore. It is also the case for boolean, and integer, although PHP complains about the short name of the operator.

This issue has been with PHP since PHP 4.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Roman Loops

By Stephen Rees-Carter

<?php

for($i = 0; $i < 10; ++$i) {
    for($ii = 0; $ii < 10; ++$ii) {
        for($iii = 0; $iii < 10; ++$iii) {
            for($iv = 0; $iv < 10; ++$iv) {
                for($v = 0; $v < 10; ++$v) {
                }
            }
        }
    }
}

If the classic variable with a for() loop is $i, then nested loop should use $ii, then $iii, $iv, $v.

See Also

PHP Features

Last updated: 11 October 2026

Finally Catches Exception

<?php

function foo() {
    try {
        throw new Exception('yeah');
    } finally {
        return 1;
    }
}

print foo();
// print 1, no exception

?>

In this code, finally is executed after the throw. Since it contains a return, the function is finalized before the throw is executed. The exception is then lost, and the scripts displays 1. This is why it is recommended to avoid return in a finally clause.

See Also

PHP Features

Last updated: 11 October 2026

Always Compatible Variadic

By Sergii Shymko

<?php

interface i {
    function foo($a, $b = 2);
}

class x implements i {
    // This is compatible with the interface definition
    function foo(...$args) {}
}

?>

A variadic argument is a valid and compatible signature, for methods that overwrite other methods.

Method compatibility usually require the arguments to be the same, but with a variadic argument and also some of the arguments from the parent method, the signature is still considered compatible by PHP.

See Also

PHP Features

Last updated: 11 October 2026

Yield, Not Return

<?php

function foo() {
    yield 1;
    yield 2;
    yield 3;
    
    return ' 4';
}

function goo($a, $b, $c) {
    print "$a $b $c";
}
print PHP_EOL;

goo(...foo());

goo(...($g = foo()));
echo $g->getReturn();

?>

This code displays 1 2 3. This is because the ... operator acts as a foreach and runs the generator. It then spreads the values as arguments for the function call, and, in this case, it matches the needed arguments.

Note that named parameters are also supported, with the good PHP version.

The returned value of the generator is lost, in this case. If you need to access the returned values, you must get the generator, and call the getReturn() method.

See Also

PHP Features

Last updated: 11 October 2026

try, catch, finally Are All Optional

<?php

try {
    doSomething();
} catch (Exception $e) {
    processException($e);
} finally {
    cleanupWork();
}

// finally is optional
try {
    doSomething();
} catch (Exception $e) {
    processException($e);
}

// catch is optional
try {
    doSomething();
}

// In the end, catch is also optional
    doSomething();

?>

The finally clause in a try-catch-finally is actually optional: it can be omitted.

The catch clauses in a try-catch-finally are also optional: they can be omitted.

When the catch and finally clauses are all omitted, the try clause can also be omitted safely.

See Also

PHP Features

Last updated: 11 October 2026

null Or Boolean As Array

<?php


var_dump(null [[]]); // NULL
var_dump(true [[]]); // NULL
var_dump(false [[]]); // NULL

var_dump( [][[]]); // fatal error

Null and booleans support the array syntax, but always return NULL as value. Since PHP 7.4, they also report a warning, as this is not good code.

Arrays also report errors when the key type is not valid, such as another array or an object.

Interestingly, Null and boolean accept illegal keys, with a simple warning.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Hide Sensitive Parameter

<?php

function secret($key, #[\SensitiveParameter] string $key2, $otherKey) {
    throw new Exception('');
}

$key = '123';
print secret(
    $key,                              // plain visible
    $key,                              // hidden by the method
    new SensitiveParameterValue($key)  // hidden by the caller
);

?>

PHP 8.2 introduces the #[SensitiveParameter] attribute to hide sensitive values from debug messages when used on function parameters. Additionally, the SensitiveParameterValue class allows you to mark data as sensitive at the caller level, ensuring it won’t appear in stack traces. To retrieve the original value when needed, simply call the $object->getValue() method.

See Also

PHP Features

Last updated: 11 October 2026

No Property In Interface

<?php

function naturalIntegers() {
    $i = 0;
    while (true) {
        yield $i++;
    }
}

foreach(naturalIntegers() as $i) {
    if ($i > 10) {
        break;
    }
    print $i.PHP_EOL;
}

print "finished
";

PHP is better than me : I thought I found a way to create properties in an interface, by declaring promoted properties in a constructor.

Someone coding the PHP engine was smarter than me : it feels so good! (BTW, who would that be?)

It’s also going to save me some sanity, as I don’t have to upgrade my Static Analysis Engine.

Note that PHP 8.4 introduced properties in interfaces, as long as they have property hooks.

See Also

PHP Features

Last updated: 11 October 2026

The Uncallable Function

<?php

function foo() {
    echo __METHOD__;
    yield 1;
}

foo(); // displays nothing, no code is executed
echo 'the end'; // displays the end

?>

The function foo() cannot be called. When the code does so, nothing happens.

The trick is the inclusion of a call to yield (or yield from) in the function. It turns the function into a generator, and calling it directly does nothing. It needs to be called with foreach() or iterator_to_array().

This trick applies to functions, arrow functions, closures and methods.

See Also

PHP Features

Last updated: 11 October 2026

Easy Wither Call

By Benoit Viguier

<?php

class Foo
{
    public function __construct(
        private readonly int $a,
        private readonly string $b,
    ) {
    }

    public function withA(int $a): self
    {
        return new self(...(get_defined_vars() + get_object_vars($this)));
    }

    public function withB(string $b): self
    {
        return new self(...(get_defined_vars() + get_object_vars($this)));
    }
}

$f = (new Foo(1, "foo"))
    ->withA(2)
    ->withB("bar");

var_dump($f);

Can be useful to create “with-ers” in an immutable (value) object with a LOT of readonly properties.

‘+’ is the addition of arrays, the first values have priority.

get_defined_vars() gets the local variables, with their name, unlike func_get_args().

Valid in PHP 8.1, with support for variadic and named keys.

See Also

PHP Features

Last updated: 11 October 2026

Array Is Full

<?php

$array = [PHP_INT_MAX => 1];
$array[] = 2;

// Cannot add element to the array as the next element is already occupied

When using append, the next id is calculated based on the largest integer key ever used in that array. When the array has reached the biggest integer possible, a.k.a. PHP_INT_MAX, then the next-array-key generator fails, and this error is displayed.

Also, it is a fatal error to fill an array beyond its capacity.

See Also

PHP Features

Last updated: 11 October 2026

Const Without Parenthesis

<?php

const A = 1;
echo ( A ) + (3);

const int = 2;
echo ( int ); // syntax error!!

It is possible to put a single literal inside parenthesis: that’s just the literal itself.

It is possible to put a single global constant inside parenthesis: that’s just the global constant itself.

It is possible to create a constant called int, because the name is not protected against usage, even if it is reserved for type and casting.

Then, it is not possible to int in parenthesis, because PHP confuses it with a cast.

See Also

PHP Features

Last updated: 11 October 2026

Cast Are Case Insensitive

<?php

$a = (int) $b;
$a = (INT) $b;
$a = ( int) $b;
$a = ( int ) $b;
$a = (int ) $b;
$a = (     int    ) $b;
?>

Today I learned that PHP cast operators like (int) and (array) are case-insensitive, so (STRING) works just as well. Additionally, the amount of whitespace (spaces, tabs, etc.) around these casts doesn’t affect functionality. These aspects are flexible in PHP, and what remains, such as casing and spacing style, is largely a matter of coding convention and personal or team preference.

See Also

PHP Features

Last updated: 11 October 2026

Array And Object Syntax, Not The Same Error

<?php

function foo() {
    yield 1;
    yield 2;
    yield 3;
    
    return ' 4';
}

function goo($a, $b, $c) {
    print "$a $b $c";
}
print PHP_EOL;

goo(...foo());

goo(...($g = foo()));
echo $g->getReturn();

?>

When accessing an array as an object, PHP raises a Warning and continues with NULL.

When accessing an object as an array, without ArrayAccess, PHP raises a Fatal Error and stops.

It is critical to check the syntax used to access an object.

See Also

PHP Features

Last updated: 11 October 2026

When Space Matters

<?php

echo 10 / 1 . ' ms ';
echo 10 / 2 .' ms ';

//Parse error: syntax error, unexpected single-quoted string " ms ", expecting "," or ";" 
echo 10 / 3. ' ms ';

//Parse error: syntax error, unexpected single-quoted string " ms ", expecting "," or ";" 
echo 10 / 4.' ms ';

// OK again
echo 10 / 5.6. ' ms ';

The two first echo are valid, because there is a space between the integer and the dot : PHP interpret them as a concatenation.

The third and fourth ones are a syntax error, because PHP starts interpreting a float, but fail to get the decimal part.

Finally, the last echo is valid, because PHP managed to build a float, so it doesn’t mistake the second point with another float, and use it as a concatenation.

Some edge cases of white space matters in the syntax.

See Also

PHP Features

Last updated: 11 October 2026

array array = array()

<?php

class x {
    // PHP 8.3 syntax
    const array array = array();
}

This made me chuckle… welcome to PHP 8.3 !

First array is the type, the second is the name of the constant, with the relaxed keyword, and the last one is the value of the array. It is not often that three times the same token is used in a row. Could we try 4 times?

See Also

PHP Features

Last updated: 11 October 2026

Cannot Yield And Never

<?php

function naturalIntegers() {
    $i = 0;
    while (true) {
        yield $i++;
    }
}

foreach(naturalIntegers() as $i) {
    if ($i > 10) {
        break;
    }
    print $i.PHP_EOL;
}

print "finished
";

Never, as a return type, mentions that the method will never return. This means that it either calls exit(), throws an exception, or is an infinite loop.

Infinite loop escaped my radar until I realized it is a good way to characterize the loop in an explicit way: put any explicit loop in a never method and know before hand of the implications.

Sadly, infinite loop work very well with Yield and yield from, but such methods must be typed with Generator. Too bad.

See Also

PHP Features

Last updated: 11 October 2026

Class Invasion Both Ways

<?php

class x {
    private function foo() { echo __METHOD__; }

    function YinX() {
        $y = new y();
        $y->foo();
    }
}

class y extends x {
    function YinY() {
        $this->foo();
    }
}

// here, Y can call private foo();
(new x)->YinX();

// here, Y cannot call foo()
(new y)->YinY();

?>

Class invasion, it when an object access anything it wants in another object of the same class. Thanks to the way PHP checks each object’s perimeter.

The same checks allow the other object to access the current one’s, as seen in that example.

See Also

PHP Features

Last updated: 11 October 2026

Typed By Enum

<?php

enum e {
    case A;
    
    const B = 1;
}

var_dump(e::A instanceof e);
var_dump(e::B instanceof e);

?>

Enumeration cannot be instantiated into an object. In fact, the cases of the enumeration are the objects. Hence, a case object has the enumeration type.

Then, an enumeration (class) constant cannot be confused with a case, because the enumeration cannot be instantiated, and class constants also do not support new initializers.

See Also

PHP Features

Last updated: 11 October 2026

Change A Static Variable Outside Its Function

<?php

function &foo() {
    static $x = [];
    print_r($x);
    
    return $x;
}

$a = foo()[] = 3;
var_dump($a);
foo();

Static variables are persistent between calls to the same method. They are local, unless they are returned with a reference. Then, they can be accessed in the main code, and read or even written.

This is the case here, without even storing the value: it is actually updated on the fly with the array notation on the function call.

See Also

PHP Features

Last updated: 11 October 2026

Scalar Types Are Unqualified

<?php

function foo(\array $array) {}
foo([]);
//TypeError: foo(): Argument #1 ($array) must be of type array, array given, 

function goo(\callable $callable) {}
goo(strlen(...));
//TypeError: foo(): Argument #1 ($array) must be of type callable, array Colsure, 

function hoo(\int $int) {}
hoo(1);
//Type declaration 'int' must be unqualifid

Scalar types like int or iterable should be used without a leading slash. However, there are exceptions. In PHP 8.3, types such as callable and array are still misinterpreted as class names, which can result in confusing or poorly worded error messages. This inconsistency remains a known issue in the language, affecting type declarations and error reporting for these specific cases, until PHP 8.5.

See Also

PHP Features

Last updated: 11 October 2026

array_append() And Short Assignation

<?php

$x = [];
$x[] = 1;
$x[] += 2;
$x[] ??= 3;
$x[] *= 4;
$x[] **= 5;
$x[] |= 6;

print_r($x);

?>

PHP allows using short assignation operators with the array append operators. This means that the code adds 2 to the array append. No error is displayed, except in the case of ??=, who is yield a Fatal error, with an explicit “Cannot use [] for reading”.

In any case, all of those expressions make little sense : the short assignation operators shall exist on an existing value, not a new one. Here, it looks like the default value is null.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Coalesce And Null

<?php

// very slow
$total = @$x;

// looks weird
$total = $x ?? null;

// lengthy 
if (!isset($x)) {
    $total = null;
} else {
    $total = $x;
}

?>

I’m still struggling to pick a side in this debate.

@ is too slow, because it merely hides the error.

?? looks dumb. It looks like : if it is null, use null as default.

The if() command is long to type.

Is there another way to handle this issue?

See Also

PHP Features

Last updated: 11 October 2026

All Classes Aliases

<?php

class_alias(class1::class, class2::class);

class_alias(trait1::class, trait2::class);

class_alias(enum1::class, enum2::class);

class_alias(interface1::class, interface1::class);

?>

To make a class alias, use class_alias();

To make an interface alias, use class_alias();

To make an enum alias, use class_alias();

To make a trait alias, use class_alias();

Also, there is no ::enum operator, ::trait operator or ::interface operator, so you can just just ::class

In the end, they all share the same name space.

See Also

PHP Features

Last updated: 11 October 2026

Mixed And Untyped Are Not Compatible Types

<?php

class x {
    protected $p;
    protected mixed $p2;
}

class x2 extends x {
    protected mixed $p;
    // type of x2:$p must not be defined (as in class x)
    
    protected $p2;
    // Type of $x2:$p2 must be mixed
}

A property typed mixed cannot be redefined later as untyped.

A property untyped cannot be redefined later as mixed.

Although, both syntax represent the same reality: any type goes.

Same for return type, in method compatibility situations. Although, it happens only when the parent is mixed, and the child untyped.

With return type, PHP does check if anything is returned with a type of mixed, and not if it is untyped.

See Also

PHP Features

Last updated: 11 October 2026

Abstract Constants

By WebSmithery

<?php

abstract class Foo {
    
    // self-referential 'abstract' declaration
    const NAME = self::NAME;
}

class Fooling extends Foo {
    
    // Overrides definition from parent class
    // Without this declaration, an error will be triggered
    const NAME = 'Donald';
}

There’s a pretty common pattern to declare “abstract class constants” in PHP.

PHP lazy loading will prevent the error “Fatal error: Uncaught Error: Cannot declare self-referencing constant self::NAME” if the constant is overloaded.

This makes an effective ‘abstract constant’, that must be defined to be usable.

See Also

PHP Features

Last updated: 11 October 2026

Cannot Static Static Property

<?php

class x {
    // the way to go is self
    static private self $property1;
    
    // tough luck not possible
    static private static $property2;
    
}
?>

Static can be used as a keyword to mark a property as static (sic). It could also be used as a type for that same property, leading to those confusing lines below.

In fact, static would accept self, but also parent and any children class: just like self. So, self is sufficient to type properties, and there is no need for static static.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

More Attribute Usage

<?php

#[MyAttribute]
class x {}

#[MyAttribute]
enum e {}

#[MyAttribute]
interface i {}

#[MyAttribute]
trait t {}

// ===================

#[MyFunctionAttribute]
function foo() {}

$c = #[MyFunctionAttribute] function () {};

$f = #[MyFunctionAttribute] fn () => 1;

PHP attributes are for classes, but also for enums, traits and interfaces. With ::TARGET_CLASS.

PHP attributes are for functions, and also for closures and arrow functions (but not for methods). With ::TARGET_FUNCTION.

Beware of the position of the attribute with closures and arrow functions, though.

See Also

PHP Features

Last updated: 11 October 2026

Run Any Script In A Phar

<?php


include 'phar://exakat.phar/script/load.php';

It is not possible to call directly a PHP script stored in a Phar archive: the only way to reach it, is using include 'phar://$path', where PHP understands the phar:// protocol.

And that makes it possible to call directly a PHP script from the shell: include it directly.

In terms of security, it is important to remember that a PHAR is an archive, and anything may be accessed or extracted.

See Also

PHP Features

Last updated: 11 October 2026

Static Variable Outside A Method

<?php


namespace x {
    foreach([1,2] as $i) {
        static $s = 0;
        
        print $s + $i;
        ++$s;
    }
    
}

// 13

?>

TIL that PHP static variables can be declared out of a function, in the namespace.

Then, it is a simple variable with a default value. There is no way to call the global scope again (that makes no sense). In fact, including the same file simply restarts the context and the static variable again.

May be a warning from the linter could be nice.

In PHP 8.3, duplicate ‘static’ variable definitions is forbidden. Not in a loop, which is a bad practice anyway.

See Also

PHP Features

Last updated: 11 October 2026

Enumeration’s Cases Comparisons

By Frederic Bouchery

<?php

enum A: string
{
    case Foo = 'bar';
}

enum B: string
{
    case Foo = 'bar';
}

echo A::Foo === B::Foo ? 'Yes' : 'No';

Enumeration cases are different when the enumeration are different, not when the value of the case is different. It is a difference with using constants for enumerations.

To compare their value, the comparison must be done with the ‘value’ property, not the object.

See Also

PHP Features

Last updated: 11 October 2026

All Static Curly Syntaxes And One Wrong

<?php

echo A::A;
echo A::{A};
echo A::{$A};
echo A::${A};
echo A::${$A};
echo A::${${A}};
echo A::${${$A}};
echo A::${${$A}};
echo A::${${A}};
echo A::${${$A{A}}};

?>

What is the only invalid syntax in the following list?

Assume that all needed definitions are available.

Let’s review them all.

  1. This is a classic class constant syntax, or an enumeration case.

  2. This is the new PHP 8.3 variable class constant syntax. The trick is that the dynamic part is the global constant A.

  3. This is the new PHP 8.3 variable class constant syntax. The name of the constant is in the variable $A.

  4. This is the variable static property class. The name of the property is in the global constant A.

  5. This is the variable static property class. The name of the property is in the variable $A.

  6. This is the variable static property class. The name of the property is in the global constant A, which builds the variable variable ${A}.

  7. This is the variable static property class. The name of the property is in the variable $A, which builds the variable variable ${$A}.

  8. This is the variable static property class. The name of the property is in the variable variable $$A, which builds the variable variable ${$$A}.

  9. This is the variable static property class. The name of the property is in the variable variable ${$A}. It is a composition of previous cases.

  10. This is the problematic one. $A{A} is an attempt at reading a element at position A in the variable $A. This is a removed PHP feature, since PHP 8. It should be written $A[A], though it would be too obvious for the puzzle.

See Also

PHP Features

Last updated: 11 October 2026

No Such Function As Empty

<?php

// bool(true)
var_dump(empty(null));

//Fatal error: Uncaught Error: Call to undefined function empty() 
var_dump(\empty(null));

There is no function called empty(), which could be accessed using a fully qualified name, such as \empty. There is also no function that can be defined as such, since empty() is already taken.

When the code needs to pass around a empty function, the best approach is to wrap a call to the empty() language construct, in a closure.

See Also

PHP Features

Last updated: 11 October 2026

Strange –> Operator

By Andrew Schmelyun

<?php

$i = 10;

while( $i --> 0) {
    print $i . ', ';
    
}

This PHP code is valid. It works because of the way operator syntax is parsed: it’s another way of writing while ($i-- > 0): the spaces are not important in this syntax. The result is a very visual way to explain what the loop is doing.

See Also

PHP Features

Last updated: 11 October 2026

Unexpected Keys In Array

<?php

$array = [null => 42, false => 43];

var_dump(array_key_exists(null, $array));  // true
var_dump(array_key_exists('', $array));    // true
var_dump(array_key_exists(false, $array)); // true
var_dump(array_key_exists(0, $array));     // true
var_dump(array_key_exists(0.0, $array));   // true

print count($array). " elements in the array
";

It is possible to put 2 elements in a PHP array, find different 5 keys with array_key_exists() or isset() and yet, still count 2 distinct elements (key wise).

The type-juggling for array keys is applied in every PHP features, to keep things easy to use.

This code is one rare way to show how it still leaks. Depending on the context, it might be very confusing.

See Also

PHP Features

Last updated: 11 October 2026

Recursive Generator

<?php

function foo() {
    static $f = 0;
    yield ++$f;
    yield from goo();
}

function goo() {
    static $g = 'a';
    yield ++$g;
    yield from foo();
}

foreach (foo() as $foo) {
    print $foo.PHP_EOL;
}

?>

Generators in PHP, when using the yield keyword, can become recursive through the use of the yield from construct. This allows a generator to delegate part of its iteration to another generator, creating a chain of generators. However, this recursive behavior is only effective when the generator is consumed by an external iteration mechanism such as a foreach() loop or a function like iterator_to_array(). Without such iteration, the recursive yielding will not be triggered or evaluated.

See Also

PHP Features

Last updated: 11 October 2026

Documenting Parameters

<?php

// what does 1 and true mean here?
$keys = array_keys($array, 1, true);

// yes but of course
$keys = array_keys($array, filter_value:1, strict:true);

?>

Sometimes, even a literal value used as parameter is not readable. An easy way to document the literal values is to provide the name of the parameter. Since the parameters need a smart name to be used in the method, using it in the method call provides more context to the parameter.

Another option is to make that literal value a constant, and use that constant to provide a readable name.

See Also

PHP Features

Last updated: 11 October 2026

Catch Is Optional

<?php

try {
    $x = initSomething();
    doSomething();        // may throw an exception
}

// no catch: exception keep their way

// finally is always executed
// with or without exception being caught
finally{
    $x->shutdown();
}

?>

In a Try Catch Finally command, only the try part is compulsory. It is possible to create a try command without any catch clause: that way, exceptions are indeed not caught. The finally clause is also optional, but when it is provided, it is always executed, even if exceptions are not caught.

See Also

PHP Features

Last updated: 11 October 2026

Void Parameter In array_keys()

<?php

$array = [3 => null, 4 => 4];

var_dump(array_keys($array, null));
// 3

var_dump(array_keys($array /*, void */));
// 3, 4

There is a ‘void’ parameter in PHP. It is the second argument of array_keys().

That second parameter is often omitted (and unknown).

If present, it is typed ‘mixed’ to allow any value to be searched (here, null).

If absent, array_keys() returns ALL keys. When absent, it is not null, nor any other type. The last one possible is ‘void’.

Type is then : void|mixed.

See Also

PHP Features

Last updated: 11 October 2026

Unsetting Properties Surprises

<?php

class X {
    public $a;
    public ?int $b = 1;
    
    function clean() {
        unset($this->a);
        unset($this->b);
    }
}

$x = new X;
$x->clean();

var_dump($x->a);
var_dump($x->b);

?>

Unsetting properties is always a surprise.

First, if the property was typed, it yields a Fatal Error, as the property cannot be accessed before initialization. And, the unset destroyed the property.

Also, checking an unset property with property_exists() is done against the class definition, not the current object state.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

Multiple Aliases

<?php

trait t {
    function foo() {
        echo __METHOD__.PHP_EOL;
    }
}

class x {
    use t {
        foo as goo;
        foo as ioo;
    }
}

(new x)->foo(); // t::foo
(new x)->goo(); // t::foo
(new x)->ioo(); // t::foo

It is possible to rename an imported method from a trait, by using an alias name. It is also possible to import it several times, with different names.

And, unlike static/self, it is not possible for the called method to know how it was called.

See Also

PHP Features

Last updated: 11 October 2026

Plus Plus Minus Minus

<?php

$d = 0;

echo $d + + - - 1;
?>

What does a separated list of plus and minus do to a literal? They will be merged independently in the literal. Here, two negations will make a positive, so we get a plus.

Don’t forget the space between the plus and minus, or it will be a post increment operator, and a different answer.

See Also

PHP Features

Last updated: 11 October 2026

Constants Can Be Impossible

<?php

enum x : string {
    case B = 'b';
    const C = 'c';
}

class x2 {
    const E = x::B->value;
    const F = x::C + [];
}

echo x2::E;

In this code, the constant x2::F is not possible, because adding a string and an array results in a fatal error.

Yet, this is determined at execution time, and only when the constant is used.

Since this constant is never used, its code is never executed, and it doesn’t yield any error. PHP has optimized the error away.

See Also

PHP Features

Last updated: 11 October 2026

Constants Are Mutable

<?php
  
class x {
    public string $p = 'abc';
}

const A = new x;

A->p = 'def';

echo A->p; // def

?>

Since PHP 8.2, it is possible to use an object with a constant: this is the new initializer feature.

With a object in a constant, it is still possible to call methods or properties, and mutate the constants. Indeed, the constant is still representing the same object, but the properties in the objects are not constants.

See Also

PHP Features

Last updated: 11 October 2026

Quick DTO Or VO Copy

By Benoit Viguier

<?php

class DTO {
    public function __construct(
        public int $a,
        public int $b,
        public int $c,
        public int $d,
        public int $e,
        public int $f,
    ) {}
}

$dto = new DTO(1, 2, 3, 4, 5, 6);
var_dump($dto);

// #PHP 8.2+, with named parameters in arrays
$copy = new DTO(...(['d' => 42] + get_object_vars($dto)));
var_dump($copy);

// #PHP 8.0–1+, with unnamed parameters in arrays
$copy2 = new 
DTO(...array_values(array_merge(get_object_vars($dto), ['d' => 42])));
var_dump($copy2);

// #PHP 7.4– : not supported

A small PHP trick, combining named parameters, spread and union arrays operators to easily create a modified copy of a DTO: https://3v4l.org/ZWX5G#v8.2.10

It’s fun if you have a lot of parameters, but using a string containing the parameter’s name isn’t really satisfactory

It is possible to extend this syntax to PHP 8.0+ with a clever array_values() / array_merge(): https://3v4l.org/igrsW

$copy = new DTO(...(array_values(array_merge(get_object_vars($dto), ['d' => 43]))));

Now, this extended syntax is an easy prey to property definition order, constructor argument order, and temporary property deletion, unlike your original approach.

See Also

PHP Features

Last updated: 11 October 2026

Stealth Generator

By Frederic Bouchery

<?php 

function foo(): iterable {
    return ['a'];
    yield 'b';
}

foreach(foo() as $v) {
    echo $v;
}

The code below has a useless loop. The presence of the yield keyword in the function body makes it a generator. As such, foreach() will react to yield calls, though the function returns immediately, without a yield. Hence, the empty loop, even though the function returns an array: indeed, to have the function behave as expected, it is necessary to remove the unreachable yield call, and then, the foreach() can use the return for the loop.

See Also

PHP Features

Last updated: 11 October 2026

Quick Serialize

By Doeke Norg

<?php

final class ValueObject{
    
    private int $uninitialized;
    private int $initialized = 3;
    
    public function toArray(): array {
        return get_object_vars($this);
    }
}

$object = new ValueObject();

var_dump($object->toArray());

// ['initialized' => 3]

Did you know that get_object_vars() only returns the initialized parameters from your objects? This can be very useful when unserializing objects.

PS : accessing uninitialized properties leads to a Fatal error. This trick avoids it.

See Also

PHP Features

Last updated: 11 October 2026

Clone Clone Clone

<?php

class a {}
$a = new a;
// object(a)#1 (0) {}

var_dump($b = clone clone clone $a);
// object(a)#2 (0) {}

var_dump(new (new $a));
// object(a)#3 (0) {}

It is possible to chain clone operators : PHP optimize this and skips any intermediate clone. The resulting final object is number 2, so the inner clones were duly ignored.

On the other hand, it is possible to create a new object from an existing object, but it is not possible to chain the new calls without using parenthesis.

See Also

PHP Features

Last updated: 11 October 2026

Set readonly Properties Outside The Class

<?php

$factorial = function (int $n) use (&$factorial) {
    if ($n === 1) {
        return 1;
    }
    
    return $factorial($n - 1) * $n;
};

print $factorial(5);

PHP 8.1 readonly properties cannot be set from global space, but they can be forced from the host class, just like accessing private properties.

It doesn’t work outside the host class : not in global space, not in a derived class.

Besides that, readonly act as usual : it is only possible to assign the property once.

See Also

PHP Features

Last updated: 11 October 2026

Exception Polymorphism

<?php

interface i {}

class myException extends Exception implements i {}

try {
    throw new myException();
} catch(i $i) {
    var_dump($i instanceof myException);
}

Custom exceptions are like any other classes: they may implements interfaces. Later, that interface may be used to catch the exception, even if the interface has nothing to do with exceptions. And with several interfaces on the same exception, it is possible to catch them in different ways.

See Also

PHP Features

Last updated: 11 October 2026

Exponential Minus One

<?php


echo expm1(1);
echo PHP_EOL;
echo exp(1) - 1;

You can save typing by using expm1($x) instead of exp($x) - 1. Also, you might have to take care of differences, as both results might be slightly different depending on the OS you’re running it on : Debian is OK, but MacOS says it’s different.

See Also

PHP Features

Last updated: 11 October 2026

PHP Infinity Is Reachable

<?php

var_dump(is_infinite(log(0)));
// true
var_dump(1 > log(0));
// true

Infinite values are sometimes returned by PHP functions, such as log(0) or exp(PHP_INT_MAX). In these cases, beware and do not compare it directly with an integer as a positive is considered bigger than infinite. It is recommended to use the function is_finite.

See Also

PHP Features

Last updated: 11 October 2026

Negative Squares

<?php


echo -3 ** 2;
// -9

This is still my favorite PHP bug : literal negative value is squared, and is … negative. In fact, the ** operator has precedence over the minus operator, and the square is then executed before the negation. Hence, the negative results. It is useful to process, correctly, parenthesises, but not integers.

See Also

PHP Features

Last updated: 11 October 2026

Negating An Assignation

<?php

// $e contains the check
!$e = checkSomething();

// $e contains the check,
// and the condition still passes
if (!$e = checkSomething()) {
    print "Something was wrong!";
}

I always wondered why PHP allows to NOT a variable on the LEFT side of an assignation.

It makes sense with an iffectation (an assignation within an if)

I’m sure other such expressions are possible, with unary operators.

Definitely not for me, for readability reasons; same as !$o instanceof X.

See Also

PHP Features

Last updated: 11 October 2026

Numeric Separators Inside A String?

<?php

echo 1_2_3;

$a = '1_2_3';

// warning: a non-numeric value encountered
echo $a + 0;   // 1
echo (int) $a; // 1
$int = eval("return $a;");

Since PHP 7.4, there are numeric separators, to make integers more readable.

They are only for hard-coded literals, so what do you do if you have stored them in a string?

The solution is to rely on eval(), with a trick : $int = eval('return '.$a.';');. return is important to return the value that was generated by the code.

Another option is to remove the underscore _ chars, and cast the value to int.

See Also

PHP Features

Last updated: 11 October 2026

Identifier Confusions

<?php


const A = 'b::c';

class B {
    static function C() { echo __METHOD__; }
}

//valid call to \B::C()
constant('A')();

// class A not found
//new A;

// Call to undefined function A()
A();

// parse error, this is not supported
//{A}();

Identifiers are used both for constant names and for class names (CITE). Depending on the situation, they may be confused one for the other: here, A is a constant, and its value is accessible for dynamic code purposes. Yet, A() cannot be used.

There is no syntax to call dynamically a function whose name is stored in a constant, without resorting to a call the the constant() function or a temporary variable.

See Also

PHP Features

Last updated: 11 October 2026

Relaxed Syntax With Const

<?php

class x {
    const array = [];
    const int   = 2;
}

print_r(x::array);
echo x::int;
echo x::class;

?>

Relaxed syntax is the possibility to use PHP keywords as method or class constant names. This leads to funny expressions, that look like something else.

The only forbidden keyword is class, as it is used to report the name of the current class.

See Also

PHP Features

Last updated: 11 October 2026

Closure To Call

<?php


function foo($a, $b) { return $a + $b; }

foo(1,2);
foo(...)(1,2);
foo(...)(...)(1,2);

The ... operator can be used to create a closure from a method. That closure can be called immediately, or used to create yet another closure. That processed is not guarded, so the (...) operator can be called multiple times, without any effect.

See Also

PHP Features

Last updated: 11 October 2026

Get $this In A Static

<?php

class X {
    function foo() {
        print_r($this);
    }
    
    function bar() {
        self::foo();
    }
    
}
?>

A static method does not have access to the current object, by definition.

It is also possible to call statically any method within a class.

But when a static method calls a non-static method, an error is produced: Non-static method x::foo() cannot be called statically.

Finally, calling statically a non-static method still defines $this in the target method. Static is the nature of the method, not the call.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

How To array_intersect_uassoc_insensitive()

<?php
// From the PHP manual
$arrayl = array("a" => "green", "b" => "brown", "c" => "blue", "red");
$array2 = array("a" => "GREEN", "B" => "brown", "yellow", "red");

$arrays = [$arrayl, $array2];

print_r(call_user_func_array("array_intersect_uassoc", [$arrayl, $array2, "strcasecmp"] ));

array_intersect() functions use a direct comparison between the values in the array to make the comparison. When the comparison needs to be case insensitive, then one can use the u version of those functions, to make a case insensitive comparison.

See Also

PHP Features

Last updated: 11 October 2026

Class And Constant Confusion

<?php

  const stdClass = 1;
  
  new stdClass;

?>

Class names and global constant names are two distinct name spaces. It is possible to use the name of a class as a constant.

It is also possible to instantiate a class with a constant notation: that is, by omitting the parenthesis in the new call, when no arguments are needed.

This allows for very confusing lines like these ones.

See Also

PHP Features

Last updated: 11 October 2026

Don’t Forget To Yield

<?php

function foo() {
    yield 'foo';
    goo();
}

function too() {
    yield 'too';
    yield from goo();
}

function hoo() {
    yield 'hoo';
    yield goo();
}

function goo() {
    yield 'goo';
}

foreach(foo() as $a) { print $a.PHP_EOL;}
// goo
foreach(too() as $a) { print $a.PHP_EOL;}
// too goo
foreach(hoo() as $a) { print $a.PHP_EOL;}
// hoo Uncaught Error: Object of class Generator
// could not be converted to string

?>

It is possible to delegate a generator to another generator.

One point to keep in mind is that they should not be called raw, as nothing happens.

And don’t forget the from part of the keyword, otherwise, it yields the generator, instead of running it.

See Also

PHP Features

Last updated: 11 October 2026

Inconsistent Constructor Signatures

<?php

declare(strict_types=1);

class x {
    function __toString() : string {
        return "YES";
    }
}

function foo(string $a) {}

// piece of syntax of the day (POSOTD)
echo $x = new x;

print " A $x";

// Fata error: Argument #1 ($a) must be of type string, x given, foo($x);

?>

PHP enforces that methods have the same signature in a parent class and in a children class. It raises a Fatal Error if not.

Unless for constructors, where the signatures can be different.

This exception to the rule is for legacy purposes, as many source code have varying signatures in a class hierarchy.

Yet, modern OOP recommends to synchronize those signatures, so has to allow instantiation using the same set of arguments.

See Also

PHP Features

Last updated: 11 October 2026

foreach() With All The Same Keys

<?php

function myGenerator() {
    yield 0 => 1;
    yield 0 => 2;
    yield 0 => 3;
}

foreach (myGenerator() as $key => $value) {
    echo $key . PHP_EOL;
}

// Output:
// 0
// 0
// 0

?>

It is possible for a foreach() loop to produce multiple times the same key. To do so, avoid using arrays, which enforce the unique key.

One need to use a generator or a Traversable class, where the same key is always yielded.

See Also

PHP Features

Last updated: 11 October 2026

Battle Of Definition

<?php

interface I {
    function __construct(int $i);
}

class X implements I {
    function __construct(int $i) {}
}

// Fatal error: Declaration of Yy::__construct(string $a) must be
// compatible with I::__construct(int $i)

class Y extends X {
    function __construct(string $a) {}
}

?>

Methods signatures must be compatible with the parent class’s definition. This is true, except for __construct(), for which the compatibility is never checked.

Yet, compatibility is still enforced when the __construct() definition is in an interface. Then, both the implementing table and all its children must have the interface’s compatibility.

See Also

PHP Features

Last updated: 11 October 2026

Variadic All The Yields

<?php

function foo() {
    yield 1;
    yield 2;
    yield 3;
    
}

function goo($x, $y, $z) {
    print "$x $y $z
";
}

goo(...foo());
// 1 2 3

?>

The ellipsis operator works on generators: it is akin to a foreach() on all the values, or a call to iterator_to_array().

Here, the generator produces three values, and they are used to fill the three argument positions. It makes a kind of shortcut.

See Also

PHP Features

Last updated: 11 October 2026

Refactoring strpos()

<?php

$a = 'cde';

if ( strpos($a, 'abc') === 0)       { /**/ }
// Replace with 
if ( str_starts_with($a, 'abc'))    { /**/ }

if ( strpos($a, 'abc') !== 0)       { /**/ }
// Replace with 
if ( !str_starts_with($a, 'abc'))   { /**/ }

?>

Converting an expression like strpos() === 0 to use str_starts_with() is simple and direct. However, caution is needed when the comparison involves a difference rather than equality. In such cases, replacing the functions isn’t a straightforward one-to-one substitution, as the logic and intent behind the original expression may change, leading to unexpected behavior if not handled carefully.

See Also

PHP Features

Last updated: 11 October 2026

is_a() Versus Instanceof

<?php

class A {}

var_dump(new a instanceof A);
// true

var_dump(new a instanceof A::class);
// Fatal error

var_dump(is_a(new a, A::class));
// true

var_dump(is_a(new a, A);
// Undefined constant A

is_a() and instanceof are the same feature: check if an object is of a specific class. Yet, they do things in different ways.

Using the ::class operator is safe in both cases.

Using a name is a class name for instanceof, while it is a constant name with is_a(). This may lead to confusion.

See Also

PHP Features

Last updated: 11 October 2026

Exporting Properties

By Tim Macdonald

<?php

class MyList
{
    private $list = [];

    function &getList()
    {
        return $this->list;
    }

    function add($item)
    {
        $this->list[] = $item;
    }
}

$list = new MyList;
$list->add(1);
$list->add(2);

$arr = &$list->getList();

$arr[] = 3;
$arr[] = 4;

dd($list->getList());

// array:4 [
//   0 => 1
//   1 => 2
//   2 => 3
//   3 => 4
// ]

With an accessor and a reference, it is possible to export a private property and manipulate it from the outside of the object.

This is not recommended, as it exposes data that is supposed to be protected by its visibility.

See Also

PHP Features

Last updated: 11 October 2026

Another Cast To Int

<?php

$a = '2';

$b = +$a;

var_dump($b);

?>

Another way to cast a value to int, besides the obvious (int) operator and the classic + 0, is to use the unary operator + operator on a variable: it add a sign operation on the variable, and the + sign is invariant: yet, the content of the variable is turned into an integer.

See Also

PHP Features

Last updated: 11 October 2026

ReturnTypeWillChange Is For All

<?php

interface i {
    function foo(): int;
}

class x implements i {
    #[Returntypewillchange]
    function foo() : int { return 'abc'; }
}

ReturnTypeWillChange is an attribute that tells PHP that the return type of the related method is different from the defined by the PHP native methods. In fact, this attribute may also be used on custom interfaces, to skip the type checks.

The method signature must be compatible with the interface definition, but, with the attribute, it may actually return anything else. This also means that types may only be trusted after checking for attributes.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

URL As Comments

<?php

http://www.example.com/foo 
function foo($a) {
    // what does this do?
}

?>

The code below includes a raw URL in the code. PHP understand that code as a goto label, followed by a online comment starting with //. As such, the comment is valid, but cannot be repeated, as it would make a dual goto label, and PHP would raise an error.

See Also

PHP Features

Last updated: 11 October 2026

Coalesce And Assignation

<?php

$a = null;
$b = 2;
$c = $a ?? $b = 3;

echo "$a $b $c";
// 3 3

The coalesce operator has lower precedence than assignment, meaning the assignment occurs first. Essentially, it behaves like parentheses: the right-hand expression is evaluated and stored in the variable, and then that stored value is used in the coalesce operation. This order of execution ensures the variable holds the evaluated result before it’s processed by the coalesce logic.

See Also

PHP Features

Last updated: 11 October 2026

Missing Methods Are Fatal

<?php

class x {
    function p() {
        return $this->p;
    }
}

$x = new x;
var_dump($x->p);
var_dump($x->foo());

Calling a missing method is a fatal error. Reading a missing property is a warning, and defaults to NULL. Writing to an undefined property is deprecated in PHP 8.3, and will become a fatal error in PHP 9.0. This means that both errors will have the same impact in the code.

See Also

PHP Features

Last updated: 11 October 2026

Only Initialize With Short Assignation Coalesce

<?php

class x {
    private static self $x;

    static function getInstance() {
        // all good
        return self::$x ??= new self();
    }

    static function getInstance2() {
        // Not ok : cannot access before initialisation
        return self::$x = self::$x ?? new self();
    }

    static function getError() {
        // Not ok : cannot access before initialisation
        if (!self::$x instanceof self) {
            self::$x = new self();
        }

        return self::$x;
    }
}

It is not possible to access a property before its initialisation. This is true to both static and normal properties.

While normal properties are initialized at constructor time, static properties might require a check before assignation : in case the property has not been yet assigned, a Fatal error will stop the code execution.

In fact, there is a way : it is the short assignation with coalesce, which will accept to check the NULL value, and only fill it if it is null.

See Also

PHP Features

Last updated: 11 October 2026

Keys Are Integers Whenever Possible

<?php

$array = [];
$array["01000"] = "Bourg-en-Bresse";
$array["75001"] = "Paris";

var_dump($array);

Array keys are only strings or integers: the later has priority. So, when storing a string that can be converted to an integer, PHP does the conversion automatically.

In the case displayed, the keys are French zip codes, which might start with a leading 0. But PHP converts it into integer, and drops that leading 0. The value is still correctly indexed, but now, the string representation has changed.

See Also

PHP Features

Last updated: 11 October 2026

Fast Creation Of stdClass Objects

<?php

// fastest
$final = (object) ['a' => 1, 'b' => 2];

// 2nd fastest
$array = [];
$array['a'] = 1;
$array['b'] = 2;
$final = (object) $array;

//slow
$final = new stdClass();
$final->a = 1;
$final->b = 2;

//slowest
class myStdClass extends stdClass {
    $function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }
}
$final = new mystdclass(1, 2);

The fastest way to create a stdClass object is to create an array, and then cast it to stdClass with the (array) cast operator. It is still faster even if the array is build piece-meal: two times slower.

Setting directly properties on the stdClass object is then about three times slower, and creating a class extension with an adapted __construct method is then four times slower.

In the end, this is a micro optimisation.

See Also

PHP Features

Last updated: 11 October 2026

An Elephpant In The Code

<?php

// decimal version
echo mb_chr(128024);

// hexadecimal version
echo "\u{01f418}";

When there is a need an elePHPant in a text, PHP always have one ready: well, an elephant, at least.

The unicode for an elephant is 128024 or 0x1F418. It may be used directly inside a string, or with the mb_chr() function, and not with the chr function, which is only handling a single byte.

See Also

PHP Features

Last updated: 11 October 2026

Internal Static Call

<?php

class a {
    function foo() {
        echo __METHOD__ . PHP_EOL;
    }
}

class b extends a {}
class c extends b {
    function bar() {
        $this::foo();
        $this->foo();
        a::foo();
        b::foo();
        c::foo();
        d::foo();
        static::foo();
        self::foo();
        parent::foo();
    }
}

class d extends c {}

(new c)->bar();

Trap of the day: one of the calls in bar() generates a ‘Non-static method a::foo() cannot be called statically’ error.

Which one? It is the d::foo(). All other calls are made within the C class : internal calls may use static or normal syntax, while external calls must use the correct call syntax. This allows calls like ‘parent::__construct()’.

When the call to bar() is made with ‘(new d)’, the ‘d::foo()’ works again.

See Also

PHP Error Messages

PHP Features

Last updated: 11 October 2026

strict_types Exceptions

<?php declare(strict_types = 1);

class x {
  function __toString() : string {
    return "abc";
  }
}

$x = new x;
print $x . " and $x
";

print $x;
echo $x;
echo PHP_EOL;

print implode(', ', [$x, $x]);
print implode($x, [1, 2]);

?>

strict_types do not apply to PHP operators, only on to typed structures.

Here, concatenation and interpolation all call __toString(), but not foo().

As you can see, print() and echo() are safe too, while implode() is not: actually, it is not safe for the first argument, but still OK with the elements of the array, in the second argument.

See Also

PHP Features

Last updated: 11 October 2026

Playing With Integers Limits

<?php

  $a = PHP_INT_MAX;
  $b = $a + 1;
  $c = (int) $b; 
  $d = intval($b);

  var_dump($b < 0);
  var_dump($c < 0);
  var_dump($d < 0);
  
?>

In mathematics, there’s the famous concept known as the ‘Ramanujan Summation’, where the infinite sum of all positive integers surprisingly equals -1/12. In PHP, we encounter a more practical and less abstract issue called integer overflow. When working with integers near the PHP_INT_MAX limit, it’s important to proceed with caution. Casting using (int) or the intval() function, as shown in the illustration, ensures correct behavior in recent PHP versions. The example demonstrates how PHP handles integer limits and type casting safely within supported boundaries.

See Also

PHP Features

Last updated: 11 October 2026

Signature Compatibility Exception

<?php

// Normal constructor
abstract class x {
     function __construct($a) {}
}

class y extends x {
    // OK
    function __construct($b, $c) {}
}

// abstract constructor
abstract class x2 {
     abstract function __construct($a);
}

class y2 extends x2 {
    // KO
    function __construct($b, $c) {}
}

// With interface
interface i3 {
     abstract function __construct($a);
}

class y3 implements i3 {
    // KO
    function __construct($b, $c) {}
}

All methods must be compatible with their parent’s signature: the types, reference options, variadic and number must be compatible (although, not equal).

The exception are constructors, which may be completely different from their parent’s. This is for backward compatibility.

And this rule also has the exception that abstract constructor, in interfaces and abstract parent classes, must be compatible, and their signature are enforced. Normal constructor are free.

See Also

PHP Features

Last updated: 11 October 2026

Object Is Not A Type

<?php

class a {}

$a = new A;
var_dump($a instanceof object); // false
var_dump(is_object($a));        // true

Such situations always make me smile, yet I am certain several of us will loose time on such a mistake. object is a type that can be used with argument, returntypes and properties, but not with instanceof. It is a bit more obvious with integer or string in the instanceof.

See Also

PHP Features

Last updated: 11 October 2026

Forgotten Throw

<?php

class MyException extends \Exception {}

if ($error !== false) {
    // This looks like 'throw' was omitted
    new MyException();
}

Sometimes, the throw keyword is forgotten, even as it is needed. It is difficult to realize it is missing: PHP instantiates correctly the exception, yet it immediately ignores it, as there is no usage of that exception: no error is reported. One need to identify a situation with an error, understand that the error is missing.

See Also

PHP Features

Last updated: 11 October 2026

Keywords In Namespaces

<?php

// PHP 8.0+ OK
// PHP 8.0- KO

namespace if\then\else {}

Since PHP 8.0, it is possible to use PHP keywords in namespaces.

In PHP 8.0, namespaces are processed independently, for their literal value. Before then, each namespace level was a distinct token, separated by the namespace separator: PHP keyword would then run into a processing conflict.

The last keyword that one can’t still use in a namespace is namespace, when used as the first part of the namespace : it is later replaced dynamically by the current namespace.

See Also

PHP Features

Last updated: 11 October 2026

No Warning For Unused Variables

<?php

  $a;
  // crii crii 
  
  3 || $b; 
  // ...
  
  echo $c;
  // Warning: Undefined variable $c
  
?>

PHP variable optimisation in action: the undefined variables are only reported when they are used.

The first is omitted : there are no operations.

The second is skipped : no need to execute the second term of the or.

The third is reporting a warning.

Of course, an assignation is not reported with an undefined variable, as it will be set. Although, may be reusage of a variable could be reported.

See Also

PHP Features

Last updated: 11 October 2026

Self In An Interface

<?php

interface i {
    function foo(self $i); 
}

class x implements i {
    //Declaration of x::foo(x $i) must be compatible with i::foo(i $i)
    function foo(self $i) {} 
    
    // OK
    function foo(i $i) {}
}

The keyword self in an interface represents that same interface: it does not represent the host class. Hence, using self as return type means that the method must return an object that implements the type i, not of the host class.

See Also

PHP Features

Last updated: 11 October 2026

instanceof Object

<?php

class a {}

$a = new a;

$b = '\\a';
var_dump($a instanceof $b);
var_dump($a instanceof \a);

// C is not a constant, but a class name (see use)
const object = '\\a';
var_dump($a instanceof object);
use a as c;
var_dump($a instanceof c);

// compile error : unexpected T_CLASS
// var_dump($a instanceof a::class);
// compile error : unexpected T_CONSTANT_ENCAPS_STRING
// var_dump($a instanceof 'a');

object is a type, but it can also be used as a constant name. Then, instanceof accepts it for testing a variable.

There are some other cases around instanceof, which are surprising upon first read.

It is possible to use a string or an object in a variable, but not a direct string, a constant nor a ::class.

See Also

PHP Features

Last updated: 11 October 2026

stdClass Object

<?php

var_dump(new stdclass);
var_dump((object) []);
var_dump($b = new stdclass());
var_dump(new stdclass(3));

PHP recycles the previously created stdClass objects ids. The following code displays Object #1, until it is stored in $b. When the stdClass is not stored in a container (variable, property, etc.), it is completely lost and no object counting happens.

Also, PHP’s stdClass’s constructor ignores all its arguments, as they are not used.

See Also

PHP Features

Last updated: 11 October 2026

A Case Of Misplaced Parenthesis

<?php

// suspicious and legit
if (in_array($a, $b, $c === true)) {}

// could it be this one? 
if (in_array($a, $b, $c) === true) {}

The closing parenthesis of the in_array() call may be misplaced, yet yield a valid PHP code, and even, a valid business case. This all depends on the actual value given to the $c variable.

It is most probably a bug, given the number of arguments in in_array().

See Also

PHP Features

Last updated: 11 October 2026

Checking For Names At Different Times

<?php


class stdClass {}
// Checked at execution time

interface Iterator {}
// Checked at execution time

function mb_substr() {}
// Checked at compile time

PHP lint detects early to avoid redeclared functions, based on the local PHP executable.

php -l => Cannot redeclare mb_substr().

That detection doesn’t apply to CITE until execution.

php => Cannot declare class stdClass

because the name is already in use`. This is a difference in processing similar information, which may cause late surprises.

See Also

PHP Features

Last updated: 11 October 2026

Integer Becomes Negative

<?php

$a = PHP_INT_MAX;
$b = $a + 1;
$c = (int) $b;
$d = intval($b);

var_dump($b < 0);
var_dump($c < 0);
var_dump($d < 0);

$a is the largest integer in PHP. So, $b, which is one bigger than $a, is actually a float, which is bigger and still positive. When casting the float to an integer, the integer is turned into a negative number by overflowing.

See Also

PHP Features

Last updated: 11 October 2026

Not All Strings In Arrays

<?php

class A {
    function __toString() {
        return "A";
    }
}

$a = new a;

$b = [$a => $a]; //Illegal offset type

print_r($b);

Array keys are coerced into strings or integers, while array values are left intact.

Yet, this doesn’t apply to array keys, so this code will display an Uncaught TypeError: Illegal offset type, even without strict_types.

This code needs an explicit cast to string to work.

See Also

PHP Features

Last updated: 11 October 2026

Recursive Arrow Function

<?php

$sum = fn($a, $sum) => empty($a) ? 0 : array_pop($a) + $sum($a, $sum);

print $sum([1,2,3], $sum);

?>

To make an arrow function recursive, it must include itself in the list of arguments. That way, it can call itself.

The intriguing point here is that the arrow function is defined based on the current variables, and the function holding variable is only defined after the arrow function.

See Also

PHP Features

Last updated: 11 October 2026

null Does Not Always Equal false

<?php

var_dump(null == false);

print_r([null => 1,
        false => 1,
        ]);

?>

null equals false, so the first equality is actually true.

On the other hand, using null and false as keys in an array, lead them to be cast to string: they end up being different, and that allows two elements to be stored in the array.

See Also

PHP Features

Last updated: 11 October 2026

Recursive Closure

<?php

$factorial = function (int $n) use (&factorial) {
    if ($n === 1) {
        return 1;
    }
    
    return $factorial($n - 1) * $n;
};

print factorial( 5 );

?>

To make a recursive closure, the closure must be both stored in a variable and passed as a use parameter to the same factorial.

It also works for arrow functions, although there is no the use, but the variable must be defined first, so it can be used in context later.

See Also

PHP Features

Last updated: 11 October 2026

Cast Precedence

<?php

$c = 100;

echo (int) $c * 0.12345;

?>

Casting has a higher precedence than multiplication (and addition). Here, the cast on the float happens first, which leads to 0. This 0 is later multiplied by 100, and that still gives 0.

If $c was the first operand, it would be cast first to integer, with no impact, and then multiplied: that leads to a float value, even with the cast.

See Also

PHP Features

Last updated: 11 October 2026

Enum As A Name

<?php

class enum {} 

enum class {} 

?>

It is possible to name a class enum, but not to name an enum class. In fact, ‘enum’ is an authorized name for interfaces and traits, to help with backward compatibility.

This also means that an enumeration may be called ‘enum’.

See Also

PHP Features

Last updated: 11 October 2026

Type Is Not Always Returned

<?php

class x implements y {
    function foo(): bool {
        throw new exception('Ah!');
    }
}

interface y {
    function foo(): bool;
}

?>

A method can display a return type, and never return it actually. This happens with exceptions, errors, yield and infinite loops. May be ‘never’ would be a better choice, if possible.

On the other hand, a function returning ‘void’, aka returning nothing, should not have any return statement, including the hidden one at the end of the method.

See Also

PHP Features

Last updated: 11 October 2026

The Namespace Stack

<?php

namespace {
    const A = 'in \\';
}

namespace A {
    const A = 'in A';
}

namespace A\B {
    const A = 'in A\\B';
}

namespace A\B\C {
    echo A;
}

This is a reminder that namespaces are not nested, but are merely prefixes. Hence, there is the current namespace, and the global one for the occasional fallback, but nothing in between. Namespaces are not folders, even when they are stored as such.

See Also

PHP Features

Last updated: 11 October 2026

Catch On Interfaces

<?php

interface i {}

class x extends \Exception implements i {}

try {
    throw new x();
} catch (i $x) {
    print "Caught!";
}

Exceptions can be caught using their interfaces rather than their specific class names or parent classes. This introduces polymorphism to exception handling, allowing the same exception to be caught in multiple ways. By targeting interfaces, developers gain flexibility in managing errors, especially when different exception types share common behavior defined by a shared interface.

See Also

PHP Features

Last updated: 11 October 2026