phpFashion

Rubrika PHP

Property Hooks in PHP 8.4: Game Changer or Hidden Trap?

What if I told you your PHP objects could be cleaner, more elegant, and easier to work with? Well, that dream is now a reality! PHP 8.4 introduces revolutionary features called property hooks and asymmetric visibility that completely transform object-oriented programming as we know it. Say goodbye to clunky getters and setters – we now have a modern, intuitive way to control object data access. Let's explore how these features can revolutionize your code.

Property hooks provide a smart way to define what happens when you read from or write to object properties – and they're much cleaner and more efficient than the traditional magic methods __get/__set. Think of it as getting all the power of magic methods without any of their usual drawbacks.

Let's look at a real-world example that shows why property hooks are so valuable. Consider a common Person class with a public age property:

class Person
{
	public int $age = 0;
}

$person = new Person;
$person->age = 25;  // OK
$person->age = -5;  // OK, but that makes no sense!

While PHP ensures the age will be an integer thanks to the int type (available since PHP 7.4), what about that negative age? In the past, we'd need getters and setters, make the property private, and write a bunch of boilerplate code. With hooks, there's a much more elegant solution:

class Person
{
	public int $age = 0 {
		set => $value >= 0 ? $value : throw new InvalidArgumentException;
	}
}

$person->age = -5;  // Oops! InvalidArgumentException warns us about the invalid value

The beauty lies in its simplicity – from the outside, the property behaves exactly like before. You can read and write directly through $person->age, but now you have complete control over what happens during the write operation. And that's just scratching the surface!

We can take it further and create hooks for reading too. Hooks can have attributes, and they can contain complex logic beyond simple expressions. Check out this example of working with names:

class Person
{
	public string $first;
	public string $last;
	public string $fullName {
		get {
			return "$this->first $this->last";
		}
		set(string $value) {
			[$this->first, $this->last] = explode(' ', $value, 2);
		}
	}
}

$person = new Person;
$person->fullName = 'James Bond';
echo $person->first;  // outputs 'James'
echo $person->last;   // outputs 'Bond'

Here's something crucial to understand: hooks are always used whenever a property is accessed (even within the Person class itself). The only exception is when you directly access the actual variable inside the hook code.

A Blast from the Past: Lessons from SmartObject

For those familiar with Nette Framework, here's an interesting historical perspective. The framework offered similar functionality 17 years ago through SmartObject, which significantly enhanced object handling at a time when PHP was quite limited in this area.

I remember the initial wave of overwhelming enthusiasm where developers used properties everywhere, followed by a complete reversal where they avoided them entirely. Why? There weren't clear guidelines about when to use methods versus properties. But today's native solution is in a different league altogether. Property hooks and asymmetric visibility are fully-fledged tools that provide the same level of control as methods. This makes it much easier to determine when a property is truly the right choice.

…pokračování


The Hidden Surprises of PHP Readonly Properties

Picture this: data that's as stable as bedrock – set it once, and it stays that way forever. That's exactly what PHP 8.1 delivered with readonly properties. Think of it as giving your objects a safety vault – keeping their data secure from accidental changes. Let's explore how this powerful feature can streamline your code and what gotchas you need to watch out for.

Here's a quick taste of what we're talking about:

class User
{
    public readonly string $name;

    public function setName(string $name): void
    {
        $this->name = $name;  // First assignment - all OK
    }
}

$user = new User;
$user->setName('John');      // Great, name is set
echo $user->name;            // "John"
$user->setName('Jane');      // BOOM! Exception: Cannot modify readonly property

Once that name is set, it's locked in place. No accidental changes, no sneaky updates.

When is uninitialized really uninitialized?

Here's a common misconception: many developers think readonly properties must be set in the constructor. But PHP is actually much more flexible than that – you can set them at any point in an object's lifecycle, with one crucial rule: only once! Before that first assignment, they exist in a special ‘uninitialized’ state – think of it as a blank slate waiting for its first and only value.

Here's an interesting twist – readonly properties can't have default values. Why? Think about it: if they had default values, they'd essentially be constants – set at object creation and unchangeable from that point on.

Types are mandatory

When using readonly properties, you must explicitly declare their type. This isn't just PHP being picky – the ‘uninitialized’ state only works with typed variables. No type declaration means no readonly variable. Don't know the exact type? No worries – you can always fall back on mixed.

…pokračování


How to Handle Getters When They Have Nothing to Return?

Software development often presents dilemmas, such as how to handle situations when a getter has nothing to return. In this article, we'll explore three strategies for implementing getters in PHP, which affect the structure and readability of code, each with its own specific advantages and disadvantages. Let's take a closer look.

