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

Op vr 20 feb 2026 om 08:43 schreef Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>:

On 19 February 2026 20:24:56 GMT, Mirco Babin <mirco.babin@gmail.com> wrote:

>The Php scripting aspect makes it difficult in general
>for untyped projects to fail early.

This is a completely irrelevant statement. We're not talking about
failing early "in general", we're talking about a compile-time check
that PHP already makes in other contexts.

Failing early is not always possible. The compiler can not always infere
the correct return type. E.g. this example will succeed to compile:

class UnableToInfereReturnType
{
    private function TruelyUnknown() : mixed
    {
        $a = time();
        if ($a % 2 === 0) {
            return ['important'];
        }
    }

    private function ActAsVoid() : void
    {
        return $this->TruelyUnknown();
    }

    // Lets act as if the return type is implicitly void
    public function __construct() // : void
    {
        return $this->ActAsVoid();
    }
}

$it = new UnableToInfereReturnType();

// Runtime error, not a compile error.
// Fatal error: A void method must not return a value in /in/MJCjX on line 15

Even in a project where each and every function has a return type
declaration, even all packages in the "vendor" directory have a return
type declaration, there are still cases where compilation will succeed
and a TypeError will be thrown at runtime.

In an untyped project compilation will *always* succeed, because
all return types are implicitly mixed. And the compiler can do nothing
with mixed return types. In an untyped project it will always be a
TypeError at runtime.

Fail early is very subjective.

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

The way I see it, we have to draw the line somewhere regarding what
use of "__construct" methods is "acceptable", and what use we're going
to force users to fix. There are three options on the table:

1) You may return any value, the "new" operator

     will silently discard it [current behaviour]

2) You must not return a value
3) You may return a value in the definition, but
   must make sure that no uses of the "new" operator
   reach that return statement

I find option 3 unnecessarily complicated. Faced with an existing code
base, checking and fixing violations of option 2 seems a lot easier
than keeping the return values but making sure they won't error under option 3.

I think that either we should add a simple, easily checked, rule - option 2;
or we should avoid breaking people's code, and retain option 1.

Thank you for your input so far. I have decided to move forward to the official
discussion phase.

Kind regards,
Mirco Babin

Hello Mirco,

On Fri, Feb 20, 2026, 11:48 PM Mirco Babin <mirco.babin@gmail.com> wrote:

Op vr 20 feb 2026 om 08:43 schreef Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>:

On 19 February 2026 20:24:56 GMT, Mirco Babin <mirco.babin@gmail.com> wrote:

The Php scripting aspect makes it difficult in general
for untyped projects to fail early.

This is a completely irrelevant statement. We’re not talking about
failing early “in general”, we’re talking about a compile-time check
that PHP already makes in other contexts.

Failing early is not always possible. The compiler can not always infere
the correct return type. E.g. this example will succeed to compile:

class UnableToInfereReturnType
{
private function TruelyUnknown() : mixed
{
$a = time();
if ($a % 2 === 0) {
return ['important'];
}
}

private function ActAsVoid() : void
{
return $this->TruelyUnknown();
}

// Lets act as if the return type is implicitly void
public function __construct() // : void
{
return $this->ActAsVoid();
}
}

$it = new UnableToInfereReturnType();

// Runtime error, not a compile error.
// Fatal error: A void method must not return a value in /in/MJCjX on line 15

Both can, and should, have errors before runtime. This is easily detected at compile time. Whether or not the return is used in a branch, it is seen before runtime, obviously.

In a context of __construct, disallow any return value can be detected as well. Any return statement with anything used with it can be rejected (and should).

It is documented as void, we could “fix” it with a bug fix.

Also, about this whole discussion , that __ construct, can still be called as a normal method. Some relics of php 4-5 migration path and then work around serialization or hydration challenges.

Most maintained projects out there moved away from these hacks and rely on new reliable solution for these challenges, like doctrine or symfony serializer.

If anything, this whole cleanup could be done right, disallow returning value and finally make __construct what it is designed and made for, a constructor. Not a normal method.

__
Pierre

@pierrejoye | http://www.libgd.org

On 20 February 2026 16:07:01 GMT, Mirco Babin <mirco.babin@gmail.com> wrote:

Failing early is not always possible. The compiler can not always infere
the correct return type.

PHP defines "void" separately from "null", precisely so that the compiler doesn't need to infer anything about the returned value. All it has to do is distinguish "return;" from "return some_expression;"

Example: Online PHP editor | output for cTaBP

Rowan Tommins
[IMSoP]

Op za 21 feb 2026 om 17:28 schreef Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>:

