On Fri, Nov 24, 2023 at 2:30 PM Deleu <deleu...@gmail.com> wrote:
>>
>> Hey Mike,
>>
>> Using a static method to enforce LSP when it should have been enforced
>> in the first place, merely proves my point that it violates LSP. I
>> don't know how else to spell it out, I guess we can try this one next:
>>
>> class A {
>>     final public static function foo() {
>>         return new static();
>>     }
>> }
>>
>> class B extends A {
>>     public function __construct(private string $name) {}
>> }
>>
>> $a = B::foo();
>>
>> It just plainly violates LSP and you can't write safe code that
>> touches constructors. I don't know how else to spell this out.
>
>
> Hey Robert, as we've established we may not agree on this subject and I 
> really don't want to sound like I want to convince you, so feel free to 
> ignore my email or reply that this isn't helping. My goal here is merely to 
> try and address what I think the error is in case it gives you some clarity.
> -----
>
> In this snippet, you're using `new static()` which again is something 
> PHP-specific and not 100% OOP definition. PHP has a friendly syntax to help 
> you figure out what the class name is during runtime, this again is metadata. 
> With these tools, I believe you are able to accomplish what you want by 
> explicitly adding the constructor to your public API.
>
> ```
> abstract class Template
> {
>     abstract public function __construct(string 
> $explicitlyDefinitionOfPublicAPI);
> }
>
> class A extends Template {
>     public function __construct(string|null $ignore) {}
>
>     final public static function foo()
>     {
>         return new static('parameter defined from A');
>     }
> }
>
> class B extends A {
>     public function __construct(public string $name) {}
> }
>
> $b = B::foo('test');
> var_dump($b->name);
> ```
>
> Let's break this down. The `Template` class uses the Abstract Template 
> pattern to define a public interface of the inheritance chain. As such, it 
> offers the ability to opt-in to something that isn't standard: The 
> Constructor method being part of a class Public API.
>
> Two things happen from this. We are forced to implement a constructor on 
> class A otherwise we get:
> Fatal error: Class A contains 1 abstract method and must therefore be 
> declared abstract or implement the remaining methods (Template::__construct) 
> in /in/7qX9j on line 8
>
> And we inherit the Constructor from A in B or we need to override it. My 
> conclusion here is that 1) if you know what you're doing and 2) you want to 
> make a constructor's object part of your public API, and therefore, respect 
> LSP, you are free to do so.
>
> - Why shouldn't this be the default behavior in PHP?
> Let's ignore for a second 28 years of breaking change and focus on the OOP 
> principle
>
>
> ```
> <?php
>
> abstract class Queue
> {
>
>     final public function serialize(array $message): string
>     {
>         return json_encode($message);
>     }
>
>     abstract public function push(array $message): void;
> }
>
> final class SqsQueue extends Queue
> {
>     public function __constructor(private \Aws\Sqs\Client $client, private 
> string $queue) {}
>
>     public function push(array $message): void
>     {
>         $data = $this->serialize($message);
>
>         $client->sendMessage([
>             'QueueUrl' => $this->queue,
>             'Message' => $data,
>         ]);
>     }
> }
>
> final class RedisQueue extends Queue
> {
>     public function __constructor(private 
> \Illuminate\Redis\Connectors\PhpRedisConnector $connector, private array 
> $configuration, private array $options) {}
>
>     public function push(array $message): void
>     {
>         $connection = $this->connector->connect($this->configuration, 
> $this->options);
>
>         $data = $this->serialize($message);
>
>         $command = "redis.call('rpush', KEYS[1], ARGV[1])";
>
>         $connection->eval($command, 1, 'queues:my-queue', $data);
>     }
> }
> ```
>
> This is a text-book case of LSP. A class that expects to receive a `Queue` 
> object can freely use `push()` in a consistent and predictable manner, 
> allowing substitution as it sees fit. The object constructor is exempt from 
> LSP because it is the implementation detail of a particular class.
> RedisQueue needs to be able to communicate with Redis in order to provide a 
> queueing capability.
> SqsQueue needs to be able to communicate with AWS SQS in order to provide a 
> queuing capability.
>
> They have different dependencies/configurations and they wouldn't be able to 
> perform their capabilities if the language forced their constructor to follow 
> a single compatible signature.
>
> -----
>
> In conclusion, I think PHP has the best of both worlds. We get little helpers 
> to accommodate how OOP looks like in a dynamic script language (i.e. new 
> static()) and we have a fully functioning LSP that allows you to take 
> advantage of it however you see fit. The Queue example goes to show why 
> having a constructor as part of the public API of a class hierarchy would be 
> extremely bad, but PHP is nice enough to let you opt-in to it if you have 
> reasons to force a class hierarchy to have a single dependency signature.
>
>
> --
> Marco Deleu

Hello internals,

I won't be pursuing this as an RFC. It seems either an abstract class
or interface can force users to obey LSP, but it is purely opt-in. It
strikes a good balance, but does create some surprises when someone
comes along with a bug they wrote themselves into without realizing
it.

Thank you all for your discussion!

> Let's ignore for a second 28 years of breaking change and focus on the OOP 
> principle

I wish more people had that attitude on this list. :D I'm of the
opinion that you first decide to do something, then figure out how to
make it backward-compatible and maintain it. Take annotations, for
example, it was wanted, and then it was figured out how to (cleverly)
make them backward compatible. If people had gotten hung up on
backward compatibility or maintenance from the beginning, I doubt it
would have ever made it past that point. Now, annotations are
increasingly useful in PHP, from describing how to serialize an
object, to validations, to guards.

Robert Landers
Software Engineer
Utrecht NL

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

Reply via email to