Universal Getter with a Parameter

The first solution, used in Nette, is to create a single getter method that can either return null or throw an exception if the value is not available, depending on a boolean parameter. Here is an example of what the method might look like:

public function getFoo(bool $need = true): ?Foo
{
    if (!$this->foo && $need) {
        throw new Exception("Foo not available");
    }
    return $this->foo;
}

The main advantage of this approach is that it eliminates the need to have several versions of the getter for different use cases. A former disadvantage was the poor readability of user code using boolean parameters, but this has been resolved with the introduction of named parameters, allowing you to write getFoo(need: false).

However, this approach may cause complications in static analysis, as the signature implies that getFoo() can return null under any circumstances. Tools like PHPStan allow explicit documentation of method behavior through special annotations, improving code understanding and its correct analysis:

/** @return ($need is true ? Foo : ?Foo) */
public function getFoo(bool $need = true): ?Foo
{
}

This annotation clearly defines what return types the method getFoo() can generate depending on the value of the parameter $need. However, for instance, PhpStorm does not understand it.

Pair of Methods: hasFoo() and getFoo()

Another option is to divide the responsibility into two methods: hasFoo() to verify the existence of the value and getFoo() to retrieve it. This approach enhances code clarity and is intuitively understandable.

public function hasFoo(): bool
{
    return (bool) $this->foo;
}

public function getFoo(): Foo
{
    return $this->foo ?? throw new Exception("Foo not available");
}

The main problem is redundancy, especially in cases where the availability check itself is a complex process. If hasFoo() performs complex operations to verify if the value is available, and then this value is retrieved again using getFoo(), these operations are repeated. Hypothetically, the state of the object or data might change between the calls to hasFoo() and getFoo(), leading to inconsistencies. From a user's perspective, this approach may be less convenient as it forces calling a pair of methods with repeating parameters. It also prevents the use of the null-coalescing operator.

The advantage is that some static analysis tools allow defining a rule that after a successful call to hasFoo(), no exception will be thrown in getFoo().

Methods getFoo() and getFooOrNull()

The third strategy is to split the functionality into two methods: getFoo() to throw an exception if the value does not exist, and getFooOrNull() to return null. This approach minimizes redundancy and simplifies logic.

public function getFoo(): Foo
{
    return $this->getFooOrNull() ?? throw new Exception("Foo not available");
}

public function getFooOrNull(): ?Foo
{
    return $this->foo;
}

An alternative could be a pair getFoo() and getFooIfExists(), but in this case, it might not be entirely intuitive to understand which method throws an exception and which returns null. A slightly more concise pair would be getFooOrThrow() and getFoo(). Another possibility is getFoo() and tryGetFoo().

Each of these approaches to implementing getters in PHP has its place depending on the specific needs of the project and the preferences of the development team. When choosing a suitable strategy, it's important to consider the impact on readability, maintenance, and performance of the application. The choice should reflect an effort to make the code as understandable and efficient as possible.


First Steps in OOP in PHP: Essentials You Need to Know

Are you looking to dive into the world of Object-Oriented Programming in PHP but don't know where to start? I have for you a new concise guide to OOP that will introduce you to all the concepts like class, extends, private, etc.

In this guide, you will learn about:

This guide is not intended to make you a master of writing clean code or to provide exhaustive information. Its goal is to quickly familiarize you with the basic concepts of OOP in current PHP and to give you factually correct information. Thus, it provides a solid foundation on which you can further build, such as applications in Nette.

As further reading, I recommend the detailed guide to proper code design. It is beneficial even for those who are proficient in PHP and object-oriented programming.


Compilation errors in PHP: why are they still a problem?

Programming in PHP has always been a bit of a challenge, but fortunately, it has undergone many changes for the better. Do you remember the times before PHP 7, when almost every error meant a fatal error, instantly terminating the application? In practice, this meant that any error could completely stop the application without giving the programmer a chance to catch it and respond appropriately. Tools like Tracy used magical tricks to visualize and log such errors. Fortunately, with the arrival of PHP 7, this changed. Errors now throw exceptions like Error, TypeError, and ParseError, which can be easily caught and handled.

However, even in modern PHP, there is a weak spot where it behaves the same as in its fifth version. I am talking about errors during compilation. These cannot be caught and immediately lead to the termination of the application. They are E_COMPILE_ERROR level errors. PHP generates around two hundred of them. It creates a paradoxical situation where loading a file with a syntax error in PHP, such as a missing semicolon, throws a catchable ParseError exception. However, if the code is syntactically correct but contains a compilation-detectable error (like two methods with the same name), it results in a fatal error that cannot be caught.

