Hello internals,
I've written a small RFC to make it possible to call constructors of
objects as a callable, rather than only being able to call them
through the 'new' language construct.
https://wiki.php.net/rfc/callableconstructors
After gathering informal feedback for the RFC, one person said that
they felt it needed more of an emphasis on why it should be
implemented. I wanted to keep the actual RFC short, in the hope that
more people would read it in it's entirety, and also wanted to leave
library specific code out of the RFC. So, as an addendum to the RFC
text:
I use the Auryn* DIC library. What I've wanted to do, and should be
able to do in my opinion, is this:
$injector->delegate('FooInterface', 'FooImplementation::__construct');
Which tells the injector "Whenever something requests an object that
implements FooInterface as a dependency, just execute the
FooImplementation constructor to make that object".
If constructors had always been callable in PHP, this would be a
standard thing to do and not controversial imo. It's only due to
reasons lost in the mists of time, that object instantiation can only
be invoked via 'new' that it's not possible currently, and so needs an
RFC to change.
As for the 'BC break' - I would be interested in hearing if anyone
actually (admits to) having any code that would be affected by it.
I think possibly the best way to manage this BC break, would be to
make call_user_function($fn) behave the same as $fn(), when $fn =
'Foo::__construct' in a 7.0.x release, before adding the new
functionality in 7.1.0. That would allow people to upgrade to a
version where the changed behaviour breaks entirely, and so they can
fix their code to use the correct calling semantics, before adding the
new functionality in a later version. That give a clear way for people
to detect problems, rather than it being a subtle change which is hard
to detect. However I remain open to ideas on how to manage it better.
cheers
Dan
Hi Dan,
Dan Ackroyd wrote:
I've written a small RFC to make it possible to call constructors of
objects as a callable, rather than only being able to call them
through the 'new' language construct.
While I like the concept, I dislike the execution.
My particular concern is that this makes __construct magic. It is a
so-called "magic method" already, which is why it is prefixed with two
underscores, but like all magic methods, it isn't the method itself that
is "magic". __construct itself is just a method, you can call it
directly (for example, parent::__construct()). Where the magic lies is
in what calls it: when an object is created with new
, __construct is
automatically called. Similarly, __toString is just a normal method you
can call like any other, and is only "magic" because when an object is
converted to a string, __toString is called.
Your proposal, however, would change this. Instead of __construct being
a normal method which merely initialises the object it is called on, it
would now become truly magical and actually implicitly create the object
(without the author of the method having made it do so), but only in
certain circumstances (not when called within a class, presumably).
Furthermore, it becomes a strange beast PHP has not known until now: a
method that is simultaneously static (Foo::__construct creates a new
Foo) and an instance method (parent::__construct() operates on $this),
with the two having different behaviour.
Instead of changing __construct to implicitly create the object it acts
on in certain contexts, I would suggest a simpler approach: add a magic
::new() static method that exists on all classes (think ::class,
although that is a constant). Foo::new() would work identically to new
Foo(), and would solve your use case. It would be more intuitive, I
think, and it avoids the problems of changing __construct.
Thanks.
--
Andrea Faulds
https://ajf.me/
Instead of changing __construct to implicitly create the object it acts on
in certain contexts, I would suggest a simpler approach: add a magic ::new()
static method that exists on all classes (think ::class, although that is a
constant). Foo::new() would work identically to new Foo(), and would solve
your use case. It would be more intuitive, I think, and it avoids the
problems of changing __construct.
(Un?)fortunately, new() is a valid method name for userland classes in PHP 7.0.
Adam
Hi Adam,
Adam Harvey wrote:
Instead of changing __construct to implicitly create the object it acts on
in certain contexts, I would suggest a simpler approach: add a magic ::new()
static method that exists on all classes (think ::class, although that is a
constant). Foo::new() would work identically to new Foo(), and would solve
your use case. It would be more intuitive, I think, and it avoids the
problems of changing __construct.(Un?)fortunately, new() is a valid method name for userland classes in PHP 7.0.
It is indeed, but that means the risk of backwards compatibility
breakage is quite low. PHP 7 hasn't been out for long.
Thanks!
Andrea Faulds
https://ajf.me/
Dan Ackroyd wrote:
I've written a small RFC to make it possible to call constructors of
objects as a callable, rather than only being able to call them
through the 'new' language construct.While I like the concept, I dislike the execution.
Snipped for brevity, but I agree with your sentiment here. Making
__construct more magic seems like an imperfect solution to this. I'm not
sure making a new magic method (new, __new, etc) is much better though,
since it would serve such a limited purpose.
It has come up before in various contexts that PHP lacks a true callable
type, in the sense that you never "construct" a callable, you just use
something as a callable. I think callable constructors would fit better
as part of an API that solved this.
For instance, there could be a (magic) Callable class with various named
constructors:
- Callable::forFunction(string $function_name)
- Callable::forMethod(object $instance, string $method_name)
- Callable::forStaticMethod(string $class_name, string $method_name)
- Callable::fromPaamayimNekudotayim(string $class_colon_colon_method) //
OK, maybe not ;)
This isn't perfect, because the parameters would still look like strings
to static analysis, rather than being proper compiled-in references, but
it's nicer than repeatedly analysing an array or string to evaluate
is_callable()
or type-checks.
Then we could add to that a special case for constructors:
- Callable::forConstructor(string $class_name)
This would be roughly equivalent to (and polyfillable with):
return function (...$args) use ( $class_name ) { return new $class_name(...$args); };
Regards,
--
Rowan Collins
[IMSoP]
Hi Rowan,
Rowan Collins wrote:
Snipped for brevity, but I agree with your sentiment here. Making
__construct more magic seems like an imperfect solution to this. I'm not
sure making a new magic method (new, __new, etc) is much better though,
since it would serve such a limited purpose.
Yeah, I do wonder if ::new would really be worth it.
Then we could add to that a special case for constructors:
- Callable::forConstructor(string $class_name)
Oh, that's quite a clean solution, I like that. Heck, no need for
Callable, we already have ->getClosure() in Reflection. A modified
version of that for constructors could work
(ReflectionClass::getConstructorClosure() ?).
Thanks.
Andrea Faulds
https://ajf.me/
Hi Rowan,
Rowan Collins wrote:
Snipped for brevity, but I agree with your sentiment here. Making
__construct more magic seems like an imperfect solution to this. I'm not
sure making a new magic method (new, __new, etc) is much better though,
since it would serve such a limited purpose.Yeah, I do wonder if ::new would really be worth it.
Then we could add to that a special case for constructors:
- Callable::forConstructor(string $class_name)
Oh, that's quite a clean solution, I like that. Heck, no need for
Callable, we already have ->getClosure() in Reflection. A modified
version of that for constructors could work
(ReflectionClass::getConstructorClosure() ?).Thanks.
Hey Andrea,
This would still not solve Dan's problem though. Which is representing
new Class
as a serializeable callable.
I really think ::new() makes the most sense here (and looks quite alright
unlike Class::__construct()
).
Although making a special case for new ...
is pretty simple, I too have
felt the need for callable constructors plenty of times, that would be
useful.
Then we could add to that a special case for constructors:
- Callable::forConstructor(string $class_name)
Oh, that's quite a clean solution, I like that. Heck, no need for
Callable, we already have ->getClosure() in Reflection. A modified
version of that for constructors could work
(ReflectionClass::getConstructorClosure() ?).Thanks.
Hey Andrea,
This would still not solve Dan's problem though. Which is representing
new Class
as a serializeable callable.
There are lots of types of callable which can't be serialized already -
anonymous functions, invokable objects, instance methods - so I'm still
not convinced why this particular one is so important. Anything that can
only take its callback as a string already rejects many things that are
otherwise callable, so I'm not sure why having to reject a wrapped
constructor is so much worse that it needs a new language concept.
Regards,
--
Rowan Collins
[IMSoP]
Then we could add to that a special case for constructors:
- Callable::forConstructor(string $class_name)
Oh, that's quite a clean solution, I like that. Heck, no need for
Callable, we already have ->getClosure() in Reflection. A modified
version of that for constructors could work
(ReflectionClass::getConstructorClosure() ?).
Reflection always feels to me like "the language doesn't really support
this, but you can sort of cheat this way", but maybe that's a poor
prejudice.
Regards,
--
Rowan Collins
[IMSoP]
I use the Auryn* DIC library. What I've wanted to do, and should be
able to do in my opinion, is this:$injector->delegate('FooInterface', 'FooImplementation::__construct');
I only skimmed the RFC (and am unfamiliar with Auryn beyond glancing
at its documentation for about ten seconds just now), but am I right
that this is equivalent to the following?
$injector->delegate('FooInterface', function (...$args) { return new
FooImplementation(...$args); });
If that's the case, I'm not really convinced that this is worth the
syntactic sugar. I'm also somewhat concerned about conflating
instantiation and method calls — although they both ultimately call a
method, they're not actually the same thing, since instantiating an
object does extra work at the same time. Being able to just look for
"new" in code is helpful.
Open to being convinced otherwise (examples from other languages would
be helpful), but my initial reaction isn't really positive.
Thanks,
Adam
am I right
that this is equivalent to the following?$injector->delegate('FooInterface', function (...$args) { return new
FooImplementation(...$args); });
Nope.
The vital part you missed is that with the original function, all of
the parameters (aka dependencies of the class) can be found through
reflection, and so can be created by the injector as needed. By using
function (...$args){} - all of the parameter information is lost, and
so isn't usable by an autowiring-injector.
The only equivalent thing that would be to either create a dumb
function by hand:
function createFooImpl(Bar $bar, Quux $quux) {
return new FooImplementation($bar, $quux);
}
for every possible constructor used in your app (and remember to keep
it up to date of course!), or to use eval to create a function on the
fly.
"I only skimmed the RFC"
I find this very dissapointing. Conversations are far more productive
if people have read the whole of a proposal before deciding how they
feel about it and giving feedback.
In particular reading the sections "Can't be used as strings in class
properties" and "Can't be used in string based configuration" would
give examples where the RFC is of benefit in a way that is not related
to DIC libraries.
cheers
Dan
am I right
that this is equivalent to the following?$injector->delegate('FooInterface', function (...$args) { return new
FooImplementation(...$args); });Nope.
The vital part you missed is that with the original function, all of
the parameters (aka dependencies of the class) can be found through
reflection, and so can be created by the injector as needed. By using
function (...$args){} - all of the parameter information is lost, and
so isn't usable by an autowiring-injector.
OK, I understand that now. Thanks.
Why is having a special syntax that is considered callable and
conflates instantiation and method calls (which are ultimately
separate things) better than having the injector accept a class name
and use reflection as required to instantiate that?
"I only skimmed the RFC"
I find this very dissapointing. Conversations are far more productive
if people have read the whole of a proposal before deciding how they
feel about it and giving feedback.
To be clear: I did read the entire RFC, just quickly. Since it seemed
an obvious question to me (and apparently not just me, based on
/r/php), and didn't seem to be addressed that I could see, I thought
it was better to just ask it rather than stay silent and either ask it
several days later or vote -1 without explanation.
In particular reading the sections "Can't be used as strings in class
properties" and "Can't be used in string based configuration" would
give examples where the RFC is of benefit in a way that is not related
to DIC libraries.
Honestly, I still don't find either of them particularly persuasive,
for the same reasons as the aforementioned DIC example. They're
trivially special cased without muddying instantiation and method
calls. Hence my "are there analogues for this elsewhere" question.
Adam
Why is having a special syntax that is considered callable and
conflates instantiation and method calls better than having the injector accept a class name
and use reflection as required to instantiate that?
They're
trivially special cased without muddying instantiation and method
calls. Hence my "are there analogues for this elsewhere" question.
I think this is actually something that would be useful across
applications - not just my own library.
Although I could use the Reflection code that Johannes wrote to make
it possible for my own code to use constructors as a callable, I
wouldn't be able to use it in anyone else's code, unless I asked them
nicely to add the special casing to their library code as well.
For example I would hope to use callable constructors with the Zend
service manager. That is a good example of where just being able to
specify "Foo::__construct" in a config file would save having to write
'boiler plate' instantiation functions for every object.
I think there is a small 'chicken and egg' problem here. Of course no
one is currently using constructors as callable, because it isn't
possible yet, and so it's hard to point to any code that uses them.
And so it appears that it's an small use case. I'm pretty sure if it
was adopted it would become a standard thing to do when 'wiring up'
applications, or hydrating objects fetched from various data sources.
Rowan Wrote:
I would like to join Adam in asking if
you can find examples in other languages where constructors can be used
as though they were static methods in this way.
Python would be a good example. constructors are just functions that
can be called, and passed around as 'callable' things.
Obviously Pythons concepts of classes aren't identical to PHP'
concepts - but the code is doing the same thing; passing around a
thing that can be called without having to use Reflection to make it a
callable.
==== Begin
from random import randint
class Foo:
def f(self):
return 'I am foo'
class Bar:
def f(self):
return 'I am bar'
if randint(0,1) == 0:
classname = Foo
else:
classname = Bar
obj = classname()
print obj.f()
==== End
I'm not aware of any languages that contemplated making it be
callable, but then decided it would be a bad idea.
cheers
Dan
Rowan Wrote:
I would like to join Adam in asking if
you can find examples in other languages where constructors can be used
as though they were static methods in this way.
Python would be a good example. constructors are just functions that
can be called, and passed around as 'callable' things.Obviously Pythons concepts of classes aren't identical to PHP'
concepts - but the code is doing the same thing; passing around a
thing that can be called without having to use Reflection to make it a
callable.
Looking at your example at first, I couldn't see how it demonstrated any
such passing, because you can dynamically select class names in PHP
(here's that code transposed line-by-line to PHP): https://3v4l.org/AD2m3
Looking closer, that's not actually what's happening, but it's still not
equivalent to what you want in PHP:
- The first difference is that "Foo()" rather than "new Foo()" creates
an object; the fact that "Foo" is a class rather than a function causes
this to happen when it is invoked. This is different from PHP, where
classes and functions don't share a namespace, and a class is not
something that can be invoked ("foo()" doesn't refer to the same thing
as "new foo()"). - The second difference is that you are not passing around a string
representing what to call, but a reference to the invokable object
itself. The line "classname = Foo" simply makes "classname" an alias for
the class "Foo", which is then invokable as before to trigger instantiation.
So if anything, this particular example is more like my
Callable::forConstructor() idea - a way of passing around an invokable
object which, when invoked, will cause instantiation of a particular class.
I don't, however, know Python well enough to say whether a better
example would be more like what you want in PHP.
Regards,
--
Rowan Collins
[IMSoP]
Hi Dan,
Dan Ackroyd wrote:
am I right
that this is equivalent to the following?$injector->delegate('FooInterface', function (...$args) { return new
FooImplementation(...$args); });Nope.
The vital part you missed is that with the original function, all of
the parameters (aka dependencies of the class) can be found through
reflection, and so can be created by the injector as needed. By using
function (...$args){} - all of the parameter information is lost, and
so isn't usable by an autowiring-injector.
This is true for a trivial implementation like the one above, but I
would point out that a more sophisticated userland implementation could
fix this by generating PHP code.
So it's still worth questioning whether this needs to be a built-in
function.
Thanks!
--
Andrea Faulds
https://ajf.me/
This is true for a trivial implementation like the one above, but I
would point out that a more sophisticated userland implementation could
fix this by generating PHP code.
You don't have to generate PHP code for this to work:
$arr = is_string($func) ? explode("::", $func, 2) : $func;
$ref = (sizeof($arr) === 2) ?
new ReflectionMethod($arr[0], $arr[1])
: new ReflectionFunction($func);
$args = identifyArgumentsAndGetCtorArgs($ref);
if (sizeof($arr) === 2 && $arr[1] === '__construct') {
$rc = new ReflectionClass($arr[0]);
return $rc->newInstanceArgs($args);
} else {
return $ref->invokeArgs($args);
}
I haven't tested this and it might need some fine tuning, but isn't
really complicated. (Of course you can generate PHP code for this
trivially in order to avoid the small reflection overhead)
So it's still worth questioning whether this needs to be a built-in
function.
I think it is quite obscure and a hardly used feature. Language features
and syntax sugar things should be common things.
johannes
Just adding to the use-cases: I really could've needed this the other day:
$records = array_map([MyRecord::class, '__construct'],
$db->someQuery()->fetchAll());
I used a named constructor instead, but this results with more internal
method calls:
$records = array_map([MyRecord::class, 'fromResultSetRow'],
$db->someQuery()->fetchAll());
So yeah, I see some clear advantages in making constructors (static by
design) available statically.
Marco Pivetta
Hello internals,
I've written a small RFC to make it possible to call constructors of
objects as a callable, rather than only being able to call them
through the 'new' language construct.https://wiki.php.net/rfc/callableconstructors
After gathering informal feedback for the RFC, one person said that
they felt it needed more of an emphasis on why it should be
implemented. I wanted to keep the actual RFC short, in the hope that
more people would read it in it's entirety, and also wanted to leave
library specific code out of the RFC. So, as an addendum to the RFC
text:I use the Auryn* DIC library. What I've wanted to do, and should be
able to do in my opinion, is this:$injector->delegate('FooInterface', 'FooImplementation::__construct');
Which tells the injector "Whenever something requests an object that
implements FooInterface as a dependency, just execute the
FooImplementation constructor to make that object".If constructors had always been callable in PHP, this would be a
standard thing to do and not controversial imo. It's only due to
reasons lost in the mists of time, that object instantiation can only
be invoked via 'new' that it's not possible currently, and so needs an
RFC to change.As for the 'BC break' - I would be interested in hearing if anyone
actually (admits to) having any code that would be affected by it.I think possibly the best way to manage this BC break, would be to
make call_user_function($fn) behave the same as $fn(), when $fn =
'Foo::__construct' in a 7.0.x release, before adding the new
functionality in 7.1.0. That would allow people to upgrade to a
version where the changed behaviour breaks entirely, and so they can
fix their code to use the correct calling semantics, before adding the
new functionality in a later version. That give a clear way for people
to detect problems, rather than it being a subtle change which is hard
to detect. However I remain open to ideas on how to manage it better.cheers
Dan
Just adding to the use-cases: I really could've needed this the other day:
$records = array_map([MyRecord::class, '__construct'],
$db->someQuery()->fetchAll());I used a named constructor instead, but this results with more internal
method calls:$records = array_map([MyRecord::class, 'fromResultSetRow'],
$db->someQuery()->fetchAll());
If fetchAll is coming from PDO. then you could have simply done this:
$records = $db->someQuery()->fetchAll(MyRecord::class);
On Thu, Feb 25, 2016 at 11:20 AM, Marco Pivetta ocramius@gmail.com
wrote:Just adding to the use-cases: I really could've needed this the other day:
$records = array_map([MyRecord::class, '__construct'],
$db->someQuery()->fetchAll());I used a named constructor instead, but this results with more internal
method calls:$records = array_map([MyRecord::class, 'fromResultSetRow'],
$db->someQuery()->fetchAll());If fetchAll is coming from PDO. then you could have simply done this:
$records = $db->someQuery()->fetchAll(MyRecord::class);
Not using PDO there.
Marco Pivetta
On Thu, Feb 25, 2016 at 11:20 AM, Marco Pivetta ocramius@gmail.com
wrote:Just adding to the use-cases: I really could've needed this the other day:
$records = array_map([MyRecord::class, '__construct'],
$db->someQuery()->fetchAll());I used a named constructor instead, but this results with more internal
method calls:$records = array_map([MyRecord::class, 'fromResultSetRow'],
$db->someQuery()->fetchAll());If fetchAll is coming from PDO. then you could have simply done this:
$records = $db->someQuery()->fetchAll(MyRecord::class);
Sorry, that should have been
$records = $db->someQuery()->fetchAll(PDO::FETCH_CLASS, $MyRecord::class);
If fetchAll is coming from PDO. then you could have simply done this:
$records = $db->someQuery()->fetchAll(MyRecord::class);
That is a great argument as to why constructors should be callable
without using the 'new'.
It's been possible to do that in PDO for years, and people have been
using it, and liking it, even if they weren't aware that 'under the
hood' the PDO extension was doing the equivalent of calling 'new' as a
callable.
But why is it restricted to PDO? Why can't I do the equivalent with
other DB libraries of my choice? Why can't I use it with userland
libraries that retrieve data from Redis, Mongo or any other
datasource?
It has shown to be a useful pattern inside PDO. We should be
empowering userland developers to have the same tools available to
them as extensions do, wherever possible.
This RFC would allow the equivalent of PDO::FETCH_CLASS to be
implemented by userland code. Which sounds great to me.
Moreover, if you want to pass in a callable that creates the class, why not
just create a method on that class called create (or whatever) much the
same way that singletons often work.
Why should every PHP developer have to jump through that hoop, why
can't we (as the RFC proposes) make constructors be callable, instead
of having to create another method just to call something that should
have been callable in the first place?
And to be precise, people need to use code that was either written
outside their organisation, or within their organisation but in
another department. In either case it wouldn't be possible to alter
the code just to make it callable in a certain way. And so it would be
easier if the language just supported something that could have been
supported right from the start.
cheers
Dan
It's been possible to do that in PDO for years, and people have been
using it, and liking it, even if they weren't aware that 'under the
hood' the PDO extension was doing the equivalent of calling 'new' as a
callable.
Actually, PDO_FETCH_CLASS does something rather magic: it creates an
object and actually injects individual property values into it. The
constructor is not being used as a callback at all, and by default is
called (with no parameters) after the properties have been initialised
(a rather odd sequence, but there you go).
But why is it restricted to PDO? Why can't I do the equivalent with
other DB libraries of my choice? Why can't I use it with userland
libraries that retrieve data from Redis, Mongo or any other
datasource?It has shown to be a useful pattern inside PDO. We should be
empowering userland developers to have the same tools available to
them as extensions do, wherever possible.
What exactly is stopping you doing it right now? Note that PDO has code
written specifically for this case, which takes a class name and uses it
in a particular way; if all it was doing was calling the constructor of
that class, it would be trivial to implement in userland code: "new
$classname($args)" works just fine, AFAIK.
The only part that is not possible right now - in PDO or anywhere else
in the language - is to pass the constructor directly to a context
that's expecting a callable. You have demonstrated some use cases where
that would be convenient, but all of them can be implemented a different
way reasonably easily.
Why should every PHP developer have to jump through that hoop, why
can't we (as the RFC proposes) make constructors be callable, instead
of having to create another method just to call something that should
have been callable in the first place?
You talk of "jumping through hoops" and "should have been ... in the
first place" as though it is self-evident that calling a constructor
statically should imply object creation. A constructor is not a static
method that somehow creates a new instance; rather, the "new" keyword is
what creates the instance, and the constructor is an instance method
called after creation, but before returning the new object to the caller.
It's important to remember that constructors and the "new" keyword are
not concepts that PHP invented. I would like to join Adam in asking if
you can find examples in other languages where constructors can be used
as though they were static methods in this way. While there's nothing
wrong with PHP being the only language to do something, there may be
sound theory behind how other languages work.
Regards,
--
Rowan Collins
[IMSoP]
Hello internals,
I've written a small RFC to make it possible to call constructors of
objects as a callable, rather than only being able to call them
through the 'new' language construct.https://wiki.php.net/rfc/callableconstructors
After gathering informal feedback for the RFC, one person said that
they felt it needed more of an emphasis on why it should be
implemented. I wanted to keep the actual RFC short, in the hope that
more people would read it in it's entirety, and also wanted to leave
library specific code out of the RFC. So, as an addendum to the RFC
text:I use the Auryn* DIC library. What I've wanted to do, and should be
able to do in my opinion, is this:$injector->delegate('FooInterface', 'FooImplementation::__construct');
Which tells the injector "Whenever something requests an object that
implements FooInterface as a dependency, just execute the
FooImplementation constructor to make that object".
How is this any better, or what benefit would it provide over the current
implementation, which takes a class name and then calls new on it
when the Interface is asked for?
Moreover, if you want to pass in a callable that creates the class, why not
just create a method on that class called create (or whatever) much the
same way that singletons often work.
Hi!
If constructors had always been callable in PHP, this would be a
standard thing to do and not controversial imo. It's only due to
reasons lost in the mists of time, that object instantiation can only
be invoked via 'new' that it's not possible currently, and so needs an
RFC to change.
Constructors have always been callable in PHP and still are. They are
functions like any other. What you need is a factory method, not a
constructor - constructor is called to initialize object, not create it.
But you can create factory methods at will, you just don't have to call
it __construct.
--
Stas Malyshev
smalyshev@gmail.com