Dear internals,
I would like to propose 2 RFCs:
- Disallow explicit call of __construct method
- Polymorphic dispatch
I'm sure I'm not the first who came with those 2 ideas so in case those were already proposed and rejected just let me know.
Otherwise please bless me with mana which will allow me to submit them.
Regards,
Tim
Am 12.01.2017 um 08:11 schrieb Tim Bezhashvyly:
Disallow explicit call of __construct method
I am baffled that this (still) works:
$ cat /tmp/t.php
<?php
class C
{
public function __construct()
{
print METHOD . PHP_EOL;
}
}
$o = new C;
$o->__construct();
$ php /tmp/t.php
C::__construct
C::__construct
$ php --version
PHP 7.1.0 (cli) (built: Dec 1 2016 07:39:00) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend Technologies
I am in favor of an RFC to disallow explicit invocation of interceptor
methods such as __construct().
Can we do this in PHP 7.2 or does this have to wait for PHP 8?
Polymorphic dispatch
You mean dynamic dispatch of polymorphic methods, right?
Am 12.01.2017 um 08:11 schrieb Tim Bezhashvyly:
Disallow explicit call of __construct method
I am baffled that this (still) works:
$ cat /tmp/t.php
<?php
class C
{
public function __construct()
{
print METHOD . PHP_EOL;
}
}$o = new C;
$o->__construct();$ php /tmp/t.php
C::__construct
C::__construct$ php --version
PHP 7.1.0 (cli) (built: Dec 1 2016 07:39:00) ( NTS )
Copyright (c) 1997-2016 The PHP Group
Zend Engine v3.1.0-dev, Copyright (c) 1998-2016 Zend TechnologiesI am in favor of an RFC to disallow explicit invocation of interceptor
methods such as __construct().Can we do this in PHP 7.2 or does this have to wait for PHP 8?
https://github.com/php/php-langspec/blob/master/spec/14-classes.md#constructors
Constructors are called by object-creation-expression and from within
other (derived class) constructors.
This suggests that creation is possible via the new
keyword only and
not via explicit calls to __construct
. Hence, it could be categorized
as unspecified and dropped in 7.2.
However, there are other oddities that probably should be addressed too
in the context of constructors which are actually defined and thus can
only be removed in PHP 8.
A constructor can return a result, by value or byRef.
A constructor should not call its base-class constructor more than
once.
<?php
// test.php
class A {
function __construct() {
echo __CLASS__ , "\n";
}
}
class B extends A {
function __construct() {
parent::__construct();
echo __CLASS__ , "\n";
parent::__construct();
return "PHP\n";
}
}
$b = new B;
echo $b->__construct();
$ php test.php
A
B
A
A
B
A
PHP
Note that both PHP and HHVM implement this correctly:
--
Richard "Fleshgrinder" Fussenegger
I am baffled that this (still) works:
__clone was special cased in 5.x, but special case was removed in 7.0
means that previously you couldn't do publicly $obj->__clone(); now you can
and, yes, it's as dangerous as it looks
however, the special case was removed for a reason that i'm sure is a good
one, and i'm certain the same good reason is also valid for __construct
the only reason for prohibiting explicit __construct calls is that it makes
PHP objects mutable
then don't call it explicitly, exactly like you wouldn't use reflection to
call private methods
I am baffled that this (still) works:
__clone was special cased in 5.x, but special case was removed in 7.0
means that previously you couldn't do publicly $obj->__clone(); now you can
and, yes, it's as dangerous as it looks
however, the special case was removed for a reason that i'm sure is a good
one, and i'm certain the same good reason is also valid for __construct
What is the reason?
the only reason for prohibiting explicit __construct calls is that it makes
PHP objects mutablethen don't call it explicitly, exactly like you wouldn't use reflection to
call private methods
This is one possible answer to this issue. The question is whether PHP
wants to protect developers from errors and help reduce bugs by
enforcing things or wants to be a purely convention based language and
leave it developers to use it correctly.
I am completely in favor of #1 and you seem to be in favor of #2.
I actually have the impression that it is not defined which of both PHP
actually is or wants to be. The introduction of scalar type hints in PHP
7 definitely points towards #1 but it could just be an outlier.
--
Richard "Fleshgrinder" Fussenegger
Hi All.
Am 14.01.17 um 11:03 schrieb Fleshgrinder:
[…]
the only reason for prohibiting explicit __construct calls is that it makes
PHP objects mutablethen don't call it explicitly, exactly like you wouldn't use reflection to
call private methodsThis is one possible answer to this issue. The question is whether PHP
wants to protect developers from errors and help reduce bugs by
enforcing things or wants to be a purely convention based language and
leave it developers to use it correctly.I am completely in favor of #1 and you seem to be in favor of #2.
I actually have the impression that it is not defined which of both PHP
actually is or wants to be. The introduction of scalar type hints in PHP
7 definitely points towards #1 but it could just be an outlier.
Do we as "makers of PHP" want to dictate the user what the "intended"
behaviour of PHP is? Or are we treating the user as a responsible person
that knows what to do and also not to do?
Personally I'm in favour of the later. Let the user be able to call
__construct whenever they like. It's a special method which is clearly
visible by the two underscores at the start. So the users either know
exactly what they are doing or they need to learn it.
Translated to a different example this discussion is like: "Let's remove
the handle from the window because someone might be able to open the
window and jump out of it". The handle is there for a valid reason. And
as a window-maker I have no idea in what way it might be used. But I
have to accept that there are people missusing it. But should I limit
all those users with a valid use-case for it just to save some from
missusing it? And if we start with that, where should we stop?
Just my 0.02€
Cheers
Andreas
--
,,,
(o o)
+---------------------------------------------------------ooO-(_)-Ooo-+
| Andreas Heigl |
| mailto:andreas@heigl.org N 50°22'59.5" E 08°23'58" |
| http://andreas.heigl.org http://hei.gl/wiFKy7 |
+---------------------------------------------------------------------+
| http://hei.gl/root-ca |
+---------------------------------------------------------------------+
Do we as "makers of PHP" want to dictate the user what the "intended"
behaviour of PHP is? Or are we treating the user as a responsible person
that knows what to do and also not to do?Personally I'm in favour of the later. Let the user be able to call
__construct whenever they like. It's a special method which is clearly
visible by the two underscores at the start. So the users either know
exactly what they are doing or they need to learn it.Translated to a different example this discussion is like: "Let's remove
the handle from the window because someone might be able to open the
window and jump out of it". The handle is there for a valid reason. And
as a window-maker I have no idea in what way it might be used. But I
have to accept that there are people missusing it. But should I limit
all those users with a valid use-case for it just to save some from
missusing it? And if we start with that, where should we stop?Just my 0.02€
Cheers
Andreas
Yes, that is exactly the question. This needs to be voted on. Rust would
be an example that protects you and C/C++ would be examples that do not.
Both approaches are valid approaches.
Note that the dictation will not always work, this is why e.g. Rust has
unsafe blocks. The corresponding thing in PHP imho would be reflection.
Hence, if someone has a special case:
- Rust: use unsafe
- PHP: use reflection
--
Richard "Fleshgrinder" Fussenegger
Am 14.01.2017 um 11:26 schrieb Fleshgrinder php@fleshgrinder.com:
Do we as "makers of PHP" want to dictate the user what the "intended"
behaviour of PHP is? Or are we treating the user as a responsible person
that knows what to do and also not to do?Personally I'm in favour of the later. Let the user be able to call
__construct whenever they like. It's a special method which is clearly
visible by the two underscores at the start. So the users either know
exactly what they are doing or they need to learn it.Translated to a different example this discussion is like: "Let's remove
the handle from the window because someone might be able to open the
window and jump out of it". The handle is there for a valid reason. And
as a window-maker I have no idea in what way it might be used. But I
have to accept that there are people missusing it. But should I limit
all those users with a valid use-case for it just to save some from
missusing it? And if we start with that, where should we stop?Just my 0.02€
Cheers
Andreas
Yes, that is exactly the question. This needs to be voted on. Rust would
be an example that protects you and C/C++ would be examples that do not.
Both approaches are valid approaches.Note that the dictation will not always work, this is why e.g. Rust has
unsafe blocks. The corresponding thing in PHP imho would be reflection.
Hence, if someone has a special case:
- Rust: use unsafe
- PHP: use reflection
--
Richard "Fleshgrinder" Fussenegger
IMHO it already IS safe.
In PHP 4 the "constructor" was a method thad had the same name as the class. But now it is a class that has always the same name and is always preceded with two underscores. There is no way to "accidentaly" call that method. You have to explicitly call "__construct" to "break" things.
If developers "want" to break something they will find a way. Protecting "__construct" will not stop them from doing so. But it will stop the rest of us do legitimate things.
Cheers
Andreas
I'm surprised no one is raising a strong argument about the
"parent::__construct()" call, so I'll do it.
I never liked typing "parent::__construct()" to call parent's class
constructor, but now that this proposal is being discussed (and might even
be accepted) I would like to throw some more meat to the fire:
<?php
class C {
function __construct($a) {
print "C::__construct() a=$a\n";
}
}
class D extends C {
function __construct($a, $b) {
parent::__construct($a, $b); // how it is now
// parent($a); // how i would like it
print "D::__construct() a=$a b=$b\n";
}
}
$d = new D(10, 20);
If you go for blocking explicit calls to __construct() (i'm personally in
favour of it), i hope you would change the syntax for its only legitimate
use which is calling the parent's constructor within a constructor, so how
about something like parent(...)?
It would be ugly to say "You are not supposed to explicit call
__construct() ever" and then "You know what, to initialize parent class
you need to call __construct()".
The construct method is called automagically when you instantiate the
class, I don't type something like $d = new D; $d->__construct(10, 20); so
I don't see why I should type that method name inside my derived
constructor. I just need a special syntax to do so.
And while I'm at it (a bit offtopic but related to my previous argument)
how about not requiring __construct() to be defined in the parent class?
I.e. when you call parent::__construct() if there is NO constructor defined
in the parent class just do nothing instead of going fatal error. I
always wanted this feature, because when you do "new C;" it doesn't go
fatal error if there is no __construct() defined. I don't think this is
breaking covariance if you assume that every class defines an empty
__construct() with no argument. I mean, if you later add an explicit
constructor to the parent class it must be without arguments unless you
accept you might break something.
Thanks!
Am 14.01.2017 um 11:26 schrieb Fleshgrinder php@fleshgrinder.com:
Do we as "makers of PHP" want to dictate the user what the "intended"
behaviour of PHP is? Or are we treating the user as a responsible person
that knows what to do and also not to do?Personally I'm in favour of the later. Let the user be able to call
__construct whenever they like. It's a special method which is clearly
visible by the two underscores at the start. So the users either know
exactly what they are doing or they need to learn it.Translated to a different example this discussion is like: "Let's remove
the handle from the window because someone might be able to open the
window and jump out of it". The handle is there for a valid reason. And
as a window-maker I have no idea in what way it might be used. But I
have to accept that there are people missusing it. But should I limit
all those users with a valid use-case for it just to save some from
missusing it? And if we start with that, where should we stop?Just my 0.02€
Cheers
Andreas
Yes, that is exactly the question. This needs to be voted on. Rust would
be an example that protects you and C/C++ would be examples that do not.
Both approaches are valid approaches.Note that the dictation will not always work, this is why e.g. Rust has
unsafe blocks. The corresponding thing in PHP imho would be reflection.
Hence, if someone has a special case:
- Rust: use unsafe
- PHP: use reflection
--
Richard "Fleshgrinder" FusseneggerIMHO it already IS safe.
In PHP 4 the "constructor" was a method thad had the same name as the
class. But now it is a class that has always the same name and is always
preceded with two underscores. There is no way to "accidentaly" call that
method. You have to explicitly call "__construct" to "break" things.If developers "want" to break something they will find a way. Protecting
"__construct" will not stop them from doing so. But it will stop the rest
of us do legitimate things.Cheers
Andreas
--
--
Giovanni Giacobbi
2017-01-14 15:58 GMT+01:00 Giovanni Giacobbi giovanni@giacobbi.net:
I'm surprised no one is raising a strong argument about the
"parent::__construct()" call, so I'll do it.I never liked typing "parent::__construct()" to call parent's class
constructor, but now that this proposal is being discussed (and might even
be accepted) I would like to throw some more meat to the fire:<?php
class C {
function __construct($a) {
print "C::__construct() a=$a\n";
}
}
class D extends C {
function __construct($a, $b) {
parent::__construct($a, $b); // how it is now
// parent($a); // how i would like it
print "D::__construct() a=$a b=$b\n";
}
}
$d = new D(10, 20);If you go for blocking explicit calls to __construct() (i'm personally in
favour of it), i hope you would change the syntax for its only legitimate
use which is calling the parent's constructor within a constructor, so how
about something like parent(...)?
AFAIK parent is a speciual scope which help refers to variables and
functions in base classe
http://php.net/manual/kr/keyword.parent.php
So it looks like proposing your syntax could lead to huge BC break.
In other languages (eg. Java) there is special syntax quite similar with
super($a, $b)
call.
It would be ugly to say "You are not supposed to explicit call
__construct() ever" and then "You know what, to initialize parent class
you need to call __construct()".The construct method is called automagically when you instantiate the
class, I don't type something like $d = new D; $d->__construct(10, 20); so
I don't see why I should type that method name inside my derived
constructor. I just need a special syntax to do so.And while I'm at it (a bit offtopic but related to my previous argument)
how about not requiring __construct() to be defined in the parent class?
I.e. when you call parent::__construct() if there is NO constructor defined
in the parent class just do nothing instead of going fatal error. I
always wanted this feature, because when you do "new C;" it doesn't go
fatal error if there is no __construct() defined. I don't think this is
breaking covariance if you assume that every class defines an empty
__construct() with no argument. I mean, if you later add an explicit
constructor to the parent class it must be without arguments unless you
accept you might break something.Thanks!
Am 14.01.2017 um 11:26 schrieb Fleshgrinder php@fleshgrinder.com:
Do we as "makers of PHP" want to dictate the user what the "intended"
behaviour of PHP is? Or are we treating the user as a responsible
person
that knows what to do and also not to do?Personally I'm in favour of the later. Let the user be able to call
__construct whenever they like. It's a special method which is clearly
visible by the two underscores at the start. So the users either know
exactly what they are doing or they need to learn it.Translated to a different example this discussion is like: "Let's
remove
the handle from the window because someone might be able to open the
window and jump out of it". The handle is there for a valid reason.
And
as a window-maker I have no idea in what way it might be used. But I
have to accept that there are people missusing it. But should I limit
all those users with a valid use-case for it just to save some from
missusing it? And if we start with that, where should we stop?Just my 0.02€
Cheers
Andreas
Yes, that is exactly the question. This needs to be voted on. Rust
would
be an example that protects you and C/C++ would be examples that do
not.
Both approaches are valid approaches.Note that the dictation will not always work, this is why e.g. Rust has
unsafe blocks. The corresponding thing in PHP imho would be reflection.
Hence, if someone has a special case:
- Rust: use unsafe
- PHP: use reflection
--
Richard "Fleshgrinder" FusseneggerIMHO it already IS safe.
In PHP 4 the "constructor" was a method thad had the same name as the
class. But now it is a class that has always the same name and is always
preceded with two underscores. There is no way to "accidentaly" call that
method. You have to explicitly call "__construct" to "break" things.If developers "want" to break something they will find a way. Protecting
"__construct" will not stop them from doing so. But it will stop the rest
of us do legitimate things.Cheers
Andreas
--
--
Giovanni Giacobbi
--
regards / pozdrawiam,
Michał Brzuchalski
about.me/brzuchal
brzuchalski.com
AFAIK parent is a speciual scope which help refers to variables and
functions in base classe
http://php.net/manual/kr/keyword.parent.php
So it looks like proposing your syntax could lead to huge BC break.
In other languages (eg. Java) there is special syntax quite similar with
super($a, $b)
call.
super
is Java’s parent
keyword.
There is definitely no breaking change here by enabling parent()
as an
alternative to parent::__construct()
since it has no functionality at
all right now. As a matter of fact, PHP currently aborts with a fatal error:
Fatal error: Call to undefined function parent() in
Reversing keywords in the global scope is done regularly in PHP minor
versions, hence, that would not be a problem either.
I see only one single problem, users might expect that parent()
actually should call parent::__invoke()
. However, that is not the case
right now and nobody ever asked for it plus it is a little used feature
anyways which does not require special treatment (imho).
Regarding the fatal error on a missing constructor implementation in the
parent. I fully agree that this should be possible.
--
Richard "Fleshgrinder" Fussenegger
2017-01-14 19:24 GMT+01:00 Fleshgrinder php@fleshgrinder.com:
AFAIK parent is a speciual scope which help refers to variables and
functions in base classe
http://php.net/manual/kr/keyword.parent.php
So it looks like proposing your syntax could lead to huge BC break.
In other languages (eg. Java) there is special syntax quite similar with
super($a, $b)
call.
super
is Java’sparent
keyword.There is definitely no breaking change here by enabling
parent()
as an
alternative toparent::__construct()
since it has no functionality at
all right now. As a matter of fact, PHP currently aborts with a fatal
error:Fatal error: Call to undefined function parent() in
Reversing keywords in the global scope is done regularly in PHP minor
versions, hence, that would not be a problem either.I see only one single problem, users might expect that
parent()
actually should callparent::__invoke()
. However, that is not the case
right now and nobody ever asked for it plus it is a little used feature
anyways which does not require special treatment (imho).
There might be different parent scope calls not only to magic method but
also all base class methods.
Regarding the fatal error on a missing constructor implementation in the
parent. I fully agree that this should be possible.--
Richard "Fleshgrinder" Fussenegger
--
regards / pozdrawiam,
Michał Brzuchalski
about.me/brzuchal
brzuchalski.com
you guys went slightly off topic :P
You are right, I feel responsible for it (but proudly), as an excuse please
consider that the three topics covered have a certain dependancy:
Forbid __construct calls -> Forbid in constructor as well for coherency ->
need a new way to call parent's constructor
(bonus topic: implicit constructors)
It would be a pity if only the first part made it into the core, and I
personally like it plus I always wanted a better way to invoke parent's
constructor, so I threw it in the basket, but you are right, it is quite
off-topic. I'll check out the guide on how to submit RFCs so you guys can
fry me in another thread :P
you guys went slightly off topic :P
--
Giovanni Giacobbi
2017-01-15 12:41 GMT+01:00 Giovanni Giacobbi giovanni@giacobbi.net:
You are right, I feel responsible for it (but proudly), as an excuse please
consider that the three topics covered have a certain dependancy:Forbid __construct calls -> Forbid in constructor as well for coherency ->
need a new way to call parent's constructor
(bonus topic: implicit constructors)
I've talked with Fleshgrinder aside and I convinced using parent($a, $b)
as call for parent constructor fully satisfies me. Without any change to
parent::
scope resolution it's quite similar to Java's super
which is also used as
parent
constructor and scope resolution.
So I'm +1 for parent
It would be a pity if only the first part made it into the core, and I
personally like it plus I always wanted a better way to invoke parent's
constructor, so I threw it in the basket, but you are right, it is quite
off-topic. I'll check out the guide on how to submit RFCs so you guys can
fry me in another thread :Pyou guys went slightly off topic :P
--
Giovanni Giacobbi
--
regards / pozdrawiam,
Michał Brzuchalski
about.me/brzuchal
brzuchalski.com
If you go for blocking explicit calls to __construct() (i'm personally in
favour of it), i hope you would change the syntax for its only legitimate
use which is calling the parent's constructor within a constructor, so how
about something like parent(...)?
It would be ugly to say "You are not supposed to explicit call
__construct() ever" and then "You know what, to initialize parent class
you need to call __construct()".
This is actually a very good point: if you ban calls to __construct, you
need syntax for not only calling the parent constructor, but other
constructors in the chain. For instance [https://3v4l.org/jTsek]:
<?php
class A
{
public function __construct() {
echo 'A';
}
}
class B extends A
{
public function __construct() {
parent::__construct();
echo 'B';
}
}
class C extends B
{
public function __construct() {
// Skip parent constructor, but call grand-parent
A::__construct();
echo 'C';
}
}
new C;
Probably you could say "cannot call __construct directly, except in the
__construct of a descendant class", but that all seems a bit fiddly.
I'm on the -1 side on this proposal, because the logical conclusion
would be that all magic methods should be banned: __clone was already
mentioned, but should you really be calling $foo->__get($bar) directly?
If so, why is that more legitimate than $foo->__construct? And if not,
do you allow parent::__get($bar), or do you insist on that using
different syntax as well? (If you think the answers are easy for __get,
pick a different magic method and ask the same questions).
These exceptions and special cases are going to proliferate, and all to
stop a hypothetical "misuse" of the language. Do you have an example of
a bug you've seen where a user acting reasonably would have been saved
by this new prohibition?
Regards,
--
Rowan Collins
[IMSoP]
We discussed this whole thing in different channels and together refined
the original idea as well as what the actual problem and a solution
would be.
The actual problem are multiple calls to the constructor because they
break the encapsulation. We know that there are many ways to do so but
this is a very weird one.
Hence, the proposal will be to disallow multiple constructor calls.
All examples that were given so far would continue to work. However, the
weird examples that were given at the beginning will stop working and
result in an error.
--
Richard "Fleshgrinder" Fussenegger
Heya,
While I agree that it is weird to be able to call constructors more than
once, this is generally used for:
- lazy loading
- resource reset
Specifically, what is going on is something like following:<?php
final class DbConnection
{
private $dsn;
private $initializer;
public function __construct(string $dsn)
{
$this->dsn = $dsn;
// socket stuff happens here, much like with PDO
}
public function query(string $queryString) : array
{
($this->initializer)();
// irrelevant from here on
return ['query' => $queryString, 'dsn' => $this->dsn];
}
public static function lazyInstance(string $dsn) : self
{
$instance = (new
ReflectionClass(self::class))->newInstanceWithoutConstructor();
$instance->initializer = function () use ($dsn, $instance) {
$instance->__construct($dsn);
$instance->initializer = function () {
};
};
return $instance;
}
}
$instance = DbConnection::lazyInstance('mysql://something');
var_dump($instance);
var_dump($instance->query('SELECT * FROM foo'));
var_dump($instance->query('SELECT * FROM bar'));
Here's an example of it at work: https://3v4l.org/Y0eoL
The pattern is simple:
- intercept constructor call
- capture constructor parameters
- instantiate without constructor
- defer constructor call for later
The same can be used in a myriad of different ways, but this is a legit
use-cases that generally don't involve coding everything into the same
class (and I generally advise against doing that anyway).
Therefore I don't see a reason to drop manual constructor calls, unless
there is a strong necessity to get rid of 'em.
Marco Pivetta
On Thu, Jan 12, 2017 at 8:11 AM, Tim Bezhashvyly tim.bezhashvyly@gmail.com
wrote:
Dear internals,
I would like to propose 2 RFCs:
- Disallow explicit call of __construct method
- Polymorphic dispatch
I'm sure I'm not the first who came with those 2 ideas so in case those
were already proposed and rejected just let me know.Otherwise please bless me with mana which will allow me to submit them.
Regards,
Tim
Heya,
While I agree that it is weird to be able to call constructors more than
once, this is generally used for:
- lazy loading
- resource reset
Specifically, what is going on is something like following:<?php
final class DbConnection
{
private $dsn;
private $initializer;
public function __construct(string $dsn)
{
$this->dsn = $dsn;
// socket stuff happens here, much like with PDO
}public function query(string $queryString) : array { ($this->initializer)(); // irrelevant from here on return ['query' => $queryString, 'dsn' => $this->dsn]; } public static function lazyInstance(string $dsn) : self { $instance = (new
ReflectionClass(self::class))->newInstanceWithoutConstructor();
$instance->initializer = function () use ($dsn, $instance) {
$instance->__construct($dsn);
$instance->initializer = function () {
};
};
return $instance;
}
}$instance = DbConnection::lazyInstance('mysql://something');
var_dump($instance);
var_dump($instance->query('SELECT * FROM foo'));
var_dump($instance->query('SELECT * FROM bar'));Here's an example of it at work: https://3v4l.org/Y0eoL
The pattern is simple:
- intercept constructor call
- capture constructor parameters
- instantiate without constructor
- defer constructor call for later
The same can be used in a myriad of different ways, but this is a legit
use-cases that generally don't involve coding everything into the same
class (and I generally advise against doing that anyway).Therefore I don't see a reason to drop manual constructor calls, unless
there is a strong necessity to get rid of 'em.Marco Pivetta
Very creative but why not the following?
<?php
final class DbConnection {
private $dsn;
private $initialized = false;
public function __construct($dsn) {
$this->dsn = $dsn;
}
private function init() {
$this->initialized = true;
echo $this->dsn , "\n";
}
public function query($queryString) {
// this is actually cheaper than calling the closure
$this->initialized || $this->init();
return ['query' => $queryString, 'dsn' => $this->dsn];
}
}
$db_conn = new DbConnection('mysql://something');
var_dump(
$db_conn,
$db_conn->query('SELECT * FROM foo'),
$db_conn->query('SELECT * FROM bar')
);
Works equally well in PHP and HHVM and achieves your goal without using
undocumented side effects and reflection.
Adding the ability of a non-lazy and lazy construction is easily added
too if desired.
--
Richard "Fleshgrinder" Fussenegger
Hey Richard,
Heya,
While I agree that it is weird to be able to call constructors more than
once, this is generally used for:
- lazy loading
- resource reset
Specifically, what is going on is something like following:<?php
final class DbConnection
{
private $dsn;
private $initializer;
public function __construct(string $dsn)
{
$this->dsn = $dsn;
// socket stuff happens here, much like with PDO
}public function query(string $queryString) : array { ($this->initializer)(); // irrelevant from here on return ['query' => $queryString, 'dsn' => $this->dsn]; } public static function lazyInstance(string $dsn) : self { $instance = (new
ReflectionClass(self::class))->newInstanceWithoutConstructor();
$instance->initializer = function () use ($dsn, $instance) {
$instance->__construct($dsn);
$instance->initializer = function () {
};
};
return $instance;
}
}$instance = DbConnection::lazyInstance('mysql://something');
var_dump($instance);
var_dump($instance->query('SELECT * FROM foo'));
var_dump($instance->query('SELECT * FROM bar'));Here's an example of it at work: https://3v4l.org/Y0eoL
The pattern is simple:
- intercept constructor call
- capture constructor parameters
- instantiate without constructor
- defer constructor call for later
The same can be used in a myriad of different ways, but this is a legit
use-cases that generally don't involve coding everything into the same
class (and I generally advise against doing that anyway).Therefore I don't see a reason to drop manual constructor calls, unless
there is a strong necessity to get rid of 'em.Marco Pivetta
Very creative but why not the following?
<?php final class DbConnection { private $dsn; private $initialized = false; public function __construct($dsn) { $this->dsn = $dsn; } private function init() { $this->initialized = true; echo $this->dsn , "\n"; } public function query($queryString) { // this is actually cheaper than calling the closure $this->initialized || $this->init(); return ['query' => $queryString, 'dsn' => $this->dsn]; } } $db_conn = new DbConnection('mysql://something'); var_dump( $db_conn, $db_conn->query('SELECT * FROM foo'), $db_conn->query('SELECT * FROM bar') );
Works equally well in PHP and HHVM and achieves your goal without using
undocumented side effects and reflection.Adding the ability of a non-lazy and lazy construction is easily added
too if desired.--
Richard "Fleshgrinder" Fussenegger
I made an example where everything was in a single class, but most
scenarios involve a lazy-loading wrapper that has no knowledge of the
original class besides its constructor.
I use this approach to generate proxy classes that are "safe" to use with
fluent interfaces, for example (because fluent interfaces are really a mess
to work with).
The alternative (for me, specifically) is to copy the constructor AST into
a closure, then store the closure somewhere. Still, if that closure also
calls the private constructor, I have to do it recursively, and so on until
I just give up :-P Also, this assumes codegen. Large pieces of code will
need rewriting, whereas I don't see strong reasoning for dropping a feature
that, while weird, is actually useful.
Marco Pivetta
Hey Richard,
I made an example where everything was in a single class, but most
scenarios involve a lazy-loading wrapper that has no knowledge of the
original class besides its constructor.I use this approach to generate proxy classes that are "safe" to use with
fluent interfaces, for example (because fluent interfaces are really a mess
to work with).The alternative (for me, specifically) is to copy the constructor AST into
a closure, then store the closure somewhere. Still, if that closure also
calls the private constructor, I have to do it recursively, and so on until
I just give up :-P Also, this assumes codegen. Large pieces of code will
need rewriting, whereas I don't see strong reasoning for dropping a feature
that, while weird, is actually useful.Marco Pivetta
That actually calls out for reflection and that's what it is good for.
Even if explicit construct calls are to be forbidden, the reflection API
is definitely a different story.
That being said, your remarks definitely tell everyone that a change in
this area -- if wanted -- is only possible in PHP 8 and has to be
considered a breaking change.
<?php
final class DbConnection {
private $dsn;
private $initializer;
public function __construct($dsn) {
$this->dsn = $dsn;
// socket stuff happens here, much like with PDO
}
public static function lazyInstance($dsn) {
$reflector = new ReflectionClass(self::class);
$self = $reflector->newInstanceWithoutConstructor();
$new = $reflector->getConstructor();
$self->initializer = function () use ($self, $new, $dsn) {
$new->invoke($self, $dsn);
$self->initializer = function () {};
};
return $self;
}
public function query($queryString) {
$this->initializer->__invoke();
// irrelevant from here on
return ['query' => $queryString, 'dsn' => $this->dsn];
}
}
$instance = DbConnection::lazyInstance('mysql://something');
var_dump($instance);
var_dump($instance->query('SELECT * FROM foo'));
var_dump($instance->query('SELECT * FROM bar'));
the only reason for prohibiting explicit __construct calls is that it
makes PHP objects mutable and it's state unpredictable. Still would
like to give this RFC a try.
I completely share this concern with Tim. Of course reflection would
still allow one to circumvent any limitations imposed by the normal
runtime. But that's what reflection is for after all.
And what about polymorphic dispatch?
I think you need to elaborate a bit more here to get some feedback.
--
Richard "Fleshgrinder" Fussenegger
Hi Richard,
thank you for support with __construct. I absolutely agree that it breaks BC and fine to propose it just for PHP 8.
By polymorphic dispatch I mean a possibility to have the same method (including constructor) in multiple variations. E.g.:
class Price
{
public function __construct(int $price)
{
...
}
public function __construct(string $price)
{
...
}
}
Regards,
Tim
Hey Richard,
I made an example where everything was in a single class, but most
scenarios involve a lazy-loading wrapper that has no knowledge of the
original class besides its constructor.I use this approach to generate proxy classes that are "safe" to use with
fluent interfaces, for example (because fluent interfaces are really a mess
to work with).The alternative (for me, specifically) is to copy the constructor AST into
a closure, then store the closure somewhere. Still, if that closure also
calls the private constructor, I have to do it recursively, and so on until
I just give up :-P Also, this assumes codegen. Large pieces of code will
need rewriting, whereas I don't see strong reasoning for dropping a feature
that, while weird, is actually useful.Marco Pivetta
That actually calls out for reflection and that's what it is good for.
Even if explicit construct calls are to be forbidden, the reflection API
is definitely a different story.That being said, your remarks definitely tell everyone that a change in
this area -- if wanted -- is only possible in PHP 8 and has to be
considered a breaking change.<?php final class DbConnection { private $dsn; private $initializer; public function __construct($dsn) { $this->dsn = $dsn; // socket stuff happens here, much like with PDO } public static function lazyInstance($dsn) { $reflector = new ReflectionClass(self::class); $self = $reflector->newInstanceWithoutConstructor(); $new = $reflector->getConstructor(); $self->initializer = function () use ($self, $new, $dsn) { $new->invoke($self, $dsn); $self->initializer = function () {}; }; return $self; } public function query($queryString) { $this->initializer->__invoke(); // irrelevant from here on return ['query' => $queryString, 'dsn' => $this->dsn]; } } $instance = DbConnection::lazyInstance('mysql://something'); var_dump($instance); var_dump($instance->query('SELECT * FROM foo')); var_dump($instance->query('SELECT * FROM bar'));
the only reason for prohibiting explicit __construct calls is that it
makes PHP objects mutable and it's state unpredictable. Still would
like to give this RFC a try.I completely share this concern with Tim. Of course reflection would
still allow one to circumvent any limitations imposed by the normal
runtime. But that's what reflection is for after all.And what about polymorphic dispatch?
I think you need to elaborate a bit more here to get some feedback.
--
Richard "Fleshgrinder" Fussenegger
Hey Tim,
On Thu, Jan 12, 2017 at 10:51 PM, Tim Bezhashvyly <tim.bezhashvyly@gmail.com
wrote:
Hi Richard,
thank you for support with __construct. I absolutely agree that it breaks
BC and fine to propose it just for PHP 8.By polymorphic dispatch I mean a possibility to have the same method
(including constructor) in multiple variations. E.g.:class Price
{
public function __construct(int $price)
{
...
}public function __construct(string $price) { ... }
}
Regards,
TimHey Richard,
I made an example where everything was in a single class, but most
scenarios involve a lazy-loading wrapper that has no knowledge of the
original class besides its constructor.I use this approach to generate proxy classes that are "safe" to use
with
fluent interfaces, for example (because fluent interfaces are really a
mess
to work with).The alternative (for me, specifically) is to copy the constructor AST
into
a closure, then store the closure somewhere. Still, if that closure also
calls the private constructor, I have to do it recursively, and so on
until
I just give up :-P Also, this assumes codegen. Large pieces of code will
need rewriting, whereas I don't see strong reasoning for dropping a
feature
that, while weird, is actually useful.Marco Pivetta
That actually calls out for reflection and that's what it is good for.
Even if explicit construct calls are to be forbidden, the reflection API
is definitely a different story.That being said, your remarks definitely tell everyone that a change in
this area -- if wanted -- is only possible in PHP 8 and has to be
considered a breaking change.<?php final class DbConnection { private $dsn; private $initializer; public function __construct($dsn) { $this->dsn = $dsn; // socket stuff happens here, much like with PDO } public static function lazyInstance($dsn) { $reflector = new ReflectionClass(self::class); $self = $reflector->newInstanceWithoutConstructor(); $new = $reflector->getConstructor(); $self->initializer = function () use ($self, $new, $dsn) { $new->invoke($self, $dsn); $self->initializer = function () {}; }; return $self; } public function query($queryString) { $this->initializer->__invoke(); // irrelevant from here on return ['query' => $queryString, 'dsn' => $this->dsn]; } } $instance = DbConnection::lazyInstance('mysql://something'); var_dump($instance); var_dump($instance->query('SELECT * FROM foo')); var_dump($instance->query('SELECT * FROM bar'));
the only reason for prohibiting explicit __construct calls is that it
makes PHP objects mutable and it's state unpredictable. Still would
like to give this RFC a try.I completely share this concern with Tim. Of course reflection would
still allow one to circumvent any limitations imposed by the normal
runtime. But that's what reflection is for after all.And what about polymorphic dispatch?
I think you need to elaborate a bit more here to get some feedback.
--
Richard "Fleshgrinder" Fussenegger
That's generally understood as "Method overloading". There were more
discussions about it in here recently: you might want to look them up.
Marco Pivetta
Hi Marco,
the only reason for prohibiting explicit __construct calls is that it makes PHP objects mutable and it's state unpredictable. Still would like to give this RFC a try.
And what about polymorphic dispatch?
Regards,
Tim
Heya,
While I agree that it is weird to be able to call constructors more than once, this is generally used for:
- lazy loading
- resource reset
Specifically, what is going on is something like following:<?php
final class DbConnection
{
private $dsn;
private $initializer;
public function __construct(string $dsn)
{
$this->dsn = $dsn;
// socket stuff happens here, much like with PDO
}public function query(string $queryString) : array { ($this->initializer)(); // irrelevant from here on return ['query' => $queryString, 'dsn' => $this->dsn]; } public static function lazyInstance(string $dsn) : self { $instance = (new ReflectionClass(self::class))->newInstanceWithoutConstructor(); $instance->initializer = function () use ($dsn, $instance) { $instance->__construct($dsn); $instance->initializer = function () { }; }; return $instance; }
}
$instance = DbConnection::lazyInstance('mysql://something');
var_dump($instance);
var_dump($instance->query('SELECT * FROM foo'));
var_dump($instance->query('SELECT * FROM bar'));Here's an example of it at work: https://3v4l.org/Y0eoL https://3v4l.org/Y0eoL
The pattern is simple:
- intercept constructor call
- capture constructor parameters
- instantiate without constructor
- defer constructor call for later
The same can be used in a myriad of different ways, but this is a legit use-cases that generally don't involve coding everything into the same class (and I generally advise against doing that anyway).
Therefore I don't see a reason to drop manual constructor calls, unless there is a strong necessity to get rid of 'em.
Marco Pivetta
http://twitter.com/Ocramius http://twitter.com/Ocramius
http://ocramius.github.com/ http://ocramius.github.com/
Dear internals,
I would like to propose 2 RFCs:
- Disallow explicit call of __construct method
- Polymorphic dispatch
I'm sure I'm not the first who came with those 2 ideas so in case those were already proposed and rejected just let me know.
Otherwise please bless me with mana which will allow me to submit them.
Regards,
Tim