Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:120478 Return-Path: Delivered-To: mailing list internals@lists.php.net Received: (qmail 93377 invoked from network); 30 May 2023 21:46:18 -0000 Received: from unknown (HELO php-smtp4.php.net) (45.112.84.5) by pb1.pair.com with SMTP; 30 May 2023 21:46:18 -0000 Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 564DB1804BC for ; Tue, 30 May 2023 14:46:17 -0700 (PDT) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on php-smtp4.php.net X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE,SPF_NONE, T_SCC_BODY_TEXT_LINE 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-yw1-f169.google.com (mail-yw1-f169.google.com [209.85.128.169]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange ECDHE (P-256) server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Tue, 30 May 2023 14:46:16 -0700 (PDT) Received: by mail-yw1-f169.google.com with SMTP id 00721157ae682-565eaf83853so40323617b3.3 for ; Tue, 30 May 2023 14:46:16 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=dqxtech-net.20221208.gappssmtp.com; s=20221208; t=1685483176; x=1688075176; 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=/M+GIxzvJYaZnHrbqGx+OwYAVgb7rXq6n6t+IMOCExU=; b=B58QWtKezctUxUyMG/K80XVibygugo+fie6QqTV9x9Udn5keHGvKBB2UAa08wMVASL xiwadZI4iRsWAgGgzUg95ExxGsYmL1+lIp+Vj8QDTsU3hgljTnEmhqf2ehICkvurAYAp MCeAtI8UVvCsEcQTysmeZoNIXwR67dd36wvy7BPHGShIq/BRJp5rMaNvS9pPOh0OiAun IxmUbwn8uM3nL9ZVgXZQFWPux5jhYT5YmvxBtlBib9b0HbLh5lm7Wv2j44FI5dFlp9GT F9O78bs8Oim6ntnLccctEn6H1zSmDwDKWPKjJd/RoND4Ce6ot4ZoC/2LAh2xSa9JXH9Q 8Fdw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20221208; t=1685483176; x=1688075176; 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=/M+GIxzvJYaZnHrbqGx+OwYAVgb7rXq6n6t+IMOCExU=; b=d2bn/NWbvGTxcpCFlXqq7WxKvu3MaMpe7/PCurUMQuhrK7r7W20KZtHdLSY5LurieV IVvjS8PJHKn7tAupGeUKNSSU+0Ng60oxCQpJm5FTjUePQE91uESjdjVyyNCSF9IlDLTV 9eVxTuFK0O9AU+VEZgg2k8t98keRezqH2//iS92/ECza6NfeWYg5y+9hgrFDud1ZDGxi ie6azRFrj2K0hh0sGmy9S//VEjTqjzri6kAjd2iaM8qmZLE3YZSZfd0rIOTdZcZWjaCA 9OTBxb/ZKW6+eao+xZhOW/eC1ArjdNXB9hFfNCw8MFXtHFDq5uP88ELvt0FmcpGho97A 6M/g== X-Gm-Message-State: AC+VfDxDmwYPAfQCe5EHEzThCbhzziLMnSwBH1twofjZUp/iPnxFGoMa hNv9Y2RFSbJC+Pr3z7Wqc+aqgFOl6jIC7FTL0CCjkxgVrV8tzJ0jI44I4g== X-Google-Smtp-Source: ACHHUZ5vmwbjoH38SfqOuhalsNPrb/gk0Ab/5HqlbwS6LXu35mbZ5dusI9tceJ70SI/LO2CwKZ5F8XbQpA2vObFwDBo= X-Received: by 2002:a0d:d9ce:0:b0:55d:edf3:9e0a with SMTP id b197-20020a0dd9ce000000b0055dedf39e0amr3711076ywe.27.1685483175802; Tue, 30 May 2023 14:46:15 -0700 (PDT) MIME-Version: 1.0 References: <64EAB991-7082-47B8-B546-73CD08243C6E@koalephant.com> <23DF3A9A-9F11-4786-A011-41AE0FA9CFA9@koalephant.com> <75a8db86-c91d-4f5e-b19e-e968926b4e8a@app.fastmail.com> In-Reply-To: Date: Tue, 30 May 2023 23:46:04 +0200 Message-ID: To: Larry Garfield Cc: php internals Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Subject: Re: [PHP-DEV] Declaration-aware attributes From: andreas@dqxtech.net (Andreas Hennings) On Tue, 30 May 2023 at 22:45, Larry Garfield wrote= : > > On Tue, May 30, 2023, at 7:34 PM, Andreas Hennings wrote: > > On Tue, 30 May 2023 at 19:12, Larry Garfield w= rote: > >> > >> I've run into this issue in my attribute library as well (https://gith= ub.com/Crell/AttributeUtils). What I do there is allow attributes to opt-i= n to a callback method via interface. For example: > >> > >> #[\Attribute] > >> class AttribWithName implements FromReflectionClass > >> { > >> public readonly string $name; > >> > >> public function __construct(?string $name =3D null) > >> { > >> if ($name) { > >> $this->name =3D $name; > >> } > >> } > >> > >> public function fromReflection(\ReflectionClass $subject): void > >> { > >> $this->name ??=3D $subject->getShortName(); > >> } > >> } > > > > So it is technically from outside it is a setter, whereas from inside > > it is not really. > > Technically, this means you need a version of the attribute object > > that can exist without the values from the reflector. > > The constructor needs to initialize some "stub" values, until the > > setter is called. > > Every other method also needs to support the case when the setter has > > not been called yet, or possibly throw an exception. > > Also, your property type has to allow null, so `?string`, not `string`. > > Except the property type is not null above? The argument is, but not the= property. (I could use CPP here with a null value instead, if we had asym= metric visibility.) True! My conception of readonly was distorted by the same static analysis tools that you complained about earlier! https://3v4l.org/CUgYv I think a more accurate label would be "write once". > > Also, if the interface is called by the reflection system itself as part = of getInstance() then yes, we can guarantee that it's been run, just like w= e can guarantee the constructor has run. The concern was that you can also construct the attribute object with a regular `new AttribWithName()` expression, and then you can omit the ->fromReflection() call. But we could say it is part of the contract that you have to call the method before the object is fully operational. > > >> (Side note: This is why static analysis tools that forbid writing to r= eadonly properties except from the constructor are wrong; it's also an exam= ple of where asymmetric visibility would be superior to readonly. But I di= gress.) > > > > Technically there is no guarantee that the setters will be called > > before any other method, and only once. > > If these methods can write on readonly properties, then any method can. > > Unless we somehow mark these methods as special. > > There's nothing special about readonly properties there. An uninitialize= d non-readonly property is no more or less susceptible to still being unini= tialized when you need it. Singling out readonly here is just dumb, and ex= acerbates the problems of readonly. > > > On the other hand, a wither method with "clone with" should be allowed > > to work on readonly properties. > > You could rewrite your method like this, once we have clone with: > > (or use the old-school syntax but without readonly) > > > > public function withReflector(\ReflectionClass $subject): static > > { > > return ($this->name !=3D=3D NULL) > > ? $this > > : clone $this with (name: $subject->getShortName(); > > } > > > > Then in the discovery code: > > > > $attribute =3D $reflectionClass->getAttributes()[0]->newInstance(); > > $attribute =3D $attribute->withReflector($reflectionClass); > > In that library I actually have several opt-in post-constructor setup rou= tines. The documentation covers them all. Making them all withers would b= e just needlessly slow. Basically it's an example of a "mutable and then l= ocked" object, which I emulate by virtue of only calling the setup methods = from the library, and using readonly properties. > > >> That's why I think an opt-in interface is the way to go. It also avoi= ds any confusion around the constructor parameters, which is, based on this= thread, a confusing area. :-) > > > > What do you think about a placeholder syntax to avoid confusion with a > > skipped parameter? > > Like > > > > #[A('x', ?', 'y')] > > Oh god no. For one, function calls are already complicated enough and th= e next thing we should add there is some kind of partial application, which= already discussed using ?. Second, that the attribute cares about what it= is attached to is not something the caller should care about. If the user= of the attribute needs to specify "and put the reflection object here" in = the attribute usage, we've failed. I'd vote against any such proposal in a= ny form, whatever the syntax. I was actually thinking of the partial application with the `?` symbol. The metaphor would be that we create a partial callable from the `new A(.., ?)` expression, and then PHP calls that callable with the reflector argument. It would all just be a metaphor to justify the `?` placeholder, because PHP would actually just skip the extra step and evaluate the expression in one go. But the only reason was to have something to avoid a skipped parameter. We could also put other placeholders that feel more like expressions, but I am not happy with that either. In the end I could also live with a method call as you propose. > > >> My second preference would be the ReflectionAttribute::inProgress() ca= ll in the constructor, or something like that. I like that less because it= 's a stateful call, but it would also reduce the issue with readonly proper= ties (as in the example above) by making both alternatives available in the= constructor, so maybe it's an acceptable tradeoff. > > > > I would like to avoid anything that is stateful or that leaves an > > incomplete stub object. > > (But I think I made this clear enough, so..) > > I don't think it's going to be possible to have both. We either have a s= tatic call of some kind (the inProgress() method above or similar) that onl= y works sometimes (ie, while an attribute is being constructed), or we have= multiple setup methods (which the engine can ensure are called, but techni= cally that does mean the object has a moment where it's potentially incompl= ete). I doubt we can have our cake and eat it too, here. > > Personally I think the "thou shalt not ever have an object that isn't per= fectly setup" rule is over-blown. It's a good guideline, but it has edge c= ases where it just simply doesn't work. This is one of them. > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >