Re: [RFC][Discussion] Why can constructors violate LSP?

From: Date: Thu, 23 Nov 2023 21:50:18 +0000
Subject: Re: [RFC][Discussion] Why can constructors violate LSP?
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-121792@lists.php.net to get a copy of this message
On Thu, Nov 23, 2023 at 10:30 PM Deleu <deleugyn@gmail.com> wrote: > > > > On Thu, Nov 23, 2023 at 5:31 PM Robert Landers <landers.robert@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 Thanks Marco, That's an interesting perspective and one I would agree with for the most part, especially if you take my illustration at face value. Where it gets weird/breaks down is when you have a class-string, that you assert is the correct type, and then try to instantiate it: // from somewhere $class = "C"; if(is_subclass_of($class, P::class)) { $example = new $class("world"); } If PHP didn't offer these built-in methods, then I would fully agree with you, but it does, which puts it into a weird position where sometimes a class is substitutable, and in this one special case, it is not. Robert Landers Software Engineer Utrecht NL

« previous php.internals (#121792) next »