Hey Richard,
On Thu, Jan 12, 2017 at 9:58 PM, Fleshgrinder <[email protected]> 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/