FAQ
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

Search Discussions

  • Anthony Ferrara at Nov 17, 2011 at 10:14 pm
    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
    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


    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php
  • Ralph Schindler at Nov 17, 2011 at 11:01 pm
    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#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
    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
  • Anthony Ferrara at Nov 18, 2011 at 4:08 am
    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
  • Ralph Schindler at Nov 18, 2011 at 5:18 am
    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
  • Pierre Joye at Nov 18, 2011 at 9:29 am
    hi,

    On Thu, Nov 17, 2011 at 10:12 PM, Ralph Schindler
    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,
  • Ralph Schindler at Nov 18, 2011 at 5:40 pm
    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:

    1) 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

    2) "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?

    3) 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)

    4) 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

    http://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_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
  • Benjamin Eberlei at Nov 18, 2011 at 6:37 pm
    Hey Ralph,

    i think your argument on this is wrong. If we go your road:

    1. Fact: PHP follows the LSP
    2. Fact: The constructor is not part of the type, but a factory method.
    3. 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
    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.

    The facts are this:

    1) 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<http://marc.info/?r=1&w=2&q=b&l=php-internals&s=lsp>

    2) "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<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?

    3) 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<http://news.php.net/php.internals/25089>(Pierre)

    4) We should meet everyone's existing expectations, Rasmus talks about
    this:

    http://news.php.net/php.**internals/25090<http://news.php.net/php.internals/25090>(Rasmus)

    and this is the general expectations:

    http://www.google.com/search?**q=constructor+liskov<http://www.google.com/search?q=constructor+liskov>

    http://stackoverflow.com/**questions/5490824/should-**
    constructors-comply-with-the-**liskov-substitution-principle<http://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_checks<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


    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php
  • Anthony Ferrara at Nov 18, 2011 at 6:40 pm
    Comments Inline
    1) 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.
    2) "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)...
    3) 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)
    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...
  • Pierre Joye at Nov 18, 2011 at 7:02 pm

    On Fri, Nov 18, 2011 at 6:40 PM, Ralph Schindler 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:

    1) 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.
    2) "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

    4) 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,
  • Derick Rethans at Nov 18, 2011 at 9:55 am

    On Thu, 17 Nov 2011, Ralph Schindler wrote:

    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 Joye at Nov 18, 2011 at 11:05 am
    I strongly disagree, this encourages bad practices. We could however
    reduce the error level to warning.
    On Fri, Nov 18, 2011 at 10:54 AM, Derick Rethans wrote:
    On Thu, 17 Nov 2011, Ralph Schindler wrote:

    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

    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php


    --
    Pierre

    @pierrejoye | http://blog.thepimp.net | http://www.libgd.org
  • Ralph Schindler at Nov 23, 2011 at 6:25 pm
    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? **
    On 11/18/11 5:05 AM, Pierre Joye wrote:
    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
  • Anthony Ferrara at Nov 23, 2011 at 6:37 pm
    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
    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? **
    On 11/18/11 5:05 AM, Pierre Joye wrote:

    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

    --
    PHP Internals - PHP Runtime Development Mailing List
    To unsubscribe, visit: http://www.php.net/unsub.php
  • Richard Quadling at Nov 24, 2011 at 10:48 am

    On 23 November 2011 18:37, Anthony Ferrara wrote:
    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
    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? **
    On 11/18/11 5:05 AM, Pierre Joye wrote:

    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
  • Anthony Ferrara at Nov 24, 2011 at 12:32 pm
    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
    On Thu, Nov 24, 2011 at 5:48 AM, Richard Quadling wrote:
    On 23 November 2011 18:37, Anthony Ferrara wrote:
    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
    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? **
    On 11/18/11 5:05 AM, Pierre Joye wrote:

    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

Related Discussions

Discussion Navigation
viewthread | post
Discussion Overview
groupphp-internals @
categoriesphp
postedNov 17, '11 at 9:12p
activeNov 24, '11 at 12:32p
posts16
users6
websitephp.net

People

Translate

site design / logo © 2022 Grokbase