On 20 February 2026 16:07:01 GMT, Mirco Babin <mirco.babin@gmail.com> wrote:
>Failing early is not always possible. The compiler can not always infere
>the correct return type.

PHP defines "void" separately from "null", precisely so that the compiler
doesn't need to infer anything about the returned value. All it has to do
is distinguish "return;" from "return some_expression;"

Example: Online PHP editor | output for cTaBP

That is a very good find. Are the following claims correct?

1) The example given would imply that void is not a type.

<?php

function test(): void
{
    return NotLoaded::noIdeaWhatThisReturns(); // Compile error
}

echo 'At runtime'."\n";

// Fatal error: A void function must not return a value in ... on line 5

2) This example shows that a void function actually returns null.

<?php

function test(): void
{
    return;
}

echo 'At runtime'."\n";
var_dump(test());

// At runtime
// NULL

3) That would imply that the adjustment in this RFC can never be
   implemented. Because the comparison with void can never be made.
   It would have to check against null, and that would allow
   "return null;" as a valid case, which is unwanted.

    $__constructReturnValue = $newed->__construct(...$args);
    if ($__constructReturnValue !== void) {
        // PHP 8.6: Deprecated: Returning a value from the
        //          __construct()  constructor is deprecated.
        // PHP 9: throw new ConstructorError(
        //            'The __construct() constructor must not
        //             return a value.');
    }

4) That would also imply that the fail early of the implicit ": void"
   return type declaration is true for all cases, both typed and untyped.
   In all cases it would be a compile error, because the compiler only
   checks "return something;" vs. "return;".

   It would never throw a TypeError at runtime.

Kind regards,
Mirco Babin

On Mon, Feb 23, 2026, 4:23 PM Mirco Babin <mirco.babin@gmail.com> wrote:

Op za 21 feb 2026 om 17:28 schreef Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk>:

On 20 February 2026 16:07:01 GMT, Mirco Babin <mirco.babin@gmail.com> wrote:

Failing early is not always possible. The compiler can not always infere
the correct return type.

PHP defines “void” separately from “null”, precisely so that the compiler
doesn’t need to infer anything about the returned value. All it has to do
is distinguish “return;” from “return some_expression;”

Example: https://3v4l.org/cTaBP

That is a very good find. Are the following claims correct?

In all cases it would be a compile error, because the compiler only
checks “return something;” vs. “return;”.

It would never throw a TypeError at runtime.

that’s correct, as the return statement will have an expression, it will be detected.

absence of return is equivalent to return; as well.

you can see it using the ast extension, that may help you to see what can or cannot be check at compile vs runtime, I general. the engine does many compile time checks but not as much as a static analyzers or a compiled language can do, but things similar to this are cheap and usually done.

best,

Pierre

@pierrejoye | http://www.libgd.org

Hello Pierre,

Op vr 20 feb 2026 om 19:29 schreef Pierre Joye <pierre.php@gmail.com>:

Hello Mirco,

In a context of __construct, disallow any return value can be detected
as well. Any return statement with anything used with it can be
rejected (and should).

It is documented as void, we could "fix" it with a bug fix.

- PHP: Constructors and Destructors - Manual
  lists the __construct() function as void.
- But the implementation of __construct() is mixed.

Also, about this whole discussion , that __ construct, can still be
called as a normal method. Some relics of php 4-5 migration path and
then work around serialization or hydration challenges.

Most maintained projects out there moved away from these hacks and
rely on new reliable solution for these challenges, like doctrine
or symfony serializer.

I have found Migrating from PHP 4 to PHP 5.0.x.
But I didn't find any specific __construct() instructions, other than
Constructors and Destructors. At
that page, it is not mentioned the __construct() must not return a value.

Could you explain on serialization or hydration challenges in more
details? Perhaps with a theoretical example?
An (old) real-world example would be even better, if you can find one.

Kind regards,
Mirco Babin

hi Mirco,

On Mon, Feb 23, 2026, 11:00 PM Mirco Babin <mirco.babin@gmail.com> wrote:

Hello Pierre,

Op vr 20 feb 2026 om 19:29 schreef Pierre Joye <pierre.php@gmail.com>:

Hello Mirco,

In a context of __construct, disallow any return value can be detected
as well. Any return statement with anything used with it can be
rejected (and should).

It is documented as void, we could “fix” it with a bug fix.

l

see https://www.php.net/manual/en/language.oop5.decon.php

__construct(mixed ...$values = “”): void

