Subject: [IDEA for RFC] let the "new" operator fail when the
__construct() function returns a value.
Date: 2 february 2026, 19:00h (Europe/Amsterdam)

Hello Internals,

The starting point of this idea is
https://github.com/php/php-src/issues/21090

By the way, I urge everyone to try out "yield 1" from the BC impact chapter.
That really demonstrates that the current "new" methodology is pointless.

# Problem

The "new" operator internally calls the __construct() function. However,
the return value of the __construct() function is silently lost. This is
very error-prone; I personally made a major security mistake with this when
upgrading from Laravel 9 to Laravel 12.

To prevent future problems, my proposal is to have the "new" operator
explicitly issue warnings, and later have it explicitly abort when the
__construct() function returns a value.

A high-level overview of the "new" operator's operations. I haven't explored
this in detail; it's about the overall picture.

```php
// High level picture of the "new" operator.
$newed = acquire_memory_and_initialize_object()
if (function_exists($newed, '__construct')) {
    $newed->__construct(...);
    // NOTICE that a return value from the __construct() function is silently
    //        discarded.
}
return $newed;
```

# My proposal

Modify the "new" operator to the following. This will prevent a return value
from being silently lost. Instead, a warning will be issued and later the
"new" operator will throw a TypeError.

```php
// High level picture of the adjusted "new" operator.
$newed = acquire_memory_and_initialize_object()
if (function_exists($newed, '__construct')) {
    $__constructReturnValue = $newed->__construct(...);
    if ($__constructReturnValue !== void) {
        // DEPRECATION: returning a value from the __construct() function
        //              when called by the "new" operator is deprecated.
        // LATER: throw new TypeError('the __construct() function must not
        //        return a value when called by the "new" operator.');
    }
}
return $newed;
```

# BC impact

Yes, there can be BC impact. If the __construct() function is also used as
a regular function. For example,

```php
class SomeTheoreticalExample
{
    public $uniqueId;

    public function __construct(bool $returnSomeValue)
    {
        static $uniqueId = 0;
        $this->uniqueId = $uniqueId;
        $uniqueId++;

        if ($returnSomeValue) {
            // return some pointless value. Pointless, because it is
            // silently discarded by the "new" operator.

            return 'abc';
            // return 1;
            // return 1.23;
            // return null;
            // return true;
            // return false;
            // return ['a', 'b', 'c'];
            // return [6 => 'six', 7 => 'seven', 67 => 'six seven'];
            // return ['a' => 'a', 'b' => 'b', 'c' => 'c', 0 => 'Zero'];

            // return SomeEnum::Spades;

            // return function() {};
            // return fn($a) => $a;

            // return new DateTimeImmutable();
            // return fopen('php://memory', 'w+');

            // return $this;
            // return &$this;
            // return new SomeTheoreticalExample(false);

            // Laravel 12 controller specific, of course this is very
            // very wrong. It will NEVER redirect, and the flow
            // continues to reach the (unauthorized) controller function.
            // return redirect()->route('login');

            // This is a very terrible case. Try it out yourself and
            // watch the returned $newed->uniqueId.
            // Spoiler alert: it won't be 0.
            // yield 1;
        }
    }
}

// Before the RFC TypeError fact: nothing.
// After the RFC fact: will fail.
$newed = new SomeTheoreticalExample(true);

// Before the RFC TypeError fact: nothing.
// After the RFC fact: never called, because of the previous statement failure.
$someReasonToCall__ConstructAgain = $newed->__construct(true);
```

# RFC

I've been asked to follow the RFC process. And I've been told that the
rejected PHP RFC Make constructors and destructors return void
( https://wiki.php.net/rfc/make_ctor_ret_void ) rejects my proposal.
However, my proposal is explicitly about the "new" operator and not
about return types. It does, however, concern return values.

I am very very reluctant to follow the RFC process myself:
* It would take up a lot of my free time. I would write the RFC first in
  Dutch, my native language, and then translate it into English. This is
  because I speak and write English, but don't understand all the nuances
  of (American/Australian/British/Canadian/Irish/Nigerian/...) English.
* While I do have some knowledge of C and the php-src codebase, writing a
  PR to implement the RFC would take up a significant amount of my
  free time.
* The True Async RFC has demonstrated that the author of an RFC must have
  a certain degree of trustworthiness. And I, as a passerby, have not
  established any trustworthiness.
* The voting process of an RFC is highly uncertain and can lead to
  rejection. The whole process could ultimately be a complete waste
  of my time.

Anyone who would like to create an RFC for this, please go ahead.

Yours sincerely,
Mirco Babin

Reply via email to