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

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

Reply via email to