Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:121808 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 74424 invoked from network); 24 Nov 2023 14:40:55 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 24 Nov 2023 14:40:55 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 1ED45180035 for ; Fri, 24 Nov 2023 06:41:00 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 4.0.0 (2022-12-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-2.1 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL,SPF_HELO_NONE, SPF_PASS autolearn=no autolearn_force=no version=4.0.0 X-Spam-Virus: No X-Envelope-From: Received: from mail-oo1-f45.google.com (mail-oo1-f45.google.com [209.85.161.45]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Fri, 24 Nov 2023 06:40:59 -0800 (PST) Received: by mail-oo1-f45.google.com with SMTP id 006d021491bc7-5844bc378feso1075180eaf.0 for ; Fri, 24 Nov 2023 06:40:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1700836854; x=1701441654; darn=lists.php.net; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=8+ALl7zoybhzT3apnnbVy3/3CRe9Pyu57wU0J8Gci28=; b=BiMJoXAuXPc9cq0ZfZy8CE13oKpNEhd5I8rW5ePS5okyiy6A8qUQtOkRjMbts5aB3m nlBoUt8gpYwPBBBbaCioNzE648STuZrz9pkQj0UjODR0psX2Uz5xBLqSUl4uy5ceLNA3 I/Dtupj6kFzqC4Qk3n0Fq8wt6UoFYQhtaX9Gi3bhG88sWZj7Ke+23Hb6TTQ0QOEhL5cH X0R3EdT3tEfQL5YxeB16109r3HYaNuNna2oodowpbyzzGnIW/z584RWtVP/NzPbxprHN otna2Gt3968dNfuw55tbDGIn6Z0oF1fZ45RakQfryOzRFnbnKn066haL+iuE6MGjnyuU VuZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700836854; x=1701441654; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=8+ALl7zoybhzT3apnnbVy3/3CRe9Pyu57wU0J8Gci28=; b=A9RAk37JYLhKdkFcOm+oNYr8B0vzgDHIObeVHdO1LUnhEdPmO9qbauW/Jgh1ptZtK0 cDPd19aIYx7tHq3lFVt1yaULF5aj2nLyUcv6cP7Xk9UZdF05ha+V5uV1oid5HPelLMEn 6jtHGfRWydw86sBTMvv601MdO7I5CNQU0NQQghxjZCm++uBFPzOz4a9D9BLg4FqOulmJ NI3wglq++n+mDv5XRr2V//25USKpnBTyGQdo75eeG1Ux+3jNPKr8O/EcB2yk78N37CDw mDgypEpUog+Zji2wCxj4d5nzJyQ1cwxiXnipYSIGAMBDiVmoJyY9CBWEOyXRl/VgYQrH NJkg== X-Gm-Message-State: AOJu0Yw+t07/z2u6zbTBJhK+OtEmttrBR66ZqKQlA5yy65JkHRcq4leA Q0igkPBK3j4fmJbCd94yDioF1TBlARvahu4MgNA= X-Google-Smtp-Source: AGHT+IF5hTfeLyFQKkM4hNSna9gZ5wIe2Vd1y6hczRBvCACzMUal4ixW+CbBRw6J+RNRlDzbfOl65QqH5z24g2pE+t4= X-Received: by 2002:a05:6820:162a:b0:58a:703e:fbf5 with SMTP id bb42-20020a056820162a00b0058a703efbf5mr3531067oob.5.1700836853654; Fri, 24 Nov 2023 06:40:53 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: Date: Fri, 24 Nov 2023 15:40:42 +0100 Message-ID: To: Deleu Cc: Mike Schinkel , internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP? From: landers.robert@gmail.com (Robert Landers) On Fri, Nov 24, 2023 at 2:30=E2=80=AFPM Deleu 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 =3D 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 r= eally don't want to sound like I want to convince you, so feel free to igno= re 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 yo= u 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 ex= plicitly adding the constructor to your public API. > > ``` > abstract class Template > { > abstract public function __construct(string $explicitlyDefinitionOfPu= blicAPI); > } > > 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 =3D B::foo('test'); > var_dump($b->name); > ``` > > Let's break this down. The `Template` class uses the Abstract Template pa= ttern to define a public interface of the inheritance chain. As such, it of= fers the ability to opt-in to something that isn't standard: The Constructo= r 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 dec= lared abstract or implement the remaining methods (Template::__construct) i= n /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, respec= t 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 OO= P principle > > > ``` > > 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, privat= e string $queue) {} > > public function push(array $message): void > { > $data =3D $this->serialize($message); > > $client->sendMessage([ > 'QueueUrl' =3D> $this->queue, > 'Message' =3D> $data, > ]); > } > } > > final class RedisQueue extends Queue > { > public function __constructor(private \Illuminate\Redis\Connectors\Ph= pRedisConnector $connector, private array $configuration, private array $op= tions) {} > > public function push(array $message): void > { > $connection =3D $this->connector->connect($this->configuration, $= this->options); > > $data =3D $this->serialize($message); > > $command =3D "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, al= lowing substitution as it sees fit. The object constructor is exempt from L= SP 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 hel= pers to accommodate how OOP looks like in a dynamic script language (i.e. n= ew static()) and we have a fully functioning LSP that allows you to take ad= vantage of it however you see fit. The Queue example goes to show why havin= g a constructor as part of the public API of a class hierarchy would be ext= remely bad, but PHP is nice enough to let you opt-in to it if you have reas= ons 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 OO= P 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