Explicit constructor call and polymorphic dispatch

php.internals

Tim Bezhashvyly

9 years ago
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

Sebastian Bergmann

9 years ago
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?

Fleshgrinder

9 years ago
On 1/12/2017 8:19 AM, Sebastian Bergmann wrote:
> 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? >
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_: https://3v4l.org/DFGCa
-- Richard "Fleshgrinder" Fussenegger

Net Mo

9 years ago
> > 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

Fleshgrinder

9 years ago
On 1/13/2017 8:24 AM, Wes wrote:
>> >> 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? On 1/13/2017 8:24 AM, Wes wrote:
>> 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 >
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

Andreas Heigl

9 years ago
Hi All. Am 14.01.17 um 11:03 schrieb Fleshgrinder:
> On 1/13/2017 8:24 AM, Wes wrote:
[…]
> On 1/13/2017 8:24 AM, Wes wrote: >>> 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 >> > > 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.
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 | +---------------------------------------------------------------------+

Fleshgrinder

9 years ago
On 1/14/2017 11:20 AM, Andreas Heigl wrote:
> 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

Andreas Heigl

9 years ago
> Am 14.01.2017 um 11:26 schrieb Fleshgrinder <php@fleshgrinder.com>: > >> On 1/14/2017 11:20 AM, Andreas Heigl wrote: >> 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

Giovanni Giacobbi

9 years ago
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! On 14 January 2017 at 13:15, Andreas Heigl <andreas@heigl.org> wrote:
> > > > Am 14.01.2017 um 11:26 schrieb Fleshgrinder <php@fleshgrinder.com>: > > > >> On 1/14/2017 11:20 AM, Andreas Heigl wrote: > >> 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 > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
-- Giovanni Giacobbi

Michał Brzuchalski

9 years ago
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! > > > On 14 January 2017 at 13:15, Andreas Heigl <andreas@heigl.org> wrote: > > > > > > > > Am 14.01.2017 um 11:26 schrieb Fleshgrinder <php@fleshgrinder.com>: > > > > > >> On 1/14/2017 11:20 AM, Andreas Heigl wrote: > > >> 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 > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: http://www.php.net/unsub.php > > > > > > > -- > Giovanni Giacobbi >
-- regards / pozdrawiam, -- Michał Brzuchalski about.me/brzuchal brzuchalski.com

Fleshgrinder

9 years ago
On 1/14/2017 7:10 PM, Michał Brzuchalski wrote:
> 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

Rowan Collins

9 years ago
On 14/01/2017 14:58, Giovanni Giacobbi wrote:
> 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]

Fleshgrinder

9 years ago
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

Marco Pivetta

9 years ago
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 http://twitter.com/Ocramius http://ocramius.github.com/ On Thu, Jan 12, 2017 at 8:11 AM, Tim Bezhashvyly <tim.bezhashvyly@gmail.com> wrote:

Fleshgrinder

9 years ago
On 1/12/2017 9:35 PM, Marco Pivetta wrote:
> 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') ); ``` https://3v4l.org/PaqqZ 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

Marco Pivetta

9 years ago
Hey Richard, On Thu, Jan 12, 2017 at 9:58 PM, Fleshgrinder <php@fleshgrinder.com> wrote:
> On 1/12/2017 9:35 PM, Marco Pivetta wrote: > > 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') > ); > ``` > > https://3v4l.org/PaqqZ > > 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 http://twitter.com/Ocramius http://ocramius.github.com/

Fleshgrinder

9 years ago
On 1/12/2017 10:13 PM, Marco Pivetta wrote:
> 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')); ``` On 1/12/2017 10:34 PM, Tim Bezhashvyly wrote:
> 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. On 1/12/2017 10:34 PM, Tim Bezhashvyly wrote:
> And what about polymorphic dispatch? >
I think you need to elaborate a bit more here to get some feedback.
-- Richard "Fleshgrinder" Fussenegger

Tim Bezhashvyly

9 years ago
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

Marco Pivetta

9 years ago
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, > Tim > > > On 12 Jan 2017, at 22:39, Fleshgrinder <php@fleshgrinder.com> wrote: > > > > On 1/12/2017 10:13 PM, Marco Pivetta wrote: > >> 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')); > > ``` > > > > On 1/12/2017 10:34 PM, Tim Bezhashvyly wrote: > >> 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. > > > > On 1/12/2017 10:34 PM, Tim Bezhashvyly wrote: > >> 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 http://twitter.com/Ocramius http://ocramius.github.com/

Tim Bezhashvyly

9 years ago
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