try {
    require 'path_to_file.php';
} catch (ParseError $e) {
    echo "Syntactic error in PHP file";
}

Unfortunately, we cannot internally verify compilation errors in PHP. There was a function php_check_syntax(), which, despite its name, detected compilation errors as well. It was introduced in PHP 5.0.0 but quickly removed in version 5.0.4 and has never been replaced since. To verify the correctness of the code, we must rely on a command-line linter:

php -l file.php

From the PHP environment, you can verify code stored in the variable $code like this:

$code = '... PHP code to verify ...';
$process = proc_open(
    PHP_BINARY . ' -l',
    [['pipe', 'r'], ['pipe', 'w'], ['pipe', 'w']],
    $pipes,
    null,
    null,
    ['bypass_shell' => true],
);
fwrite($pipes[0], $code);
fclose($pipes[0]);
$error = stream_get_contents($pipes[1]);
if (proc_close($process) !== 0) {
    echo 'Error in PHP file: ' . $error;
}

However, the overhead of running an external PHP process to verify one file is quite large. But good news comes with PHP version 8.3, which will allow verifying multiple files at once:

php -l file1.php file2.php file3.php

Why is the operator ?? sheer misfortune?

PHP users have been waiting for the ?? operator for an incredibly long time, perhaps ten years. Today, I regret that it took longer.

  • Wait, what? Ten years? You're exaggerating, aren't you?
  • Really. Discussion started in 2004 under the name “ifsetor”. And it didn't make it into PHP until December 2015 in version 7.0. So almost 12 years.
  • Aha! Oh, man.

It's a pity we didn't wait longer. Because it doesn't fit into the current PHP.

PHP has made an incredible shift towards strictness since 7.0. Key moments:

The ?? operator simplified the annoying:

isset($somethingI[$haveToWriteTwice]) ? $somethingI[$haveToWriteTwice] : 'default value'

to just:

$write[$once] ?? 'default value'

But it did this at a time when the need to use isset() has greatly diminished. Today, we more often assume that the data we access exists. And if they don't exist, we damn well want to know about it.

But the ?? operator has the side effect of being able to detect null. Which is also the most common reason to use it:

$len = $this->length ?? 'default value'

Unfortunately, it also hides errors. It hides typos:

// always returns 'default value', do you know why?
$len = $this->lenght ?? 'default value'

In short, we got ?? at the exact moment when, on the contrary, we would most need to shorten this:

`php
$somethingI[$haveToWriteTwice] === null
? ‘default value’
: $somethingI[$haveToWriteTwice]
`

It would be wonderful if PHP 9.0 had the courage to modify the behavior of the ?? operator to be a bit more strict. Make the “isset operator” really a “null coalesce operator”, as it is officially called by the way.

PHPStan and checkDynamicProperties: true helps you to detect typos suppressed by the ?? operator.


Are You Just Following a Cargo Cult?

Many years ago, I realized that when I used a variable containing a predefined data table in a PHP function, the array had to be “recreated” each time the function was called, which was surprisingly slow. For example:

function isSpecialName(string $name): bool
{
    $specialNames = ['foo' => 1, 'bar' => 1, 'baz' => 1, ...];
    return isset($specialNames[$name]);
}

Then I discovered a simple trick that prevented the array from being recreated. It was enough to define the variable as static:

function isSpecialName(string $name): bool
{
    static $specialNames = ['foo' => 1, 'bar' => 1, 'baz' => 1, ...];
    return isset($specialNames[$name]);
}

The speed-up, if the array was a bit larger, was several orders of magnitude (like 500×).

Since then, I have always used static for constant arrays. It's possible that others followed this habit without knowing the real reason behind it, but I can't be sure.


A few weeks ago, I wrote a class that held large tables of predefined data in several properties. I realized that this would slow down the creation of instances, meaning the new operator would “recreate” the arrays each time, which is slow as we know. Therefore, I had to change the properties to static, or perhaps even better, use constants.

Then I asked myself: Hey, are you just following a cargo cult? Is it still true that without static it is slow?

It's hard to say, PHP has undergone revolutionary development and old truths may no longer be valid. I prepared a test sample and did a few measurements. Of course, I confirmed that in PHP 5, using static inside a function or with properties significantly sped things up by several orders of magnitude. However, note that in PHP 7.0, it was only by one order of magnitude. Excellent, a sign of optimizations in the new core, but the difference is still substantial. Yet, with further PHP versions, the difference continued to decrease and eventually nearly disappeared.

