Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:121807 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 70382 invoked from network); 24 Nov 2023 13:30:35 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 24 Nov 2023 13:30:35 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id F1694180048 for ; Fri, 24 Nov 2023 05:30:39 -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, HTML_MESSAGE,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-f44.google.com (mail-oo1-f44.google.com [209.85.161.44]) (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 05:30:39 -0800 (PST) Received: by mail-oo1-f44.google.com with SMTP id 006d021491bc7-58a7b55bef5so54896eaf.1 for ; Fri, 24 Nov 2023 05:30:34 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1700832633; x=1701437433; darn=lists.php.net; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=DYuBQV2RyCFZhSSyCnAubVdfRpTDr/NfxgUXG6Z+ueE=; b=Ao8jNKDrczeFWhGnlZE2dj3I50Kf7Ayab6qrZ8cR+xvtf6sTEidWV0ogs5ZtOe5tRY 98bxcwAhRToyIooYwNfEJOAGG7KBUEjKaJ60CjQBCnZ/1ImKoWM/bmXBtfDbBX83XF92 Fr9W3OJt1cBPhjlxCOty5AwDkVFSwWazxF/aZ14UNfoODelCUOtnMFjXdQ/XN7i6a409 pHfwyD0Tv8QXESfYJpKvO5XWOj1rWj4vT6enZ7mTkgsVcstOOHoDMJ4lxHqyPOPsnbLq DWhDtN7Fq9/aXkqVWpwyuTtbKJv4CdoznIiXTz5y6gMUbe8BrSKdGChs2PEanzzIQbgI zs1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700832633; x=1701437433; h=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=DYuBQV2RyCFZhSSyCnAubVdfRpTDr/NfxgUXG6Z+ueE=; b=uOQdxck02mxzl/Voi1yTL4PLmJD4E3Fqa4Eo52jvxUBcIo4HS7QbxWTxafqJf4PhE1 GALC1Xc6k+9clG+V4IK9N5NJSqznRIsDedsprKsz7L4kRTPeVz4V+o4IacPgLvF6ZQof sXnMvGJhokqU8Hgj3bJ7LQIIVGp3zbdnGGj2aEvARRE5aN7D1rFwccmtNJeKPWCnQFcw bsLry5VSDR/dVE5YaEiVsxSavPRNTk7BOlQ8brlmJ3kMvqPwAYaXiaVER/jzkM4hsCEB evefxiwhA2Qw+Rt/0k5FnhdI8eTgNJN4bKsEEH73Z1dPOk5yFlMbl9dUC4RNAf8ehTR4 lXEQ== X-Gm-Message-State: AOJu0YwX7b8XvQXrdDxmypihywyJ/qJWcRvTmYl/2/I0lAfNcIuUaGLE keE1L5WTiTLjQNQdpodw6NgeWjzCr6m/ts1O9xFAXPxjuHQ= X-Google-Smtp-Source: AGHT+IEoAKFE+7snwwo92Gxls+ELKXT0A3X+Ry4UwwOsm9JLATDsYoCzst5li0Eh7vnEFo0KX427+t5qnDw8InF3huU= X-Received: by 2002:a05:6358:7e14:b0:16b:96fc:650e with SMTP id o20-20020a0563587e1400b0016b96fc650emr2595901rwm.3.1700832632726; Fri, 24 Nov 2023 05:30:32 -0800 (PST) MIME-Version: 1.0 References: In-Reply-To: Date: Fri, 24 Nov 2023 10:29:56 -0300 Message-ID: To: Robert Landers Cc: Mike Schinkel , internals Content-Type: multipart/alternative; boundary="000000000000e56b1c060ae5f4e2" Subject: Re: [PHP-DEV] [RFC][Discussion] Why can constructors violate LSP? From: deleugyn@gmail.com (Deleu) --000000000000e56b1c060ae5f4e2 Content-Type: text/plain; charset="UTF-8" > > 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 ``` 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 --000000000000e56b1c060ae5f4e2--