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.
// 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
( 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
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
( 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'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 isvoid.
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 thenewkeyword?
That is correct.
So, if you call
$object->__construct(), there's no error, and you can
get the return value, but if you donew 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
So, if you call
$object->__construct(), there's no error, and you can
get the return value, but if you donew 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
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?
As written to Tim Düsterhus: I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
Kind regards,
Mirco Babin
Op wo 4 feb 2026 om 03:30 schreef Ben Ramsey ramsey@php.net:
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 thenewkeyword?So, if you call
$object->__construct(), there's no error, and you can
get the return value, but if you donew Foo(), you'd get an error?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'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 isvoid.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
Hi
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).
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 :-)
- 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 (https://wiki.php.net/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? :-)
Best regards
Tim Düsterhus
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).
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
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
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:
https://github.com/php/php-src/issues/21090#issuecomment-3870404274
Kind regards,
Mirco Babin
Op za 7 feb 2026 om 00:56 schreef Juliette Reinders Folmer
php-internals_nospam@adviesenzo.nl:
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).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
Hello Tim Düsterhus,
… 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.
This argument has convinced me. And writing it down helps clear my mind.
All that said: If you are willing prepare an initial RFC Draft based on
the official RFC template (https://wiki.php.net/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? :-)
I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
Are you still willing to carry out the implementation? If so:
- Should I mention you as coauthor?
- In the first chapter, what wording should "Implementation" be?
- What wording should "Patches and Tests" be?
Kind regards,
Mirco Babin
Hi
Am 2026-02-14 15:00, schrieb Mirco Babin:
This argument has convinced me. And writing it down helps clear my
mind.
Great, thank you!
All that said: If you are willing prepare an initial RFC Draft based
on
the official RFC template (https://wiki.php.net/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? :-)
I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93Are you still willing to carry out the implementation? If so:
Yes, I'm still willing to help out with the implementation (or to find
someone to to the implementation if the RFC passes).
- Should I mention you as coauthor?
For the “only warn on new at runtime” variant, I personally don't agree
with the proposal and thus don't want to officially co-author it. But
I'm nevertheless willing to help with the implementation and with
feedback to ensure that the RFC is in the best possible shape
policy-wise. As previously mentioned, I believe it should consistently
warn at compile-time and not make a difference between “using new” and
“calling __construct() manually”.
- In the first chapter, what wording should "Implementation" be?
- What wording should "Patches and Tests" be?
It's okay to leave that out for now in both cases. An implementation is
not necessary to vote on the RFC and it can be filled in later.
The next step would be registering a Wiki account at
https://wiki.php.net/rfc?do=register and then request RFC karma, so you
can create a proper RFC page and “officially” start the discussion.
Having the page will also make the RFC text much more convenient to read
compared to the Gist.
I've taken a quick look at the text and “format-wise” it seems to
contain all the necessary information. Great work!
Best regards
Tim Düsterhus
Op vr 20 feb 2026 om 12:35 schreef Tim Düsterhus tim@bastelstu.be:
Hi
Am 2026-02-14 15:00, schrieb Mirco Babin:
This argument has convinced me. And writing it down helps clear my
mind.Great, thank you!
All that said: If you are willing prepare an initial RFC Draft based
on
the official RFC template (https://wiki.php.net/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? :-)
I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93Are you still willing to carry out the implementation? If so:
Yes, I'm still willing to help out with the implementation (or to find
someone to to the implementation if the RFC passes).
- Should I mention you as coauthor?
For the “only warn on new at runtime” variant, I personally don't agree
with the proposal and thus don't want to officially co-author it. But
I'm nevertheless willing to help with the implementation and with
feedback to ensure that the RFC is in the best possible shape
policy-wise. As previously mentioned, I believe it should consistently
warn at compile-time and not make a difference between “using new” and
“calling __construct() manually”.
- In the first chapter, what wording should "Implementation" be?
- What wording should "Patches and Tests" be?
It's okay to leave that out for now in both cases. An implementation is
not necessary to vote on the RFC and it can be filled in later.
Thank you for helping out.
The next step would be registering a Wiki account at
https://wiki.php.net/rfc?do=register and then request RFC karma, so you
can create a proper RFC page and “officially” start the discussion.
Ok, I have done that.
Having the page will also make the RFC text much more convenient to read
compared to the Gist.I've taken a quick look at the text and “format-wise” it seems to
contain all the necessary information. Great work!
FYI: I used the following procedure for previewing:
- Goto https://www.dokuwiki.org/changes (or some other page that allows editing)
- Choose for the pencil: edit this page
- Paste in the gist
- Preview
Kind regards,
Mirco Babin
The next step would be registering a Wiki account at
https://wiki.php.net/rfc?do=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
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
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
I updated the RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
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
https://wiki.php.net/rfc/deprecations_php_8_6#deprecate_returning_a_value_from_construct
Best regards
Tim Düsterhus
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 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:
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 athttps://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
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:
https://wiki.php.net/rfc/deprecations_php_8_4#deprecate_uniqid
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
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
Hi Miro, and welcome!
I agree that allowing constructors to return a value leads to confusion,
and would support deprecating and then forbidding this.
However, I agree with others that this should be checked when compiling
the constructor, not when running it under the "new" operator. In other
words, make any method named "__construct" give the same error as a
function or method marked ": void".
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.
According to the current policy, it is allowed to "resurrect" a failed
proposal after just 6 months:
https://github.com/php/policies/blob/main/feature-proposals.rst#resurrecting-rejected-proposals
That seems very short to me, but in this case it has been nearly 6 years.
I would also note that the RFC only narrowly failed - it received 60%
Yes votes, but failed to meet the threshold of two-thirds.
You can see the discussion and voting threads for that previous RFC
here: https://externals.io/message/110612 and
https://externals.io/message/110824
It looks like there was a lot of discussion around the exact timeline,
and whether to allow an explicit ": void". A new RFC could focus on
only the "void-like behaviour", and set out a clear timeline. In other
words:
- In PHP 8.next, raise a Deprecation at compile-time if a method named
"__construct" returns a value - In PHP 9.0, throw an Error instead
I am very very reluctant to follow the RFC process myself
I would like to echo everything Tim said on this, and encourage you to
start the RFC.
If you look down the list of accepted RFCs at https://wiki.php.net/rfc
you will see that some are long discussions of complex features; but
others are just a few paragraphs. The way you laid out your e-mail
already has most of what's needed: state the Problem, detail the
Proposal, and assess the Impact.
Regards,
--
Rowan Tommins
[IMSoP]
Hi Mirco, and welcome!
I agree that allowing constructors to return a value leads to confusion,
and would support deprecating and then forbidding this.However, I agree with others that this should be checked when compiling
the constructor, not when running it under the "new" operator. In other
words, make any method named "__construct" give >the same error as a
function or method marked ": void".
Hello Rowan Tommins,
I disagree with the compiling part. The __construct() function can
currently not have a return type, so the return type is implicitly
"mixed". Changing that would be a BC break for those calling the
__construct() function as a regular function.
As written to Tim Düsterhus: I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
Kind regards,
Mirco Babin
Hi Mirco,
Am 17. Februar 2026 07:59:43 MEZ schrieb Mirco Babin mirco.babin@gmail.com:
Hi Mirco, and welcome!
I agree that allowing constructors to return a value leads to confusion,
and would support deprecating and then forbidding this.However, I agree with others that this should be checked when compiling
the constructor, not when running it under the "new" operator. In other
words, make any method named "__construct" give >the same error as a
function or method marked ": void".Hello Rowan Tommins,
I disagree with the compiling part. The __construct() function can
currently not have a return type, so the return type is implicitly
"mixed". Changing that would be a BC break for those calling the
__construct() function as a regular function.As written to Tim Düsterhus: I have prepared a RFC at
https://gist.github.com/MircoBabin/aaa574297c8d1baa879f19c99ce28e93
I'm with Roman here as well.
Yes, it's a BC break, but as Roman suggested, there would be a deprecation phase before and forbidden only in PHP 9. This should give enough time to adopt.
Also, you are talking about the constructor only, which is ok, but I believe the destructor needs to be discussed as well and mentioned in the RFC.
Kind regards,
Mirco Babin
Regards,
Marc
Hello Marc,
Op di 17 feb 2026 om 08:40 schreef Marc B. marc@mabe.berlin:
I'm with Roman here as well.
Yes, it's a BC break, but as Roman suggested, there would be a
deprecation phase before and forbidden only in PHP 9. This should give
enough time to adopt.
This RFC explicitly wants to reduce the BC impact to a minimum as stated
in the prologue. Changing the return type declaration implicitly to void
breaks that goal. The "Unaffected calling the parent constructor example"
would become affected by "void" return type.
If someone wants to follow the "void" path after this RFC, that's still
possible. For the new "void" RFC it doesn't matter whether this RFC gets
accepted or rejected.
Also, you are talking about the constructor only, which is ok, but I
believe the destructor needs to be discussed as well and mentioned in
the RFC.
I have mentioned the __destruct() destructor in the Rejected Features
chapter. Because:
- The details of the destructor are completely different; it is called if
the zval reference count reaches 0 and is called by the garbage collector. - Also the destructor does not have security-sensitive impact when returning
values. - Also, the use of destructors is very unusual; I never use them.
- This requires a separate RFC.
Kind regards,
Mirco Babin
I disagree with the compiling part. The __construct() function can
currently not have a return type, so the return type is implicitly
"mixed". Changing that would be a BC break for those calling the
__construct() function as a regular function.
It's a BC break either way - which is fine, if it only happens in PHP 9.0.
Consider this example in your draft RFC:
class UnaffectedBaseClass
{
public function __construct()
{
return ['important'];
}
}
Right now, there is nothing stopping someone calling that constructor as
new UnaffectedBaseClass. In PHP 9.0, that working code will become an
error - a BC break.
This is true even if the class is marked abstract:
abstract class AbstractBaseClass
{
public function __construct()
{
return ['important'];
}
}
class ValidSubClass extends AbstractBaseClass
{
}
$it = new ValidSubClass(); // Error in PHP 9.0
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. In which
case, why use the reserved name "__construct"?
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.
In the example above, I would much prefer to see the error (or
deprecation notice) as soon as AbstractBaseClass is compiled, rather
than waiting for something in the code base to call "new ValidSubClass".
Similarly, if there's a long constructor with a return statement inside
some rarely used conditional logic, I would prefer to be told
immediately that there is a mistake, rather than having a production
error when the rare situation happens.
Once I'm told about the problem, I can choose between two things:
- Stop the constructor returning a value, e.g. by replacing "return
foo();" with "foo(); return;" - Rename the method to a non-reserved name, and if needed add a new
constructor which calls the method and discards the result
Thinking that through, having an error thrown only when calling with
new would actually be more disruptive. I would probably vote "No" to
that version of the proposal.
Regards,
--
Rowan Tommins
[IMSoP]
Op di 17 feb 2026 om 20:26 schreef Rowan Tommins [IMSoP] imsop.php@rwec.co.uk:
I disagree with the compiling part. The __construct() function can
currently not have a return type, so the return type is implicitly
"mixed". Changing that would be a BC break for those calling the
__construct() function as a regular function.It's a BC break either way - which is fine, if it only happens in PHP 9.0.
Consider this example in your draft RFC:
class UnaffectedBaseClass { public function __construct() { return ['important']; } }Right now, there is nothing stopping someone calling that constructor as
new UnaffectedBaseClass. In PHP 9.0, that working code will become an
error - a BC break.This is true even if the class is marked
abstract:abstract class AbstractBaseClass { public function __construct() { return ['important']; } } class ValidSubClass extends AbstractBaseClass { } $it = new ValidSubClass(); // Error in PHP 9.0
I have added this example to the RFC.
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.
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?
FYI: I have added "Ban __construct() as a regular function call" to the
rejected features section.
Banning the __construct() as regular function call, except when called
as "parent::__construct()", is a rejected feature for this RFC.
- Only the "new" keyword and "parent::__construct()" would then be allowed
to call the %%__construct()%% constructor. - The __construct() would only serve as constructor. It would become truly
a magic function. - Lazy ghost and ReflectionClass::newInstanceWithoutConstructor() would be
a challenge. - This would go hand in hand with a implicit change to "void" return type
declaration. - The goal of this RFC is minimal BC impact. This would have major impact.
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.In the example above, I would much prefer to see the error (or
deprecation notice) as soon as AbstractBaseClass is compiled, rather
than waiting for something in the code base to call "new ValidSubClass".
That's a personal preference. Sometimes I want compilation errors right
away, but often I just want it to work. And if one web page crashes
because of an incorrect "return," I'd rather fix it on the fly than get
stuck with all sorts of compilation errors that I'm forced to resolve.
Because the other webpages then do work, it is just 1 that is broken.
It's about the obligation to fix it upfront versus fixing it when it
goes wrong. Fixing it upfront is a black/white scenario, either it works
or it fails. Fixing it when it goes wrong is a gray scenario, it works
until it breaks.
Similarly, if there's a long constructor with a return statement inside
some rarely used conditional logic, I would prefer to be told
immediately that there is a mistake, rather than having a production
error when the rare situation happens.
Normally, there's a test phase in between. There's always a chance that
the test phase will already detect the faulty web page.
Once I'm told about the problem, I can choose between two things:
- Stop the constructor returning a value, e.g. by replacing "return
foo();" with "foo(); return;"- 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();
}
Thinking that through, having an error thrown only when calling with
newwould actually be more disruptive. I would probably vote "No" to
that version of the proposal.
FYI: I have added "Changing to void return type declaration" to the
rejected features section. There already was "aborting during
compilation" in the rejected feature section.
Conclusion:
There are two conflicting goals in the prologue: "Maximum warning"
and "Minimal BC impact". You prioritize Maximum warning. I prioritize
Minimal BC impact.
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 works now, so why should something
be changed? This old project has no security issues whatsoever. E.g.
because it is an internal project, a cli tool, something that is
not public.
FYI: I updated the prologue.
"This RFC prioritizes minimal BC impact above all else."
Kind regards,
Mirco Babin
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.
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".
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.
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".
Once I'm told about the problem, I can choose between two things:
- Stop the constructor returning a value, e.g. by replacing "return
foo();" with "foo(); return;"- 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?
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.
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. 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.
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.
--
Rowan Tommins
[IMSoP]
Op do 19 feb 2026 om 13:54 schreef Rowan Tommins [IMSoP] imsop.php@rwec.co.uk:
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.
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.
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:
- Stop the constructor returning a value, e.g. by replacing "return
foo();" with "foo(); return;"- Rename the method to a non-reserved name, and if needed add a new
constructor which calls the method and discards the resultThere 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
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.class UnaffectedBaseClass { public function __construct() { return ['important']; } } class UnaffectedCallParentConstructor extends UnaffectedBaseClass { public function __construct() { $important = parent::__construct(); } } $it = new UnaffectedCallParentConstructor();
In that example, the method on UnaffectedBaseClass can not be used as a constructor under your proposal. As such, it's just a normal method which can be trivially renamed:
class UnaffectedBaseClass
{
public function initialise()
{
return ['important'];
}
}
class UnaffectedCallParentConstructor extends UnaffectedBaseClass
{
public function __construct()
{
$important = parent::initialise();
}
}
$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.
True. I'm not convinced the language should bend over backwards to support such a pattern, though.
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.
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.
I don't see how this contradicts what I said. Instead of it failing immediately, it fails "at some point" - so, it will take longer to happen. You consider that a good thing, I consider it a bad thing; I guess we're not going to convince each other on that.
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:
- You may return any value, the "new" operator will silently discard it [current behaviour]
- You must not return a value
- 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.
Regards,
Rowan Tommins
[IMSoP]
Op vr 20 feb 2026 om 08:43 schreef Rowan Tommins [IMSoP] imsop.php@rwec.co.uk:
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:
- You may return any value, the "new" operator
will silently discard it [current behaviour]- You must not return a value
- You may return a value in the definition, but
must make sure that no uses of the "new" operator
reach that return statementI 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,
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 <some value> 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
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.
- https://www.php.net/manual/en/language.oop5.decon.php#object.construct
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 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.
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,
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.
- https://www.php.net/manual/en/language.oop5.decon.php#object.construct
lists the __construct() function as void.- But the implementation of __construct() is mixed.
l
see https://www.php.net/manual/en/language.oop5.decon.php
__construct(mixed https://www.php.net/manual/en/language.types.mixed.php
...$values = ""): void
https://www.php.net/manual/en/language.types.void.php
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,
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
Rowan Tommins
[IMSoP]
Op za 21 feb 2026 om 17:28 schreef Rowan Tommins [IMSoP] imsop.php@rwec.co.uk:
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?
- 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
- 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`
- 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.');
}
-
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
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