[PHP-DEV] [IDEA for RFC] let the "new" operator fail when the __construct() function returns a value.

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

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.

// 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.

// 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,

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
( PHP: 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

On 2/2/26 12:00, Mirco Babin wrote:

# 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.

Can you elaborate on this? I'd like to better understand what the problem was with constructors that returned values when you upgraded 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.

So, to restate your proposal: you want to disallow returning from constructors…but only if invoked when using the `new` keyword?

So, if you call `$object->__construct()`, there's no error, and you can get the return value, but if you do `new Foo()`, you'd get an error?

And I've been told that the
rejected PHP RFC Make constructors and destructors return void
( PHP: 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'm not sure how this differs from the void return type proposal. When it throws a TypeError, it will effectively be saying, "this method can't return anything," which means the return type is `void`.

The "Make constructors and destructors return void" RFC was voted on and declined in 2020. PHP has come a long way since then, and I'm curious whether a similar proposal (targeting PHP 9) might pass nowadays. I'd certainly consider voting for it. Perhaps in PHP 8.6, we could begin emitting a deprecation message for constructors that return values?

Cheers,
Ben

Can you elaborate on this? I'd like to better understand what the
problem was with constructors that returned values when you upgraded
from Laravel 9 to Laravel 12.

I can't share real code, because this project is not open source. The
project I maintain uses constructor middleware as follows:

// Laravel 9
class MyController extends Controller
{
    public function __construct()
    {
        $this->middleware(function ($request, $next) {
            if (!Security::isLoggedIn()) {
                return redirect()->route('login');
            }

            return $next($request);
        });
    }
}

The goal is to redirect guests to the login page. To prevent
unauthorized actions from being performed.

In Laravel 9, MyController was instantiated before any middleware
was executed. When __construct was executed, Laravel was still
initializing, not much could be done in the __construct function.
Hence the middleware solution.

Then came Laravel 12 and constructor middleware was removed. Also,
middleware is now executed before the controller is instantiated. And
here's where I made my mistake. I removed the $this->middleware() call,
and changed this to:

// Laravel 12 - my mistake, this it totally wrong!
class MyController
{
    public function __construct()
    {
        if (!Security::isLoggedIn()) {
            return redirect()->route('login');
        }

        // Because the return value is pointless, no redirection
        // did find place.
        // The unauthorized user could actually call real controller
        // functions, which could be anything from  showHomepage() to
        // rebootTheSystem() - exaggerated of course.
        //
        // But PHP did not warn me, did not error, did not speak up,
        // did nothing to inform me of my mistake.
    }

    public function showHomepage(Request $request)
    {
    }

    public function rebootTheSystem(Request $request)
    {
    }
}

So, to restate your proposal: you want to disallow returning from
constructors…but only if invoked when using the `new` keyword?

That is correct.

So, if you call `$object->__construct()`, there's no error, and you can
get the return value, but if you do `new Foo()`, you'd get an error?

That is correct.

The "Make constructors and destructors return void" RFC was voted on and
declined in 2020. PHP has come a long way since then, and I'm curious
whether a similar proposal (targeting PHP 9) might pass nowadays. I'd
certainly consider voting for it. Perhaps in PHP 8.6, we could begin
emitting a deprecation message for constructors that return values?

Deprecation sounds like a good step forward.

Kind regards,
Mirco Babin

Hi

On 2/2/26 19:00, Mirco Babin wrote:

# 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
( PHP: 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.

Personally I find the fact that it's illegal to define a return type on `__construct()` to be a pretty good indication that the constructor is not meant to be able to return anything. As such I'm a little surprised by the disagreement in that RFC, though it should be noted that a majority was in favor (just not a 2/3's majority).

It would definitely make sense to me to revisit this topic and also disallow __construct() to be a Generator. That is a great find you made there!

I am very very reluctant to follow the RFC process myself:

I'd like to encourage you going through the RFC process. It is less complicated that it may seem at first and from my experience, the folks on this list are very supportive of new contributors.

* 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.

I'd say that the majority of folks on this list are not native speakers of English. You definitely don't need to have a perfect English vocabulary and grammar to be able to write an RFC. In fact using simple language is often preferable, since the purpose of an RFC is to accurately describe a technical topic to a wide audience with differing levels of English knowledge. You are not trying to write a novel that should sound nice.

For the topic at hand it's also a very straight-forward proposal that I don't think needs much explanation: Disallow returning values from `__construct()`. It's a good start to get your feet wet.

* 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.

You don't necessarily need to do the implementation (entirely) yourself. It definitely helps to have an implementation to figure out the edge cases, but as mentioned above, I don't think there are that many edge cases here.

I expect it to be in scope of the Foundation developers to create an implementation for an “outside RFC” that was accepted by the community. To me this sounds like a good use of the Foundation funds, but I can't speak for certain :slight_smile:

* 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 True Async RFC is a very complex RFC that touches many fundamental parts of PHP and thus has a “large scope” that needs to be taken into account. It is a quite different beast from what we are discussing here.

* 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.

I wouldn't say that the voting process is *highly* uncertain. For a majority of RFCs the results are quite clear (often unanimous). Rejection is always a possibility, but even then I wouldn't say it's a waste of time per se: The discussion will provide valuable insight for the greater community. In any case …

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

… the time investment would equally apply to anyone else writing an RFC on your behalf. In fact it would possibly be even larger, since they would have to become familiar with the issue first, whereas you already encountered in practice, so you already have an idea of what needs to change and *just* need to write it down.

----------

All that said: If you are willing prepare an initial RFC Draft based on the official RFC template (PHP: rfc:template) and to go through the process of officially discussion the RFC, I'm happy to do the “polishing” work as an official coauthor / mentor to make sure there are no missing bits or other mistakes. I'm also willing to get someone to do the implementation (potentially I'm doing it myself).

Do we have a deal? :slight_smile:

Best regards
Tim Düsterhus

Hi

On 2/5/26 18:40, Mirco Babin wrote:

So, if you call `$object->__construct()`, there's no error, and you can
get the return value, but if you do `new Foo()`, you'd get an error?

That is correct.

As mentioned in my other email, I believe it should just be an error either way.

In fact I would expect the result to be quite similar to how it already is a compile-time (!) error to return something from a void function:

     function foo(): void {
         return 1;
     }
     // Fatal error: A void function must not return a value

Best regards
Tim Düsterhus

FYI/Just in case anyone already wants to forbid this - there is a sniff available which will flag both constructors as well as destructors with a return type and/or when the function returns something: Universal.CodeAnalysis.ConstructorDestructorReturn Ref:

···

On 6-2-2026 20:59, Tim Düsterhus wrote:

On 2/2/26 19:00, Mirco Babin wrote:

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.

Personally I find the fact that it’s illegal to define a return type on __construct() to be a pretty good indication that the constructor is not meant to be able to return anything. As such I’m a little surprised by the disagreement in that RFC, though it should be noted that a majority was in favor (just not a 2/3’s majority).

https://github.com/PHPCSStandards/PHPCSExtra#universalcodeanalysisconstructordestructorreturn-wrench-books

FYI/Just in case anyone already wants to forbid this - there is a sniff
available which will flag both constructors as well as destructors with
a return type and/or when the function returns something:
Universal.CodeAnalysis.ConstructorDestructorReturn

Ref: GitHub - PHPCSStandards/PHPCSExtra: A collection of code standards for use with PHP_CodeSniffer

Thank you very much for this information. Using PHP Code Sniffer, we
identified two more return values from the __construct() functions.

Installing PHP Code Sniffer on Windows without using Composer is
cumbersome. For those who want to use the PHP Code Sniffer tool,
I've included installation instructions in the original issue, see:

Kind regards,
Mirco Babin

Op za 7 feb 2026 om 00:56 schreef Juliette Reinders Folmer
<php-internals_nospam@adviesenzo.nl>:

On 6-2-2026 20:59, Tim Düsterhus wrote:

On 2/2/26 19:00, Mirco Babin wrote:

# 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
( PHP: 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.

Personally I find the fact that it's illegal to define a return type on `__construct()` to be a pretty good indication that the constructor is not meant to be able to return anything. As such I'm a little surprised by the disagreement in that RFC, though it should be noted that a majority was in favor (just not a 2/3's majority).

FYI/Just in case anyone already wants to forbid this - there is a sniff available which will flag both constructors as well as destructors with a return type and/or when the function returns something: Universal.CodeAnalysis.ConstructorDestructorReturn

Ref: GitHub - PHPCSStandards/PHPCSExtra: A collection of code standards for use with PHP_CodeSniffer