Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:108753 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 55933 invoked from network); 25 Feb 2020 13:07:42 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 25 Feb 2020 13:07:42 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id D1C721804E3 for ; Tue, 25 Feb 2020 03:25:06 -0800 (PST) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-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,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=3.4.2 X-Spam-ASN: AS15169 209.85.128.0/17 X-Spam-Virus: No X-Envelope-From: Received: from mail-ot1-f44.google.com (mail-ot1-f44.google.com [209.85.210.44]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (4096 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 25 Feb 2020 03:25:04 -0800 (PST) Received: by mail-ot1-f44.google.com with SMTP id p8so11699890oth.10 for ; Tue, 25 Feb 2020 03:25:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to; bh=jspayZxHr7WKTdCqM3QxvuoTyEXgbqO1kJFSURb7bDU=; b=cvAE0p2nM+HemfQMYkR5L25OEpMOM2AwpF5r1N7bop7vLouvHZES3wpdo9QuEXiKEq VF4xXX5V/EGd930elze1qE4PKpvxJx8nQ4EQF5PyJnqq4w92NpRlo0fexscr8H4AHyOd 2TfYBqHxXFnGkDcZ8r3EvGvHwLddn3fUxcQRPn5UBXaZDDWrk3eyAOFlJSrc+I4t/119 0p6ZglXJFx1ZAJ/TFW811EwN4gLUW9Edo7gWgrliHrz/iStLRyvIKFBgyEjG24QuvxwW i/eNVQsrKvhvUnLNWD7+L7kJPYdIydrSqJGKju2+76FzVKJFflCGD/g6v/nLMnxZMQ0x mjyg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to; bh=jspayZxHr7WKTdCqM3QxvuoTyEXgbqO1kJFSURb7bDU=; b=ogkmJF5udsGZvO4l1AfS7WuMInqk22d8qiKD1Uv7nPHD9YzB4u6/fExM4wnnJ4aSKC Uc8AYfoBqa8QKViY3NDS+p7c6d5CXgNhvXSO22xV5APpjzE8LbQOx5aAawJab3CLJeeX /PvbLEZuZCE4/eCmjq8gxaFW//F/MT1bZSvpOs5fRvTkH/9/8rG4KscyC7TSvnWmS+AM oQmTUsq5ko++/YXwLGxC/+gDfhZHfsLLDBpVXm0ZEQKn+w8OdPcslhYSpBPTRMgTQtTt /qDqtZHjfGl04Kuu7i5PkiLSOkY6CCUaLdUeQ29gbh7vpw3h5BTyotA2wTesyRlPJi/Q X62A== X-Gm-Message-State: APjAAAWAg4+k4SEYVpdBSrkx4+6P7OZ936tYCwvAOqTdzg6TUnYysmR8 2BgSk/GJSI+JrAmk740V/aLTbOTDoDOjZqx35q8lEn9v X-Google-Smtp-Source: APXvYqwU9nNAlGNynAoMzOIwvmRkVLOue48XfXb/MfKmaR9O0MvEkvl28UeWsnoHgh5id40EOpUdHtRdd1xxPLWqy1s= X-Received: by 2002:a05:6830:1198:: with SMTP id u24mr42951268otq.215.1582629903668; Tue, 25 Feb 2020 03:25:03 -0800 (PST) MIME-Version: 1.0 References: <8545d15e-ddd5-42be-8405-09697a077234@www.fastmail.com> <4d9688fe-cc57-44af-903e-05f4cbb1bbcc@www.fastmail.com> <6bcbf0a5-92d8-4cfa-a00f-e0e967fc037e@www.fastmail.com> In-Reply-To: <6bcbf0a5-92d8-4cfa-a00f-e0e967fc037e@www.fastmail.com> Date: Tue, 25 Feb 2020 12:24:51 +0100 Message-ID: To: php internals Content-Type: multipart/alternative; boundary="000000000000383db3059f64bdef" Subject: Re: [PHP-DEV] [RFC] [DISCUSSION] Immutable/final/readonly properties From: nicolas.grekas+php@gmail.com (Nicolas Grekas) --000000000000383db3059f64bdef Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Le lun. 24 f=C3=A9vr. 2020 =C3=A0 21:35, Larry Garfield a =C3=A9crit : > On Mon, Feb 24, 2020, at 7:55 AM, Rowan Tommins wrote: > > On Fri, 21 Feb 2020 at 23:18, Larry Garfield > wrote: > > > > > The with*() method style requires cloning the object. What happens t= o > the > > > locked status of a set property if the object is cloned? Are they th= en > > > settable again, or do they come pre-locked? > > > > > > Neither of those seem good, now that I think about it. If they come > > > pre-locked, then you really can't clone, change one property, and > return > > > the new one (as is the standard practice now in that case). If they > don't > > > come pre-locked, then the newly created object can have everything on > it > > > changed, once, which creates a loophole. I'm not sure what the right > > > answer is here. > > > > > > > > > As with typed properties, I wonder if there's a way we can introduce a > new > > initialisation sequence for objects, so that there's a specific point > where > > the object is considered "fully constructed" after new or clone. > > > > A couple of brainstormed ideas, with plenty of downsides I'm sure: > > > > An explicit finalise() function or keyword > > > > public function withFoo($foo) { > > $inst =3D clone $this; > > // all readonly properties are initially "unlocked" > > $inst->foo =3D $foo; > > // now lock them, perhaps also checking that no typed properties ar= e > > left uninitialised > > finalise($inst); // or finalise $inst; > > return $inst; > > } > > > > A special code block: > > > > public function withFoo($foo) { > > $inst =3D clone $this { > > // all properties are "unlocked" within this special block > > $inst->foo =3D $foo; > > }; > > // from here onwards, readonly properties can't be written to > > return $inst; > > } > > > > Perhaps could also be used with constructors: > > > > public function createFromOtherThing(OtherThing $other) { > > $inst =3D new static('some parameter') { > > // readonly properties can be written in the constructor, or > > within this block > > $inst->foo =3D $other->getFoo(); > > }; > > // object is "finalised" when the block ends > > return $inst; > > } > > If the way to resolve this question is a special "unlocked" mode, I would > definitely favor the explicit code block. That way it's self-closing and > you can't forget to do so. (Murphy's Law: If you rely on developers > remembering to do X to keep code safe, they will promptly forget to do X.= ) > > Also, FTR, any approach that forces developers to write a 9 parameter > constructor over and over is one I can never get behind, doubly so for > something that is currently only a single line. This isn't a case of "pa= in > tells you what not to do"; a value object having a lot of internal > properties is a completely valid use case, and "but composition" is not a= n > answer. I totally agree with this: there must be a way to work around the keyword - either via reflection or another means. Unlike `private`, `final` is an imperative keyword that cannot be bypassed, while there are very legit use cases for still extending final classes (proxies). For sure, the same will be legitimately needed with `readonly`. Via Reflection, it could be a new method `->setWritable(true)` (next to `->setAccessible(true)`). Another way, which is my current preference, would be to have visibility be taken into consideration when using the keyword: - `public readonly $foo` =3D> cannot be set from the outside of the class = - but can be from protected+private scopes - `protected readonly $foo` =3D> can be set only from private scope - `private readonly $foo` =3D> either unsupported or have the `writeonce` behavior defined in the RFC. This would make the keyword compatible with untyped properties and with cloning. It would provide the guarantee we need from an author pov: outside scopes cannot mess up with the state of properties. It would not provide authors a safeguard against their own mistakes - but I think that's a really secondary goal and one that is fine letting go vs the mentioned benefits. Nicolas --000000000000383db3059f64bdef--