I even found that using static inside a function in PHP 7.1 and 7.2 actually slowed down the execution by about 1.5–2×, which in terms of the orders of magnitude we are discussing, is negligible, but it was an interesting paradox. From PHP 7.3, the difference disappeared completely.

Habits are a good thing, but it is necessary to validate their meaning continuously.


I will no longer use unnecessary static within function bodies. However, for that class holding large tables of predefined data in properties, I thought it was programmatically correct to use constants. Soon, I had the refactoring done, but even as it was being created, I lamented how ugly the code was becoming. Instead of $this->ruleToNonTerminal or $this->actionLength, the code now contained the screaming $this::RULE_TO_NON_TERMINAL and $this::ACTION_LENGTH, which looked really ugly. A stale whiff from the seventies.

I even hesitated, wondering if I even wanted to look at such ugly code, and whether I might prefer to stick with variables, or static variables.

And then it hit me: Hey, are you just following a cargo cult?

Of course, I am. Why should a constant shout? Why should it draw attention to itself in the code, be a protruding element in the flow of the program? The fact that the structure is read-only is not a reason FOR STUCK CAPSLOCK, AGGRESSIVE TONE, AND WORSE READABILITY.

THE TRADITION OF UPPERCASE LETTERS COMES FROM THE C LANGUAGE, WHERE MACRO CONSTANTS FOR THE PREPROCESSOR WERE MARKED IN THIS WAY. IT WAS USEFUL TO UNMISTAKABLY DISTINGUISH CODE FOR THE PARSER FROM CODE FOR THE PREPROCESSOR. IN PHP, NO PREPROCESSORS WERE EVER USED, SO THERE IS NO REASON to write constants in uppercase letters.

That very evening, I removed them everywhere. And still couldn't understand why it hadn't occurred to me twenty years ago. The bigger the nonsense, the tougher its roots.


Should nullable types be written with or without a question mark?

I've always been bothered by any redundancy or duplication in code. I wrote about it many years ago. Looking at this code just makes me suffer:

interface ContainerAwareInterface
{
    /**
     * Sets the container.
     */
    public function setContainer(ContainerInterface $container = null);
}

Let's set aside the unnecessary commentary on the method for now. And this time also the misunderstanding of dependency injection, if a library needs such an interface. The fact that using the word Interface in the name of an interface is, in turn, a sign of not understanding object-oriented programming, I'm planning a separate article on that. After all, I've been there myself.

But why on earth specify the visibility as public? It's a pleonasm. If it wasn't public, then it wouldn't be an interface, right? And then someone thought to make it a “standard” ?‍♂️

Sorry for the long introduction, what I'm getting to is whether to write optional nullable types with or without a question mark. So:

// without
function setContainer(ContainerInterface $container = null);
// with
function setContainer(?ContainerInterface $container = null);

Personally, I have always leaned towards the first option, because the information given by the question mark is redundant (yes, both notations mean the same from the language's perspective). This is how all the code was written until the arrival of PHP 7.1, the version that added the question mark, and there would have to be a good reason to change it suddenly.

With the arrival of PHP 8.0, I changed my mind and I'll explain why. The question mark is not optional in the case of properties. PHP will throw an error in this case:

class Foo
{
	private Bar $foo = null;
}
// Fatal error: Default value for property of type Bar may not be null.
// Use the nullable type ?Bar to allow null default value

And from PHP 8.0 you can use promoted properties, which allows you to write code like this:

class Foo
{
	public function __construct(
		private ?Bar $foo = null,
		string $name = null,
	) {
		// ...
	}
}

Here you can see the inconsistency. If ?Bar is used (which is necessary), then ?string should follow on the next line. And if I use the question mark in some cases, I should use it in all cases.

The question remains whether it is better to use a union type string|null instead of a question mark. For example, if I wanted to write Stringable|string|null, maybe the version with a question mark isn't at all necessary.

Update: It looks like PHP 8.4 will require the notation with a question mark.


How Shutdown and Destructor Calls Occur in PHP

The shutdown process in PHP consists of the following steps performed in the given order:

  1. Calling all functions registered using register_shutdown_function()
  2. Calling all __destruct() methods
  3. Emptying all output buffers
  4. Terminating all PHP extensions (e.g., sessions)
  5. Shutting down the output layer (sending HTTP headers, cleaning output handlers, etc.)

Let's focus more closely on step 2, the calling of destructors. It's important to note that even in the first step, when registered shutdown functions are called, object destruction can occur. For example, if one of the functions held the last reference to an object or if the shutdown function itself was an object.

Destructor calls proceed as follows:

  1. PHP first attempts to destroy objects in the global symbol table.
  2. Then it calls the destructors of all remaining objects.
  3. If execution is halted, e.g., due to exit(), the remaining destructors are not called.

ad 1) PHP iterates through the global symbol table in reverse order, starting with the most recently created variable and proceeding to the first created variable. During this iteration, it destroys all objects with a reference count of 1. This iteration continues as long as such objects exist.