I have found https://php-legacy-docs.zend.com/manual/php4/en/migration5.
But I didn’t find any specific __construct() instructions, other than
https://php-legacy-docs.zend.com/manual/php4/en/language.oop5.decon. At
that page, it is not mentioned the __construct() must not return a value.

it should I think, maybe it was not done for bc or avoid breaking codes, but it was years ago.

Could you explain on serialization or hydration challenges in more
details? Perhaps with a theoretical example?
An (old) real-world example would be even better, if you can find one.

Sorry, i do not have the time to look. Try to google a bit, quite a lot of old bugs in various projects about that.

Best,

The next step would be registering a Wiki account at
PHP: Register and then request RFC karma, so you
can create a proper RFC page and “officially” start the discussion.

Hello Tim Düsterhus,

I updated the RFC at

and changed the status to *rejected by the author*. This RFC is not
implementable; the why is explained in the updated RFC, first section.

Kind regards,
Mirco Babin

Hi Mirco

On 3/6/26 14:58, Mirco Babin wrote:

I updated the RFC at
PHP RFC - The constructor must not return a value · GitHub
and changed the status to *rejected by the author*. This RFC is not
implementable; the why is explained in the updated RFC, first section.

Thank you for the update. While I believe there still might be a misunderstanding, it wouldn't be fair to your time to push further.

Nevertheless I really appreciate that you took the time to write an RFC document and to participate on the discussion on the mailing list. Your RFC also provided some insightful examples that will come in handy for future reference.

I plan to pick this up based on my suggested implementation and have just added the proposal as a stub to the PHP 8.6 bulk deprecation RFC at

Best regards
Tim Düsterhus

On 7 March 2026 21:31:19 GMT, "Tim Düsterhus" <tim@bastelstu.be> wrote:

I plan to pick this up based on my suggested implementation and have just added the proposal as a stub to the PHP 8.6 bulk deprecation RFC at

PHP: rfc:deprecations_php_8_6

Hi Tim,

I appreciate this is mostly just a stub to revisit later, but for the record I think this should have its own RFC. There's a risk of these bulk RFCs becoming a way to bypass our normal process, since each section has much less detail than would be required for a standalone RFC.

In this case, you will be revisiting a topic where a previous RFC was rejected, so it's important that there's a clear rationale of why either this is a different proposal, or the previous arguments no longer apply.

Regards,

Rowan Tommins
[IMSoP]

Hi Tim, Rowan,

for that specific return value in constructor, as i mentioned earlier, I think it does not even require a rfc. Not even a depreciation addition but as a courtesy.

I did not check the git history but I am pretty sure it is a forgotten part when moving away from class name construct, and it is clearly documented as : void

https://www.php.net/manual/en/language.oop5.decon.php

this is a typical case the rfc about not overdoing rfcs or bc for bug fixes or similar.

best,

Pierre

@pierrejoye | http://www.libgd.org

On Sun, Mar 8, 2026, 10:13 PM Rowan Tommins [IMSoP] <imsop.php@rwec.co.uk> wrote:

On 7 March 2026 21:31:19 GMT, “Tim Düsterhus” <tim@bastelstu.be> wrote:

I plan to pick this up based on my suggested implementation and have just added the proposal as a stub to the PHP 8.6 bulk deprecation RFC at

https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_returning_a_value_from_construct

Hi Tim,

I appreciate this is mostly just a stub to revisit later, but for the record I think this should have its own RFC. There’s a risk of these bulk RFCs becoming a way to bypass our normal process, since each section has much less detail than would be required for a standalone RFC.

In this case, you will be revisiting a topic where a previous RFC was rejected, so it’s important that there’s a clear rationale of why either this is a different proposal, or the previous arguments no longer apply.

Regards,

Rowan Tommins
[IMSoP]

Hi

On 3/8/26 15:21, Rowan Tommins [IMSoP] wrote:

I appreciate this is mostly just a stub to revisit later, but for the record I think this should have its own RFC. There's a risk of these bulk RFCs becoming a way to bypass our normal process, since each section has much less detail than would be required for a standalone RFC.

Even for the bulk deprecation RFC I try to include as much justification and explanation as possible for the sections I add. This generally includes workarounds or possible polyfills. One such example would be my proposal to deprecate `uniqid()` in 8.4:

In this case, you will be revisiting a topic where a previous RFC was rejected, so it's important that there's a clear rationale of why either this is a different proposal, or the previous arguments no longer apply.

That's fair, I'll do this as a standalone RFC. Although I don't think there is much insightful discussion to be had here. Either folks are in favor or they are not, but discussion is unlikely to change anyone's opinion.

Best regards
Tim Düsterhus