Op do 19 feb 2026 om 13:54 schreef Rowan Tommins [IMSoP] <[email protected]>:

> On 18/02/2026 15:23, Mirco Babin wrote:
>>>
>>> In fact, the only way to guarantee a class is unaffected would be to
>>> write a method called "__construct", but then use static analysis to
>>> prove that nothing actually uses that method as a constructor.
>>
>> At the moment, without changing anything to Php, PHP Code Sniffer
>> static analysis is the only option. Would this RFC be accepted, there
>> are at least additional deprecation notices. But to be sure upfront,
>> you still would have to use PHP Code Sniffer.
>
>
> The deprecation notices will tell you about code that *is* affected,
> but they can't prove that code is *not* affected, if they only happen
> at run-time.

That is correct. I'm aware of that. Prove can be obtained by using
PHP Code Sniffer.

>>>
>>> In which
>>> case, why use the reserved name "__construct"?
>>
>> I'd go one step further. Why can "__construct" be called as a regular
>> function? Why not prohibit that and make it a truly magical function?
>
>
> That's a different kind of "why". I meant, "why would a programmer
> deliberately call a function __construct, and have no intention of
> *ever* using it as a constructor".

A theoretical example can be found in
"Unaffected calling the parent constructor example".
UnaffectedBaseClass could be abstract.

```php
class UnaffectedBaseClass
{
    public function __construct()
    {
        return ['important'];
    }
}

class UnaffectedCallParentConstructor extends UnaffectedBaseClass
{
    public function __construct()
    {
        $important = parent::__construct();
    }
}

$it = new UnaffectedCallParentConstructor();
```


> If they are using it as *both* a constructor and a normal method,
> *their code will break with your proposal* because at some point
> they will call it as a constructor and hit the new error.

Not necessarily. When an if is used, __construct() can be either.

```php
class UnaffectedConstructorAndRegularFunction
{
    public function __construct($input = null)
    {
        if ($input === null) return;

        return ['important', $input];
    }
}

$it = new UnaffectedConstructorAndRegularFunction();

$important = $it->__construct('more important');
```

>>> My suggestion is that classes which are affected should show an
>>> error *as soon as possible*, so that users are prompted to fix
>>> the definition.
>>
>> That's a personal preference.
>>
>
> Yes and no. Detecting problems early is widely recognised as reducing
> the cost and risks of a project. See for instance discussion around
> the term "shift left".

The RFC prioritizes minimal BC impact. That won't change. If someone
really wants early detection, that someone should be programming in C#
and not in PHP. The Php scripting aspect makes it difficult in general
for untyped projects to fail early. The same applies to spaghetti
projects.

>>> Once I'm told about the problem, I can choose between two things:
>>>
>>> 1) Stop the constructor returning a value, e.g. by replacing "return
>>> foo();" with "foo(); return;"
>>> 2) Rename the method to a non-reserved name, and if needed add a new
>>> constructor which calls the method and discards the result
>>
>> There is a 3rd option:
>>
>> if ($calledAsRegularFunction_and_not_as_constructor) {
>>     return foo();
>> }
>
>
> Can you point to any real-life example of this?

No, it is theoretical.

>
> It feels like a lot of this discussion of BC breaks is assuming
> that there's some common code pattern relying on the current
> behaviour, but the only examples I've seen so far are of why
> it would be *wrong* to return a value.

I have given 2 theoretical examples:
- UnaffectedConstructorAndRegularFunction
- UnaffectedCallParentConstructor

Has anyone ever used it? Probably, considering PHP's age and how
long it's been possible to abuse the __construct() function.

>> There are two conflicting goals in the prologue: "Maximum warning"
>> and "Minimal BC impact". You prioritize Maximum warning. I prioritize
>> Minimal BC impact.
>
> I disagree that your proposal has a lower BC impact.
>
> The majority of code that currently relies on a constructor returning
> a value *will break under both versions of the proposal*.

Not true, see the 2 supplied examples. The "void" variant would break,
my variant would run unaffected.

> The only difference that delaying the error to run-time makes, is that
> it will take longer for people to notice that it is broken.

Not necessarily true. Assume the code is running fine on a shared hosting
platform with Php 8.5. Then the hosting provider decides to force an
update to Php 9, because of a critical vulnerability. That same code
would then break at some point. Depending on the structure of the project,
the amount of routes handled in one Controller class, and where the
mistake is programmed, the whole website will break, or only parts of it.

The "void" variant breaks immediately when the source is compiled. My
variant only breaks when the pattern is detected and leaves other parts
untouched. In this production example, a break that always occurs is
actually annoying. The more parts can continue working uninterrupted,
the more time a developer has to fix the problem.

>> Your points are correct, from the perspective of a new PHP project.
>> However, reasoning from the perspective of a very old PHP project,
>> every BC break is one too many.
>
> It is exactly those old PHP projects that I'm worried about. The ones
> that have poor testing coverage, large amounts of spaghetti code, and
> will cause some poor engineer to have a 2am alarm because someone the
> other side of the world just crashed the application with a run-time
> error.

I assume the mistake I made doesn't happen often. If it did, I wouldn't be
working on this RFC now, but a mechanism would already be active in PHP.

I assume the spaghetti contains some version of my two theoretical
examples.

So it is safer to *not* make the return type declaration implicitly "void".

Kind regards,
Mirco Babin

Reply via email to