Basically, it does the following: a) removes all unused objects in the global symbol table, b) if new unused objects appear, removes them as well, and c) continues this process. This method of destruction is used so that objects can depend on other objects in their destructor. This usually works well if objects in the global scope don't have complicated (e.g., circular) mutual dependencies.

Destruction of the global symbol table is significantly different from the destruction of other symbol tables. For the global symbol table, PHP uses a smarter algorithm that tries to respect object dependencies.

ad 2) Other objects are processed in the order they were created, and their destructors are called. Yes, PHP merely calls __destruct, but it doesn't actually destroy the object (nor does it even change its reference count). If other objects still refer to it, the object will remain available (even though its destructor has already been called). In a sense, they will be using a “half-destroyed” object.

ad 3) If execution is halted during the calling of destructors, e.g., due to exit(), the remaining destructors are not called. Instead, PHP marks the objects as already destroyed. The important consequence is that destructor calls are not guaranteed. While such cases are relatively rare, they can happen.

Source: https://stackoverflow.com/…ucted-in-php


How to write error handler in PHP?

When writing your own error handler for PHP, it is absolutely necessary to follow several rules. Otherwise, it can disrupt the behavior of other libraries and applications that do not expect treachery in the error handler.

Parameters

The signature of the handler looks like this:

function errorHandler(
    int $severity,
    string $message,
    string $file,
    int $line,
    array $context = null // only in PHP < 8
): ?bool {
    ...
}

The $severity parameter contains the error level (E_NOTICE, E_WARNING, …). Fatal errors such as E_ERROR cannot be caught by the handler, so this parameter will never have these values. Fortunately, fatal errors have essentially disappeared from PHP and have been replaced by exceptions.

The $message parameter is the error message. If the html_errors directive is enabled, special characters like < are written as HTML entities, so you need to decode them back to plain text. However, beware, some characters are not written as entities, which is a bug. Displaying errors in pure PHP is thus prone to XSS.

The $file and $line parameters represent the name of the file and the line where the error occurred. If the error occurred inside eval(), $file will be supplemented with this information.

Finally, the $context parameter contains an array of local variables, which is useful for debugging, but this has been removed in PHP 8. If the handler is to work in PHP 8, omit this parameter or give it a default value.

Return Value

The return value of the handler can be null or false. If the handler returns null, nothing happens. If it returns false, the standard PHP handler is also called. Depending on the PHP configuration, this can print or log the error. Importantly, it also fills in internal information about the last error, which is accessible by the error_get_last() function.

Suppressed Errors

In PHP, error display can be suppressed either using the shut-up operator @ or by error_reporting():

// suppress E_USER_DEPRECATED level errors
error_reporting(~E_USER_DEPRECATED);

// suppress all errors when calling fopen()
$file = @fopen($name, 'r');

Even when errors are suppressed, the handler is still called. Therefore, it is first necessary to verify whether the error is suppressed, and if so, we must end our own handler:

if (!($severity & error_reporting())) {
    return false;
}

However, in this case, we must end it with return false, so that the standard error handler is still executed. It will not print or log anything (because the error is suppressed), but ensures that the error can be detected using error_get_last().

Other Errors

If our handler processes the error (for example, displays its own message, etc.), there is no need to call the standard handler. Although then it will not be possible to detect the error using error_get_last(), this does not matter in practice, as this function is mainly used in combination with the shut-up operator.

If, on the other hand, the handler does not process the error for any reason, it should return false so as not to conceal it.

Example

Here's what the code for a custom error handler that transforms errors into ErrorException exceptions might look like:

set_error_handler(function (int $severity, string $message, string $file, int $line) {
    if (!(error_reporting() & $severity)) {
        return false;
    }

    throw new \ErrorException($message, 0, $severity, $file, $line);
});

phpFashion © 2004, 2025 David Grudl | o blogu

Ukázky zdrojových kódů smíte používat s uvedením autora a URL tohoto webu bez dalších omezení.