Hey All,
Concerning RC1, __construct() and https://wiki.php.net/rfc/prototype_checks
I think we need to round out this discussion on the __construct()
signature checking in 5.4. The current behavior (RC1) feels very wrong
with respect to PHP's class based, single inheritance, multiple
interface, no method overloading, allowed method-overriding nature.
Constructors themselves, specifically in PHP, are not explicitly
statically marked, but are "special" (even though they are part of the
instance API, and are callable- we'll consider this an implementation
detail). In general, constructor signatures have never been (and
rightfully so) governed by the LSP. Why? For a few reasons:
-
before calling new(), you actually don't have a subtype (yet)- the
subtype is a product of new() -
when you call new(), you know the exact subtype you're dealing with.
-
only subtypes who implement or override __construct() can know how
to prepare a stateful subtype. In other words, parents can't know how
to create subtypes who implement __construct() and subtypes shouldn't be
concerned with the implementation details of how a parent creates types
when a subtype has overridden __construct(); -
LSP deals with behaviors and expectations of types (read:
objects), since the constructor is generally considered "static" in
nature, a form of factory specific to the type its defined within, and
since in PHP you can't do (SomeClass instanceof SomeType) and since
(is_a('SomeClass', 'SomeType') doesn't make much sense in the context of
classes), the constructor is not subject to instance "behavior" rules.
In general, developers shouldn't be putting abstract constructors inside
an abstract class nor should they be putting constructors inside
interfaces- this is a well accepted bad practice. (Neither c# or java
allow this- of course they do have the ability to overload methods in
classes for the record. In fact, static methods are not allowed either.)
That said, we really should consider removing this limitation from
constructor signature checking. Not only is it a BC break, it doesn't
make sense in the context of PHP.
Thoughts?
Thanks,
Ralph Schindler
Honestly, I think __construct should behave like any other method when
specified abstract or via an interface.
I know you're thinking "But it's not an LSP violation in the
constructor". But my assertion to that is that it's a violation of
the contract that the abstract method / interface defined. If it's a
violation of contract, I would expect to get a fatal error. After
all, that's what a contract is for if not to enforce.
Now, you could make the argument that it's impossible to enforce a
contract (via type-hinting/instanceof) since there's no object at that
point. So the contract isn't "signed". And that's the only reason
that I could understand for wanting to change it from a fatal error to
ignored.
However, if that's taken, then I would expect to be notified that
constructors are not enforced by the interface/abstract method.
Perhaps raise a notice (or E_STRICT) in the interface on compile time
that "__construct is not enforced by interfaces"...
Additionally, what about other magic methods? Should they be handled
separately? I could understand a contract to enforce __invoke and
__toString, but what about the rest? Should they be specifiable in
contract form (get/set/call/etc)?
So, actually I'm not so sure on that myself. The Only use case I can
think of I've used here:
https://github.com/ircmaxell/PHP-CryptLib/blob/master/lib/CryptLib/Core/AbstractFactory.php#L51
Basically, it's an abstract factory that checks if a class name
implements an interface before it will "register" the class. This is
useful for implementing proxy patterns and the such. So we don't
actually instantiate until later. But we want to be sure that when we
do, we'll have the proper class implementing the proper interface.
Since the factory will call new
later, it needs to know the
constructor is valid. So declaring the constructor in the interface
actually makes sense there...
So, my opinion is that either all magic methods (with the possible
exception of __toString and __invoke) should be excluded from
interface requirements, or none should. Either way is fine with me,
but they should be consistent.
And I do lean on the side that the contract is the contract, and if
you change (overload) a method, you're breaking the contract. And if
you specify the constructor in the contract (you don't have to), you
should be bound to that contract...
So I think 5.4's behavior is the proper one. I won't be upset if it
changes, but if it does I think the rest of the magic methods (two
mentioned withstanding) should change as well...
Just my $0.02...
Anthony
On Thu, Nov 17, 2011 at 4:12 PM, Ralph Schindler
ralph@ralphschindler.com wrote:
Hey All,
Concerning RC1, __construct() and https://wiki.php.net/rfc/prototype_checks
I think we need to round out this discussion on the __construct() signature
checking in 5.4. The current behavior (RC1) feels very wrong with respect
to PHP's class based, single inheritance, multiple interface, no method
overloading, allowed method-overriding nature.Constructors themselves, specifically in PHP, are not explicitly statically
marked, but are "special" (even though they are part of the instance API,
and are callable- we'll consider this an implementation detail). In
general, constructor signatures have never been (and rightfully so) governed
by the LSP. Why? For a few reasons:1) before calling new(), you actually don't have a subtype (yet)- the
subtype is a product of new()2) when you call new(), you know the exact subtype you're dealing with.
3) only subtypes who implement or override __construct() can know how to
prepare a stateful subtype. In other words, parents can't know how to
create subtypes who implement __construct() and subtypes shouldn't be
concerned with the implementation details of how a parent creates types when
a subtype has overridden __construct();4) LSP deals with behaviors and expectations of types (read: objects),
since the constructor is generally considered "static" in nature, a form of
factory specific to the type its defined within, and since in PHP you can't
do (SomeClass instanceof SomeType) and since (is_a('SomeClass', 'SomeType')
doesn't make much sense in the context of classes), the constructor is not
subject to instance "behavior" rules.In general, developers shouldn't be putting abstract constructors inside an
abstract class nor should they be putting constructors inside interfaces-
this is a well accepted bad practice. (Neither c# or java allow this- of
course they do have the ability to overload methods in classes for the
record. In fact, static methods are not allowed either.)That said, we really should consider removing this limitation from
constructor signature checking. Not only is it a BC break, it doesn't make
sense in the context of PHP.Thoughts?
Thanks,
Ralph Schindler
Inline...
Honestly, I think __construct should behave like any other method when
specified abstract or via an interface.
Which is not fair to say because constructors are not like instance
methods, they are in fact "special", and not just inside PHP (more on
that below).
I know you're thinking "But it's not an LSP violation in the
constructor". But my assertion to that is that it's a violation of
the contract that the abstract method / interface defined. If it's a
violation of contract, I would expect to get a fatal error. After
all, that's what a contract is for if not to enforce.Now, you could make the argument that it's impossible to enforce a
contract (via type-hinting/instanceof) since there's no object at that
point. So the contract isn't "signed". And that's the only reason
that I could understand for wanting to change it from a fatal error to
ignored.
So, since PHP lets you do bad things in the first place (like have
constructors and static methods in interfaces, and abstract ctors in
abstract classes), we follow that up with another "bad" of breaking
general LSP expectations of how things work?
Isn't this trying to make two wrongs make a right?
Additionally, what about other magic methods? Should they be handled
separately? I could understand a contract to enforce __invoke and
__toString, but what about the rest? Should they be specifiable in
contract form (get/set/call/etc)?
Again, CTOR is not "special" b/c it's a PHP "magic method" its "special"
because it is shares qualities of being a static method as well as an
instance method.
The rest of the __magic() methods by and large are purely instance
methods (part of the instance API). Having them inside an interface is
completely logical. (BTW, these methods can be considered
engine-level/promoted duck typing since you don't have to implement an
interface for them to do what they do, but that's not important here)
So, actually I'm not so sure on that myself. The Only use case I can
think of I've used here:
https://github.com/ircmaxell/PHP-CryptLib/blob/master/lib/CryptLib/Core/AbstractFactory.php#L51Basically, it's an abstract factory that checks if a class name
implements an interface before it will "register" the class. This is
useful for implementing proxy patterns and the such. So we don't
Lets look at that, the proxy pattern that is. This is no longer valid
usage in 5.4:
<?php
abstract class Person {
protected $name = null;
abstract public function __construct();
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
}
class NamedAgedPerson extends Person {
protected $age;
public function __construct($name, $age) { // COMMENT OUT HERE
$this->name = $name;
$this->age = $age;
}
}
class ProxyNamedAgedPerson extends NamedAgedPerson {
public $proxy = null;
public function __construct(NamedAgedPerson $person)
{
$this->proxy = $person;
}
public function setAge($age)
{
$this->proxy->age = $age;
}
}
$np = new NamedAgedPerson('foo', 33);
$ap = new ProxyNamedAgedPerson($np);
$ap->setAge(40);
var_dump($ap);
Let's ignore the fact that it was not a very good idea to have the ctor
in the abstract person in the first place.
In the above example, it is important to remember that an instance of
NamedAgedPerson behaves exactly any other instance of Person in any
situation where "instanceof Person" is the type check constraint.
There are three things wrong with this scenario.
First, if I consume a system that has practiced this abstract ctor in
abstract class thing, I cannot create valid subtypes on my own in PHP
5.4. The above will throw an E_FATAL.
Secondly, (AND WORSE) is that by simply removing the abstract keyword
from the abstract classes __construct(), I get completely different
behavior, everything works without E_FATAL.
Thirdly, (EVEN WORSE!), is that if I comment out the $name, $age part of
the NamedAgedPerson (see: COMMENT OUT HERE above), no E_FATAL is thrown
on grandchildren who violate this abstract ctor.
So, if we choose to do this wrong+wrong=~right thing, we should at least
do it correctly/consistently all the way through the inheritance type
hierarchy. Don't you think?
And I do lean on the side that the contract is the contract, and if
you change (overload) a method, you're breaking the contract. And if
you specify the constructor in the contract (you don't have to), you
should be bound to that contract...So I think 5.4's behavior is the proper one. I won't be upset if it
changes, but if it does I think the rest of the magic methods (two
mentioned withstanding) should change as well...
Of course, this argument is mostly pedantic to be honest, as I'd be
instructing people to never attempt to put abstract ctors in abstract
classes. I won't be doing that either, but, the argument is largely
that the engine shouldn't be doing these kind of things to begin off with.
Also, I'd has to see this kind of decision impacting future decisions
(like this) b/c it was "ratified" in 5.4.
-ralph
Some responses inline...
So, since PHP lets you do bad things in the first place (like have
constructors and static methods in interfaces, and abstract ctors in
abstract classes), we follow that up with another "bad" of breaking general
LSP expectations of how things work?Isn't this trying to make two wrongs make a right?
That's why I tossed out the concept of raising a notice or E_STRICT
error if a constructor is used in an interface/declared abstract.
The rest of the __magic() methods by and large are purely instance methods
(part of the instance API). Having them inside an interface is completely
logical. (BTW, these methods can be considered engine-level/promoted duck
typing since you don't have to implement an interface for them to do what
they do, but that's not important here)
Well, that's true. However, with the exception of invoke and
toString, the same functionality can be provided by the class. __call
can be avoided by hard-coding the method (well, within reason).
__get, __set and __isset can be avoided by making the members public.
__sleep can be avoided by using the Serializable interface. That's
why I'm pushing them in to that discussion. It's an implementation
detail that's not really associated with the contract provided. That
was my point (which is really not 100% related to the constructor, but
is related in passing)...
Lets look at that, the proxy pattern that is. This is no longer valid usage
in 5.4:
That's not a proxy. That's a Bridge or Adapter pattern. Proxy
usually delays instantiation until first call (See
http://sourcemaking.com/design_patterns/proxy ). So in your example,
the proxy class would be:
class ProxyNamedAgedPerson extends NamedAgedPerson {
public $proxy = null;
protected $proxyClass = '';
public function __construct($proxyClass)
{
$this->proxyClass = $proxyClass;
}
public function setAge($age)
{
if (empty($this->proxy)) {
$class = $this->proxyClass;
$this->proxy = new $class;
}
$this->proxy->age = $age;
}
}
The usage you gave would make no sense for this discussion (the
difference in constructor implementations). However, in the proxy
example above, it makes a significant difference. If you didn't put
it in the abstract class/interface, you could get a fatal on
instantiation. Not saying it's hugely significant, but it's a valid
use-case.
First, if I consume a system that has practiced this abstract ctor in
abstract class thing, I cannot create valid subtypes on my own in PHP 5.4.
The above will throw an E_FATAL.
As it should, since you have a contract expecting what the subtype
doesn't implement.
Secondly, (AND WORSE) is that by simply removing the abstract keyword from
the abstract classes __construct(), I get completely different behavior,
everything works without E_FATAL.
You should get different behavior. Without Abstract, you no longer
have a contract, so there's nothing to be enforced. Saying that's a
problem is like saying "But my code works if I remove the private
keyword from the method declaration". It changes the meaning of the
construct significantly... (whether abstract/inferface constructors
should be allowed is another story, but if it is, you should get the
fatal)...
Thirdly, (EVEN WORSE!), is that if I comment out the $name, $age part of the
NamedAgedPerson (see: COMMENT OUT HERE above), no E_FATAL is thrown on
grandchildren who violate this abstract ctor.
Now that's a huge problem. All descendants should be forced to the
same contract... I'd classify that as a bug...
So, if we choose to do this wrong+wrong=~right thing, we should at least do
it correctly/consistently all the way through the inheritance type
hierarchy. Don't you think?
Agree
Of course, this argument is mostly pedantic to be honest, as I'd be
instructing people to never attempt to put abstract ctors in abstract
classes. I won't be doing that either, but, the argument is largely that
the engine shouldn't be doing these kind of things to begin off with.
I don't know. In PHP, a method is a method. Sure, some are called
specially by the engine, but why should the constructor be any
difference (conceptually). Sure, you usually type-hint against
interfaces after construction, but you clearly don't have to. You
could argue if it belongs or not, and I think that's worth talking
about. But if you want consistency, it should either apply to all
engine-called magic methods or none of them. The two cases above
(__toString and __invoke) could be solved with a pair of interfaces
(stringable and invokable possibly) to use instead of the methods.
Then simply remove all magic methods from interfaces... Honestly, I
don't feel strongly either way (keep all or remove all), but I think
it should be one or the other, not both...
Also, I'd has to see this kind of decision impacting future decisions (like
this) b/c it was "ratified" in 5.4.
Again, no argument at all.
Anthony
One point that was missed, that I'd like to reiterate is that:
Again, CTOR is not "special" b/c it's a PHP "magic
method" its "special" because it is shares qualities
of being a static method as well as an instance method.
That said, the constructor is not just another instance method. Put
another way, constructors are static methods that have instance level
visibility access to the newly created subtype via $this.
How are they static? They are not called by $obj->__construct() as
normal instance methods are, they are called by $obj = new
ConcreteType() (and the engine looks for a __construct() implementation
upwards from concrete to abstract, if it exists).
So, since PHP lets you do bad things in the first place (like have
constructors and static methods in interfaces, and abstract ctors in
abstract classes), we follow that up with another "bad" of breaking general
LSP expectations of how things work?Isn't this trying to make two wrongs make a right?
That's why I tossed out the concept of raising a notice or
E_STRICT
error if a constructor is used in an interface/declared abstract.
That too would be a massive BC break.
Lets look at that, the proxy pattern that is. This is no longer valid usage
in 5.4:That's not a proxy. That's a Bridge or Adapter pattern. Proxy
usually delays instantiation until first call (See
http://sourcemaking.com/design_patterns/proxy ). So in your example,
the proxy class would be:class ProxyNamedAgedPerson extends NamedAgedPerson {
public $proxy = null;
...
The usage you gave would make no sense for this discussion (the
difference in constructor implementations). However, in the proxy
example above, it makes a significant difference. If you didn't put
it in the abstract class/interface, you could get a fatal on
instantiation. Not saying it's hugely significant, but it's a valid
use-case.
For the intents and purposes of this argument, we are using a proxy here
- and, I'd argue that this particular pattern is central to the problem
at hand. (Doctrine2 also uses this pattern, to open up access to a
entity's members as well as to provide lazy loading.)
The only real requirement is that a proxy object implements all of the
same interfaces/parent-types as the subject object (compile time
checking). This ensures that for any "instanceof Subject" that you
find, objects of type SubjectProxy and type Subject pass this is_a test,
and can exhibit the expected behavior.
A way of obtaining this cleanly is through composition. While you could
have a method outside of the constructor handle the subject reference,
the cleanest approach is:
class SubjectProxy extends Subject {
protected $subject = null;
public function __construct(Subject $subject) {
$this->subject = $subject;
}
}
I shouldn't have to care if Subject has a __construct somewhere else
because only SubjectProxy knows how to instantiate subtypes of
SubjectProxy in a stateful and correct way.
First, if I consume a system that has practiced this abstract ctor in
abstract class thing, I cannot create valid subtypes on my own in PHP 5.4.
The above will throw an E_FATAL.As it should, since you have a contract expecting what the subtype
doesn't implement.
So, what you're promoting is that a parent type can enforce the way
subtypes are created.
This is contrary to all understanding of class based implementations (to
my knowledge): java or c# as far as I've checked.
Put yet another way:
Concrete types define ctors in accordance with their own needs, not the
needs of their parents or grandparents.
Of course, this argument is mostly pedantic to be honest, as I'd be
instructing people to never attempt to put abstract ctors in abstract
classes. I won't be doing that either, but, the argument is largely that
the engine shouldn't be doing these kind of things to begin off with.I don't know. In PHP, a method is a method. Sure, some are called
specially by the engine, but why should the constructor be any
difference (conceptually). Sure, you usually type-hint against
See the above comments as per why the ctors are considered special.
interfaces after construction, but you clearly don't have to. You
could argue if it belongs or not, and I think that's worth talking
about. But if you want consistency, it should either apply to all
We couldn't consider this until php 6 though as it represents a massive
BC break.
engine-called magic methods or none of them. The two cases above
(__toString and __invoke) could be solved with a pair of interfaces
(stringable and invokable possibly) to use instead of the methods.
Then simply remove all magic methods from interfaces... Honestly, I
don't feel strongly either way (keep all or remove all), but I think
it should be one or the other, not both...
If the honest-to-goodness goal is to be able to create dynamic types and
be able to bypass their constructor, use the facilities that are (now)
there for that:
http://us2.php.net/manual/en/reflectionclass.newinstancewithoutconstructor.php
or, we can drop that functionality down into core by reusing an existing
keyword:
$x = new Foo(empty); // bypass any __construct
Either way, my argument stands that we shouldn't be bucking the trend
when we've been so good at adhereing to a sane is_a model with respect
to our class/object model.
-ralph
hi,
On Thu, Nov 17, 2011 at 10:12 PM, Ralph Schindler
ralph@ralphschindler.com wrote:
Hey All,
Concerning RC1, __construct() and https://wiki.php.net/rfc/prototype_checks
I think we need to round out this discussion on the __construct() signature
checking in 5.4. The current behavior (RC1) feels very wrong with respect
to PHP's class based, single inheritance, multiple interface, no method
overloading, allowed method-overriding nature.Constructors themselves, specifically in PHP, are not explicitly statically
marked, but are "special" (even though they are part of the instance API,
and are callable- we'll consider this an implementation detail). In
general, constructor signatures have never been (and rightfully so) governed
by the LSP. Why? For a few reasons:1) before calling new(), you actually don't have a subtype (yet)- the
subtype is a product of new()2) when you call new(), you know the exact subtype you're dealing with.
3) only subtypes who implement or override __construct() can know how to
prepare a stateful subtype. In other words, parents can't know how to
create subtypes who implement __construct() and subtypes shouldn't be
concerned with the implementation details of how a parent creates types when
a subtype has overridden __construct();4) LSP deals with behaviors and expectations of types (read: objects),
since the constructor is generally considered "static" in nature, a form of
factory specific to the type its defined within, and since in PHP you can't
do (SomeClass instanceof SomeType) and since (is_a('SomeClass', 'SomeType')
doesn't make much sense in the context of classes), the constructor is not
subject to instance "behavior" rules.In general, developers shouldn't be putting abstract constructors inside an
abstract class nor should they be putting constructors inside interfaces-
this is a well accepted bad practice. (Neither c# or java allow this- of
course they do have the ability to overload methods in classes for the
record. In fact, static methods are not allowed either.)That said, we really should consider removing this limitation from
constructor signature checking. Not only is it a BC break, it doesn't make
sense in the context of PHP.
<?php
class A {
function __construct($a, $b) {
}
}
class B extends A{
function __construct($a) {
}
}
works.
While the following does not:
Abstract class A {
abstract function __construct($a, $b);
}
class B extends A{
function __construct($a) {
}
}
does not and it is per definition correct. Abstract defines the
signature and ensures that the signature are 100% the same.
We had a long discussion about that a couple of months ago. The main
disagreement was about the interpretation of the definition of
abstract. Abstract clearly talks about signature matching, as I (and
quite a lot of other) read it.
Cheers,
Pierre
@pierrejoye | http://blog.thepimp.net | http://www.libgd.org
Hey Pierre,
My perspective and expectations are framed by all sorts of existing
literature as well as the discussions on this list. It saddens me that
you did not address any of the points I've brought up. And, I simply
cannot tell what basis you have for your interpretation and opinion. It
seems more like you're basing your interpretation on preference alone.
The facts are this:
- What we know and have been told is that PHP's signature checking is
governed by Liskov Substitution Principle. There are many references to
this in the list.
I've made my argument based on the LSP.
http://marc.info/?r=1&w=2&q=b&l=php-internals&s=lsp
- "Abstract Constructors" do not exist in any other language, for all
intents and purposes, it's something we've invented:
http://www.google.com/search?&q=%22abstract+constructor%22
That's fine, so now the question is, how does this thing we've invented
play in with our current implementation of a class based object model
and how does it meet developer preexisting expectations?
- In places that have no formal expectation and understanding in other
programming languages, we've OFTEN favored looseness over strictness
Even you Pierre, once promoted "looseness"
http://news.php.net/php.internals/25089 (Pierre)
- We should meet everyone's existing expectations, Rasmus talks about this:
http://news.php.net/php.internals/25090 (Rasmus)
and this is the general expectations:
http://www.google.com/search?q=constructor+liskov
<?php
class A {
function __construct($a, $b) {
}
}class B extends A{
function __construct($a) {
}
}works.
While the following does not:
Abstract class A {
abstract function __construct($a, $b);
}class B extends A{
function __construct($a) {
}
}
Which makes no sense b/c this does work:
abstract class A {
abstract public function __construct($one, $two, $three);
}
class C extends A {
public function __construct($one, $two, $three) {}
}
class D extends C {
public function __construct($foo) {}
}
I assume this is b/c only one ->ce_parent is checked for "definition
correctness" or whatever this new feature is we're calling ... but more
to the point, do we really want to recurse every parent definition
looking for an abstract __construct() and checking the signature at runtime?
does not and it is per definition correct. Abstract defines the
signature and ensures that the signature are 100% the same.
Where do you get your foundational basis for this? What rules outside
of the LSP are we looking at to make this determination?
And, can you honestly say that of the global community of developers,
this is the expected outcome?
We had a long discussion about that a couple of months ago. The main
disagreement was about the interpretation of the definition of
abstract. Abstract clearly talks about signature matching, as I (and
quite a lot of other) read it.
According to the RFC (https://wiki.php.net/rfc/prototype_checks) a
consensus was never reached - and more discussion was needed (which as
of the date of the RFC and timestamps in the mailing list, has not
happened).
-ralph
Hey Ralph,
i think your argument on this is wrong. If we go your road:
- Fact: PHP follows the LSP
- Fact: The constructor is not part of the type, but a factory method.
- Conclusion: constructors are not allowed to be part of the interface or
abstract classes.
Reversing the argument, by introducing an abstract or interfaced
constructor you actually make the constructor part of the type, hence LSP
applies. Since PHP allows this and follows LSP, this has to be enforced.
greetings,
Benjamin
On Fri, Nov 18, 2011 at 6:40 PM, Ralph Schindler
ralph@ralphschindler.comwrote:
Hey Pierre,
My perspective and expectations are framed by all sorts of existing
literature as well as the discussions on this list. It saddens me that you
did not address any of the points I've brought up. And, I simply cannot
tell what basis you have for your interpretation and opinion. It seems more
like you're basing your interpretation on preference alone.The facts are this:
- What we know and have been told is that PHP's signature checking is
governed by Liskov Substitution Principle. There are many references to
this in the list.I've made my argument based on the LSP.
http://marc.info/?r=1&w=2&q=b&**l=php-internals&s=lsphttp://marc.info/?r=1&w=2&q=b&l=php-internals&s=lsp
- "Abstract Constructors" do not exist in any other language, for all
intents and purposes, it's something we've invented:http://www.google.com/search?&**q=%22abstract+constructor%22http://www.google.com/search?&q=%22abstract+constructor%22
That's fine, so now the question is, how does this thing we've invented
play in with our current implementation of a class based object model and
how does it meet developer preexisting expectations?
- In places that have no formal expectation and understanding in other
programming languages, we've OFTEN favored looseness over strictnessEven you Pierre, once promoted "looseness"
http://news.php.net/php.**internals/25089http://news.php.net/php.internals/25089(Pierre)
- We should meet everyone's existing expectations, Rasmus talks about
this:http://news.php.net/php.**internals/25090http://news.php.net/php.internals/25090(Rasmus)
and this is the general expectations:
http://www.google.com/search?**q=constructor+liskovhttp://www.google.com/search?q=constructor+liskov
http://stackoverflow.com/questions/5490824/should-
constructors-comply-with-the-**liskov-substitution-principlehttp://stackoverflow.com/questions/5490824/should-constructors-comply-with-the-liskov-substitution-principle<?php
class A {
function __construct($a, $b) {
}
}class B extends A{
function __construct($a) {
}
}works.
While the following does not:
Abstract class A {
abstract function __construct($a, $b);
}class B extends A{
function __construct($a) {
}
}Which makes no sense b/c this does work:
abstract class A {
abstract public function __construct($one, $two, $three);
}
class C extends A {
public function __construct($one, $two, $three) {}
}
class D extends C {
public function __construct($foo) {}
}I assume this is b/c only one ->ce_parent is checked for "definition
correctness" or whatever this new feature is we're calling ... but more to
the point, do we really want to recurse every parent definition looking for
an abstract __construct() and checking the signature at runtime?does not and it is per definition correct. Abstract defines the
signature and ensures that the signature are 100% the same.
Where do you get your foundational basis for this? What rules outside of
the LSP are we looking at to make this determination?And, can you honestly say that of the global community of developers, this
is the expected outcome?We had a long discussion about that a couple of months ago. The main
disagreement was about the interpretation of the definition of
abstract. Abstract clearly talks about signature matching, as I (and
quite a lot of other) read it.According to the RFC (https://wiki.php.net/rfc/**prototype_checkshttps://wiki.php.net/rfc/prototype_checks)
a consensus was never reached - and more discussion was needed (which as of
the date of the RFC and timestamps in the mailing list, has not happened).-ralph
Comments Inline
- What we know and have been told is that PHP's signature
checking is governed by Liskov Substitution Principle. There are
many references to this in the list.
Except that signature checking is not needed for LSP to function. You
can write all of your code using duck typing without interfaces and
abide by LSP 100%. In fact, you can do things like override a
function with a signature of foo($bar, $baz) with one of foo(), and
STILL have it be LSP compliant due to the dynamic ways PHP handles
arguments.
So LSP may be a motivator, but ti's not the reason. What could be a
reason is to enable Design-By-Contract style development where the
interface is the defined contract. Now since PHP doesn't support
dynamic introspection of the method at run-time, the signature is the
only way of determining if the method abides by the contract.
- "Abstract Constructors" do not exist in any other language, for all
intents and purposes, it's something we've invented:
True, however in most other language constructors are not actual
methods, but a pseudo method on the object that can't be called
explicitly aside form new
(obviously not true for all, but most of
the big ones).
Additionally, PHP is quite different from a fair bit of other
languages in that you can do things like new $class()
. Java and C#
don't allow this. So to them, it's not an LSP violation or even
necessary to have it abstract since you know the subtype your
instantiating. However, in PHP that's not true. You can do
variable-class-names. So that eliminates the assertion that you
know the subtype your instantiating. You don't, and you can't.
But you can check the interface on the class name in the variable
(which is what I did in that library I posted before). So by
specifying the constructor as abstract/interface is one way of adding
safety when you accept a class name and do dynamic instantiation...
http://www.google.com/search?&q=%22abstract+constructor%22
That's fine, so now the question is, how does this thing we've invented play
in with our current implementation of a class based object model and how
does it meet developer preexisting expectations?
There are two ways to see it. Either constructors are really just a
method that's called by the engine, or they are not a method and are
special "blocks" which are not callable in userland. However, since I
can legally call $obj->__construct()
, I think it's pretty safe to
say that it's considered a real method in PHP. And real methods are
specifiable in abstract/interfaces.
So really we didn't invent anything at all. The "invention" is that
the constructor is a method (with all the caveats that apply)...
- In places that have no formal expectation and understanding in other
programming languages, we've OFTEN favored looseness over strictnessEven you Pierre, once promoted "looseness"
http://news.php.net/php.internals/25089 (Pierre)
Well, looseness over strictness unless strictness is specified. Look
at the class based type hints. That's strict. Interfaces and
signature checking are absolutely strict. Considering that the use is
optional, and the construct that's using it is normally associated
with strictness that it's ok that the constructor check is strict...
Again, I'm not arguing the design decision of putting a constructor in
an interface. I'm talking about what would be expected if you did, or
more specifically if you're allowed to...
On Fri, Nov 18, 2011 at 6:40 PM, Ralph Schindler
ralph@ralphschindler.com wrote:
Hey Pierre,
My perspective and expectations are framed by all sorts of existing
literature as well as the discussions on this list. It saddens me that you
did not address any of the points I've brought up. And, I simply cannot
tell what basis you have for your interpretation and opinion. It seems more
like you're basing your interpretation on preference alone.
Again, no, I do not base my opinion on personal preferences. I only do
not buy too much from the 'let do it the PHP way' when it comes to OO,
and yes I slightly move to the strict side.
The facts are this:
- What we know and have been told is that PHP's signature checking is
governed by Liskov Substitution Principle. There are many references to
this in the list.
And for this exact case, it is correctly implemented as of now.
- "Abstract Constructors" do not exist in any other language, for all
intents and purposes, it's something we've invented:http://www.google.com/search?&q=%22abstract+constructor%22
That's fine, so now the question is, how does this thing we've invented play
in with our current implementation of a class based object model and how
does it meet developer preexisting expectations?
A construct is a specialized method, that does not change a yota what
abstract methods are.
Even you Pierre, once promoted "looseness"
http://news.php.net/php.internals/25089 (Pierre)
Only the doom never changes their mind :-D
- We should meet everyone's existing expectations, Rasmus talks about this:
We should meet consistency with standard behaviors and then it is easy
to learn, for everyone.
Cheers,
Pierre
@pierrejoye | http://blog.thepimp.net | http://www.libgd.org
That said, we really should consider removing this limitation from constructor
signature checking. Not only is it a BC break, it doesn't make sense in the
context of PHP.
I agree, we should not be having that check for constructors based on
the points you raised.
cheers,
Derick
--
http://derickrethans.nl | http://xdebug.org
Like Xdebug? Consider a donation: http://xdebug.org/donate.php
twitter: @derickr and @xdebug
I strongly disagree, this encourages bad practices. We could however
reduce the error level to warning.
That said, we really should consider removing this limitation from constructor
signature checking. Not only is it a BC break, it doesn't make sense in the
context of PHP.I agree, we should not be having that check for constructors based on
the points you raised.cheers,
Derick--
http://derickrethans.nl | http://xdebug.org
Like Xdebug? Consider a donation: http://xdebug.org/donate.php
twitter: @derickr and @xdebug--
--
Pierre
@pierrejoye | http://blog.thepimp.net | http://www.libgd.org
Internals:
Time to summarize.
It is clear to me that internals is divided on this issue. I don't
think it's a large enough issue to drag on, even when I disagree with it
- both theoretically and in practice.
For most OO developer, putting ctors as an abstract or in an interface
would not happen anyway, so this does not affect them.
** The current change represents a minor break in BC, that should be
noted in the manual. **
Also, a decision needs to be made on what to do with grandchildren. As
I mentioned, the following produces no E_FATAL and no warnings:
abstract class A { abstract public function __construct($a, $b); }
class B extends A { public function __construct($a, $b) {} }
class C extends B { public function __construct(ArrayObject $d) {}
While this is correct behavior to me (ability for concrete to use its
own ctor), using the current logic strict signature checking enforced
from an abstract, then the above is also wrong.
** Can we decide what to do with that situation? **
I strongly disagree, this encourages bad practices. We could however
reduce the error level to warning.
I think this is a sufficient compromise- I don't see anything E_FATAL
about a signature change in ctors (I actually see nothing wrong with it,
but it's clear the community is divided there).
** Can we make that change? **
Thanks,
-ralph
Ralph:
From where I'm sitting, I can see a few sane alternatives (there may
be more, but here are the options I can see):
Option 1. Remove signature checking from constructors all together.
(I don't care for this, but whatever). Additionally, since it's not
enforced, perhaps an E_DEPRECATED
or E_STRICT
error should be raised
on definition, as it is superfluous...
Option 2. Fix grandchild signature checking to be inline for how
signatures work with other methods.
Personally, I think option 2 is the better one. I see it being odd
and inconsistent that all methods work one way, and constructors work
differently. But that's just my feeling (and I know others disagree
there).
And please don't reduce the error level of a signature change (as it
would introduce even more inconsistency)...
Just my $0.02...
Anthony
On Wed, Nov 23, 2011 at 1:25 PM, Ralph Schindler
ralph@ralphschindler.com wrote:
Internals:
Time to summarize.
It is clear to me that internals is divided on this issue. I don't think
it's a large enough issue to drag on, even when I disagree with it - both
theoretically and in practice.For most OO developer, putting ctors as an abstract or in an interface would
not happen anyway, so this does not affect them.** The current change represents a minor break in BC, that should be noted
in the manual. **Also, a decision needs to be made on what to do with grandchildren. As I
mentioned, the following produces no E_FATAL and no warnings:abstract class A { abstract public function __construct($a, $b); }
class B extends A { public function __construct($a, $b) {} }
class C extends B { public function __construct(ArrayObject $d) {}While this is correct behavior to me (ability for concrete to use its own
ctor), using the current logic strict signature checking enforced from an
abstract, then the above is also wrong.** Can we decide what to do with that situation? **
I strongly disagree, this encourages bad practices. We could however
reduce the error level to warning.I think this is a sufficient compromise- I don't see anything E_FATAL about
a signature change in ctors (I actually see nothing wrong with it, but it's
clear the community is divided there).** Can we make that change? **
Thanks,
-ralph
Ralph:
From where I'm sitting, I can see a few sane alternatives (there may
be more, but here are the options I can see):Option 1. Remove signature checking from constructors all together.
(I don't care for this, but whatever). Additionally, since it's not
enforced, perhaps anE_DEPRECATED
orE_STRICT
error should be raised
on definition, as it is superfluous...Option 2. Fix grandchild signature checking to be inline for how
signatures work with other methods.Personally, I think option 2 is the better one. I see it being odd
and inconsistent that all methods work one way, and constructors work
differently. But that's just my feeling (and I know others disagree
there).And please don't reduce the error level of a signature change (as it
would introduce even more inconsistency)...Just my $0.02...
Anthony
On Wed, Nov 23, 2011 at 1:25 PM, Ralph Schindler
ralph@ralphschindler.com wrote:Internals:
Time to summarize.
It is clear to me that internals is divided on this issue. I don't think
it's a large enough issue to drag on, even when I disagree with it - both
theoretically and in practice.For most OO developer, putting ctors as an abstract or in an interface would
not happen anyway, so this does not affect them.** The current change represents a minor break in BC, that should be noted
in the manual. **Also, a decision needs to be made on what to do with grandchildren. As I
mentioned, the following produces no E_FATAL and no warnings:abstract class A { abstract public function __construct($a, $b); }
class B extends A { public function __construct($a, $b) {} }
class C extends B { public function __construct(ArrayObject $d) {}While this is correct behavior to me (ability for concrete to use its own
ctor), using the current logic strict signature checking enforced from an
abstract, then the above is also wrong.** Can we decide what to do with that situation? **
I strongly disagree, this encourages bad practices. We could however
reduce the error level to warning.I think this is a sufficient compromise- I don't see anything E_FATAL about
a signature change in ctors (I actually see nothing wrong with it, but it's
clear the community is divided there).** Can we make that change? **
Thanks,
-ralph
What is the normal way for userland developers to learn about
constructors? Do they know about LSP? If they do, does it apply to
constructors?
A comment on StackOverflow [1]
"At its heart LSP is about interfaces and contracts as well as how to
decided when to extend a class vs. use another strategy such as
composition to achieve your goal."
I am self taught. It was always my understanding that the constructor
was special in that it is, essentially, a magic method that can only
respond to the new keyword. Being able to call the constructor
directly, ..., that does feel a little odd as I can't actually
construct a new instance that way.
Using an interface to enforce the sig is fine. That's the exact nature
of an interface, to enforce the contract by design and if the
StackOverflow comment is accurate, then my understanding of interfaces
is fine with regard to LSP.
An abstract class, by its very nature is not formed. It isn't a
contract. It is an idea and the concrete doesn't need to fit
perfectly. OK. There is some things that are fixed, method signatures
most accept the parameters defined in the subclass but can add more
(as I understand things).
But is the signature of the constructor in an abstract class to be
enforced? I feel that it shouldn't be. It is a special method.
WikiPedia [2] has an article on Constructor Overloading. It says
"Constructors, used to create instances of an object, may also be
overloaded in some object oriented programming languages.". OK, so
WikiPedia may have a different opinion tomorrow.
Whilst we don't support overloading in the same way (I think we can
simulate it with __call() easily enough with ReflectionParameter being
my friend here maybe).
We, DO, provide the tools to allow overloading and non LSP coding
practises. Is it a great leap to also allow the same flexibility with
constructors?
Regards,
Richard Quadling.
[1] http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle
[2] http://en.wikipedia.org/wiki/Constructor_overloading#Constructor_overloading
Richard Quadling
Twitter : EE : Zend : PHPDoc : Fantasy Shopper
@RQuadling : e-e.com/M_248814.html : bit.ly/9O8vFY : bit.ly/lFnVea :
fan.sh/6/370
Something else to consider:
Right now, Constructors are checked on interfaces. See the following
two examples:
http://codepad.viper-7.com/9IAGNP
http://codepad.viper-7.com/edokLi
So right now, interfaces are enforcing constructors fully (in 5.3).
Which makes more sense: Having abstract methods behaving the same as
the interface declaration (to grand-children, etc), or having abstract
declarations behave differently and ignoring abstract constructors?
My vote is for consistency (since the concept between abstract methods
and interface methods is very similar)...
Anthony
Ralph:
From where I'm sitting, I can see a few sane alternatives (there may
be more, but here are the options I can see):Option 1. Remove signature checking from constructors all together.
(I don't care for this, but whatever). Additionally, since it's not
enforced, perhaps anE_DEPRECATED
orE_STRICT
error should be raised
on definition, as it is superfluous...Option 2. Fix grandchild signature checking to be inline for how
signatures work with other methods.Personally, I think option 2 is the better one. I see it being odd
and inconsistent that all methods work one way, and constructors work
differently. But that's just my feeling (and I know others disagree
there).And please don't reduce the error level of a signature change (as it
would introduce even more inconsistency)...Just my $0.02...
Anthony
On Wed, Nov 23, 2011 at 1:25 PM, Ralph Schindler
ralph@ralphschindler.com wrote:Internals:
Time to summarize.
It is clear to me that internals is divided on this issue. I don't think
it's a large enough issue to drag on, even when I disagree with it - both
theoretically and in practice.For most OO developer, putting ctors as an abstract or in an interface would
not happen anyway, so this does not affect them.** The current change represents a minor break in BC, that should be noted
in the manual. **Also, a decision needs to be made on what to do with grandchildren. As I
mentioned, the following produces no E_FATAL and no warnings:abstract class A { abstract public function __construct($a, $b); }
class B extends A { public function __construct($a, $b) {} }
class C extends B { public function __construct(ArrayObject $d) {}While this is correct behavior to me (ability for concrete to use its own
ctor), using the current logic strict signature checking enforced from an
abstract, then the above is also wrong.** Can we decide what to do with that situation? **
I strongly disagree, this encourages bad practices. We could however
reduce the error level to warning.I think this is a sufficient compromise- I don't see anything E_FATAL about
a signature change in ctors (I actually see nothing wrong with it, but it's
clear the community is divided there).** Can we make that change? **
Thanks,
-ralphWhat is the normal way for userland developers to learn about
constructors? Do they know about LSP? If they do, does it apply to
constructors?A comment on StackOverflow [1]
"At its heart LSP is about interfaces and contracts as well as how to
decided when to extend a class vs. use another strategy such as
composition to achieve your goal."I am self taught. It was always my understanding that the constructor
was special in that it is, essentially, a magic method that can only
respond to the new keyword. Being able to call the constructor
directly, ..., that does feel a little odd as I can't actually
construct a new instance that way.
Using an interface to enforce the sig is fine. That's the exact nature
of an interface, to enforce the contract by design and if the
StackOverflow comment is accurate, then my understanding of interfaces
is fine with regard to LSP.An abstract class, by its very nature is not formed. It isn't a
contract. It is an idea and the concrete doesn't need to fit
perfectly. OK. There is some things that are fixed, method signatures
most accept the parameters defined in the subclass but can add more
(as I understand things).But is the signature of the constructor in an abstract class to be
enforced? I feel that it shouldn't be. It is a special method.WikiPedia [2] has an article on Constructor Overloading. It says
"Constructors, used to create instances of an object, may also be
overloaded in some object oriented programming languages.". OK, so
WikiPedia may have a different opinion tomorrow.Whilst we don't support overloading in the same way (I think we can
simulate it with __call() easily enough with ReflectionParameter being
my friend here maybe).We, DO, provide the tools to allow overloading and non LSP coding
practises. Is it a great leap to also allow the same flexibility with
constructors?Regards,
Richard Quadling.
[1] http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle
[2] http://en.wikipedia.org/wiki/Constructor_overloading#Constructor_overloadingRichard Quadling
Twitter : EE : Zend : PHPDoc : Fantasy Shopper
@RQuadling : e-e.com/M_248814.html : bit.ly/9O8vFY : bit.ly/lFnVea :
fan.sh/6/370