On Thu, Nov 23, 2023 at 5:31 PM Robert Landers <landers.rob...@gmail.com>
wrote:

> Hello Internals,
>
> As you may know, an inherited method cannot reduce the visibility of
> an overridden method. For example, this results in a fatal error
> during compilation:
>
> class P {
>     public function hello($name = 'world') {
>         echo "hello $name\n";
>     }
> }
>
> class C extends P {
>     private function hello($name = 'world') {
>         parent::hello($name);
>         echo "goodbye $name\n";
>     }
> }
>
> However, we can make certain methods private anyway, namely,
> constructors (I haven't gone hunting for other built-in methods yet).
> This is perfectly allowed:
>
> class P {
>     public function __construct($name = 'waldo') {
>         echo "hello $name\n";
>     }
> }
>
> class C extends P {
>     private function __construct($name = 'world') {
>         parent::__construct($name);
>         echo "goodbye $name\n";
>     }
> }
>
> To my somewhat trained eye, this appears to violate the Liskov
> Substitution Principle, for example, this now can have hidden errors:
>
> function create(P $class) {
>     return new (get_class($class))();
> }
>
> proven by:
>
> $c = (new ReflectionClass(C::class))
>   ->newInstanceWithoutConstructor();
>
> create($c);
>
> Even though we thought we knew that the constructor was declared public.
>
> I'd like to propose an RFC to enforce the covariance of constructors
> (just like is done for other methods), to take effect in PHP 9, with a
> deprecation notice in 8.3.x.
>
> I'm more than happy to implement it.
>
> Does anyone feel strongly about this one way or the other?
>

Constructors are an implementation detail of a specialized class and as
such they're not subject to LSP because the goal of LSP is to be able to
make sure that any object of a given type hierarchy can be used to
accomplish a certain behavior. If you take a step back from PHP's
dynamic nature and think about LSP from a more pure type system, the fact
you're expecting an object of type C, but then you completely disregard
everything about the object itself and dive into it's metadata to build
another object, that's the moment you're no longer playing by the rules of
OOP. It's like those mathematical equations that prove that 1 = 2, they all
have one thing in common: they end up dividing by 0 at some point.

OOP here dictates that you should reach for patterns like Builder, Abstract
Factory or similar. That way you constraint yourself to the rules of OOP
and you won't get weird outcomes.

>From another point of view, when a type is expected by a function or
method, all we can expect from it is whatever was defined as the blueprint
(class/interface) of that object and the __construct() is a special method
that is not assumed to be part of that blueprint because it's not
reasonable to do `$object->__construct();` after receiving an object. As
such, a constructor cannot break LSP because the constructor is not part of
the object's API from a "receptor" point of view.

I don't have a vote so take my opinion with a bucket of salt, but if I
could I would definitely vote against such RFC.


-- 
Marco Deleu

Reply via email to