Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127602 X-Original-To: internals@lists.php.net Delivered-To: internals@lists.php.net Received: from php-smtp4.php.net (php-smtp4.php.net [45.112.84.5]) by lists.php.net (Postfix) with ESMTPS id 65A221A00BC for ; Thu, 5 Jun 2025 04:33:44 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1749097900; bh=RzIhI8LcrQaRc+DkCVlSx2QXPtjy6UQY1ASYfS6vXTg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=G90F/qcP6DtAao505jmMet1Tw2/AqNCPL928ODc1Z8h+5QIKB5FQHbMrKc7Xz5HbY zlgCp1iT63RoitO+OCkYNL3a5/IIGcFK+WmtG0bnCI1UMeYrctv1XlnD0IGgrUl9X4 RkNk8LYX/kcB4vqjC9WaVGnMDHhGwsDVYjuwE0y/i1YAhYfVS0EPJJ1MCkpogB5j0J epf18R/joVZdRr7WSgS0K+9pW3HTmUlCwdhXXbHaGGpIIw+wG6x+anKrrcHCvE3RZj KzhiP+xyEcSzPy5Gu51KLUsVbBslOncfVr9YE0SCgapqR+Ex3NCTAUT3bRKhB3eYm8 QaoHrCj9kLBrA== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 7D1D6180047 for ; Thu, 5 Jun 2025 04:31:39 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 4.0.1 (2024-03-25) on php-smtp4.php.net X-Spam-Level: * X-Spam-Status: No, score=1.7 required=5.0 tests=BAYES_50,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DMARC_PASS,HEADER_FROM_DIFFERENT_DOMAINS, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_NONE,SPF_PASS,T_REMOTE_IMAGE autolearn=no autolearn_force=no version=4.0.1 X-Spam-Virus: Error (Cannot connect to unix socket '/var/run/clamav/clamd.ctl': connect: Connection refused) X-Envelope-From: Received: from mail-qt1-f174.google.com (mail-qt1-f174.google.com [209.85.160.174]) (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 ; Thu, 5 Jun 2025 04:31:39 +0000 (UTC) Received: by mail-qt1-f174.google.com with SMTP id d75a77b69052e-4a440a72584so5078771cf.2 for ; Wed, 04 Jun 2025 21:33:42 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tithe.ly; s=google; t=1749098022; x=1749702822; 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=EHVH3pkDixawLXTRA4b95ew7TCHcvdhlw/eXf7oIT+s=; b=ttG2Rp8PZi4aWJ+2IwMyoceVxRNNktoIZuOKemV1tYz32pvJCrIj+428kBvwFWHwR4 vuJHu24+Dt3QPQVOBex75FC19xzHv606bWz/KE/SjXw1r7gqJyAkt56yUn4TSzpOu+Ck b/QYuKrzOPfx2lZIvFgcVxs7s/jZ3Rpb0hWOo= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749098022; x=1749702822; 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=EHVH3pkDixawLXTRA4b95ew7TCHcvdhlw/eXf7oIT+s=; b=tdjvZtWYK/cWRk/s4wBYSWgtpBcCBVden4PVgHt9mXOFRkAhz6P0BBIicwibRBypxR WBygOKiBQQeSjTCQDDaNrifZS8a8qSaSWFUShdYE8jrUZHf7TuqpzJPyntjTBBM4RV++ 724KRDhIz32dYdXUDnQv0rZowbxiRdc8Vbb7RFaUryVxWtTwGP80ZvEWABmFhx2cehnY PuoFxBj14lh98vap5PEHTnAxe5T4ZQzFsT9pcWriHeVnCItxsZmkrRNnXQbYNjw0YEet PFSBAmvLHQIqxEhok1P3w1KFTNsURhlz2w3jbYSRLdttueuqpMGriDmxYTg1h4NaE9dn 26wA== X-Gm-Message-State: AOJu0YwaFWdSkRU+Slkt24kMLW/2RDKKUTaGFvy5ajq62GVX+FBiCw5M v3SgXc7lZVEroEB3qbmuA7Ljtoy6RT7OvBHyeJhzzfcVkt70Sa2rTbP7J1PeLLS14v6mO5wXo/q bsUNM62+/925wGKinkY5GKxMlyaVG2P+Q/MnRIAmEtQN3alnyBKBqrm13Gg== X-Gm-Gg: ASbGncuwPVFIeMInehpp7rG+41bSVUhRnHT8RWA5P4rl/X+U+ZPNJ2/WO/PrufifksB W12z0oXg59igvDRtc+i9jDhCZ7PNPqLqiKLhMsf+FjdQ3BCbCk1KNsIXmg6Z1ECpG/L2eB6y5rw 6pmiX31hozO4ckmpu262TztG3tmFseAh9l45A= X-Google-Smtp-Source: AGHT+IEjpr+BibSVEf3x5BPOZCtwHUzgW8jkACbLTSHDCOcIqw4xOUPA4hR6S/yjlR1hiAWQWTvuSqZQaIUbKSUPPy4= X-Received: by 2002:a05:622a:4895:b0:4a5:8eef:f891 with SMTP id d75a77b69052e-4a5a5801ebdmr95275811cf.39.1749098021777; Wed, 04 Jun 2025 21:33:41 -0700 (PDT) Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow MIME-Version: 1.0 References: In-Reply-To: Date: Thu, 5 Jun 2025 14:33:05 +1000 X-Gm-Features: AX0GCFt9_i_ahiNdIsNU3IP-_F3iNX2ZdT8cbJYDsARh7zTWeaWbvpmBX0EYzDQ Message-ID: Subject: Re: [PHP-DEV] Allowing class properties to remain Uninitialized as a default value. To: Claude Pache Cc: internals@lists.php.net Content-Type: multipart/alternative; boundary="0000000000004404e60636cb9e2a" From: bradley.hayes@tithe.ly (Bradley Hayes) --0000000000004404e60636cb9e2a Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hey Claude, i did think of the same thing you proposed and I cover that in the gihub issue I linked to. This doesnt replicate the uninitialized state. Doing ti with custom classes or enums means now not only are forced to manually check each property everywhere it might be used you also have to write thousands of checks and throw errors across all the constructors of these objects. When properties have an uninitialized state you remove the need to handle any of this when loops and serializers simply exclude them. Uninitialized states is already an incredibly useful feature that exists right now. Im only proposing that we have a way to tell the constructors to ignore the parameter instead of being forced to have array as the only parameter. I get what you mean about null, but null is serving a different purpose. Its a value that represents nothing, so that you can assign something as nothing. Uninitialized is not like assigning null to a variable. // This implementation: class DTO { public function __construct( public string $id =3D uninitialized, public string $name =3D uninitialized, public null|int $age =3D uninitialized, ) {} } new DTO('some-id'); // Would produce the same result as this one... class DTO { public function __construct(array $parameters) { foreach ($parameters as $key =3D> $value) { $this->{$key} =3D $value; } } } new DTO(['id' =3D> 'some-id']); On Wed, Jun 4, 2025 at 7:11=E2=80=AFPM Claude Pache wrote: > > > Le 3 juin 2025 =C3=A0 06:22, Bradley Hayes a =C3= =A9crit : > > Uninitialized properties are really useful. > Being skipped in foreach loops and JSON encoded results and other > behaviours around uninitialized properties save a lot of time wasted on > basic checks and uncaught logical mistakes around null values. > > With the introduction of named arguments and promoted constructor > properties and read-only classes, it would be great to have the true > ability to not specify a value. > > class DTO { > public function __construct( > public string $id =3D uninitialized, > public string $name =3D uninitialized, > public null|int $age =3D uninitialized, > ) {} > } > > $dto =3D new DTO(id: 'someid', age: null); > if ($dto->age =3D=3D=3D null) echo "no age was given\n"; > echo $dto->name, PHP_EOL; // triggers the standard access before initial= isation error > > > EXAMPLE: A graphQL like API that only returns data that was asked for, is > serviced by a PHP class that only fetched the data that was asked for and > thus the DTO only has assigned values if they were fetched. > (These situations usually way more complex involving multiple SQL > joins/filters etc and nested objects/arrays in the return DTO). > > The DTO object has all the possible values defined on the class for type > safety and IDE indexing, but allows the uninitialized error to happen if > you try to use data that was never requested. > Uninitialized Errors when directly accessing a property that was not > assigned is also desirable as it indicates a logical error instead of > thinking the value is null. Null is considered a real value in the databa= se > in countless situations and API can assign null to delete a value from an > object. > > Additionally, since array unpacking now directly maps to named arguments = this would also save a ton of mapping code. > > *//array unpacking direct from the source > *$dto =3D new DTO( ...$sqlData); > > (FYI: SQL is way faster at mapping thousands of values to the naming > convention of the class than doing it in php so we do it in SQL. So yes w= e > would directly array unpack an sql result here.) > > I have is a discussion on this in github here: > https://github.com/php/php-src/issues/17771 > > The current workaround is to make the constructor take an array as its > only parameter and looping over it assigning matching array key values to > class properties and ignoring the rest. > > This works but breaks indexing and prevents the use of class inheritance > because not all the properties can be seen from the same scope forcing > every extender of the class to copy paste the constructor code from the > parent class. > > > > > Hi Bradley, > > Originally, `null` was intended to mean =E2=80=9Cno value=E2=80=9D. Today= , `null` is a > value in itself, and there has been a necessity to have something else to > encode an uninitialised state, meaning =E2=80=9Creally, no value=E2=80=9D= . Although I > understand your specific use case, I don=E2=80=99t think that it is good = long term > design decision to rely on various built-in variations of general =E2=80= =9Cno > value=E2=80=9D states: maybe tomorrow there will be a request for some = =E2=80=9Creally and > truly, no value=E2=80=9D state? Instead, I think one should use > application-specific states. With enums and union types, it is possible: > > ```php > enum DTO_status { > case uninitialized; > case deleted; > } > > > class DTO { > > function __construct( > public int|DTO_status $id =3D DTO_status::uninitialized > , public string|DTO_status $name =3D DTO_status::uninitialized > , public int|null|DTO_status $age =3D DTO_status::uninitialized > ) { } > > } > ``` > > Or, if you want to rely on the handy error =E2=80=9Cmust not be accessed = before > initialization=E2=80=9D for free, you could also write: > > ```php > class DTO { > > public int $id; > public string $name; > public int|null $age; > > function __construct( > int|DTO_status $id =3D DTO_status::uninitialized > , string|DTO_status $name =3D DTO_status::uninitialized > , int|null|DTO_status $age =3D DTO_status::uninitialized > ) { > foreach ([ 'id', 'name', 'age' ] as $var) { > if (! ${$var} instanceof DTO_status) { > $this->$var =3D ${$var}; > } > } > > } > > } > ``` > > With property hooks, you can support more elaborate things such as > `$foo->id =3D DTO_status::deleted`, although you cannot (and should not) = rely > on the built-in =E2=80=9Cmust not be accessed before initialization=E2=80= =9D error anymore, > because you cannot (and are not supposed to) return to the uninitialised > state: you have to manually throw the appropriate error in the getter. > > =E2=80=94Claude > > > > > > > --=20 Bradley Hayes / Engineer / TITHE.LY --0000000000004404e60636cb9e2a Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hey Claude, i did think of the same thing you proposed and= I cover that in the gihub issue I linked to. This doesnt replicate the uni= nitialized state.

Doing ti with custom classes or enums = means now not only are forced to manually check each property everywhere it= might be used you also have=C2=A0to write=C2=A0thousands=C2=A0of checks an= d throw errors across all the=C2=A0constructors of these objects.

Wh= en properties have an uninitialized state you=C2=A0remove the need to handl= e any of this when loops and serializers simply exclude=C2=A0them.

Uninitialized states is already=C2=A0an incredibly useful = feature that exists right now.

Im only proposing t= hat we have a way to tell the constructors to ignore the parameter instead = of being forced to have array as the only parameter.

I get what you mean about null, but null is serving a different purpose. I= ts a value that represents nothing, so that you can assign something as not= hing.

Uninitialized is not like assigning null to = a variable.

// This i=
mplementation:
class <= span style=3D"color:rgb(0,116,195)">DTO {
public function __construct(
public = string $id =3D uninitialized,
public string $name =3D uninitialized,
= public null|int $= age =3D unin= itialized,
) {}
}
new DTO('some-id'
);
//= Would produce the same result as this one...
class DTO {
public function __construct
(array $parameters) {
foreach ($parameters
as $key =3D= > $value) {
$this->{$key} =3D $value;
}
}
}
new DTO(['id' =3D> 'some-id']);

On Wed, Jun 4, 2= 025 at 7:11=E2=80=AFPM Claude Pache <claude.pache@gmail.com> wrote:


Le 3 juin 2025= =C3=A0 06:22, Bradley Hayes <bradley.hayes@tithe.ly> a =C3=A9crit :

Uninitialized properties are really useful.
Being skipped in foreach loops and JSON encoded results and other = behaviours around uninitialized properties save a lot of time wasted on bas= ic checks and uncaught logical mistakes around null values.

<= /div>
With the introduction of named arguments and promoted constructor= properties and read-only classes, it would be great to have=C2=A0the true = ability to not specify a value.
<=
span style=3D"color:rgb(0,51,179)">class DTO {
<= span style=3D"color:rgb(0,51,179)">public function __construct(
public string $id =3D uninitialize= d,
public string $name =3D uninitialized,
public null|int $age =3D uninitialized
,<= br> ) {}
}

$dto =3D = new DTO(id: &= #39;someid', age: <= span style=3D"color:rgb(0,51,179)">null
);
if ($dto->= age =3D=3D=3D null) echo <= /span>"no age was given\n";
echo $dto->name, PHP_EOL; = // triggers the standard access before initialisation error

EXAMPLE: A graphQL like API that only re= turns data that was asked for, is serviced by a PHP class that only fetched= the data that was asked for and thus the DTO only has assigned values if t= hey were fetched.
(These situations usually way more complex invo= lving multiple=C2=A0SQL joins/filters etc and nested objects/arrays in the = return DTO).

The DTO object has all the possible v= alues defined on the class for type safety and IDE indexing, but allows the= uninitialized error to happen if you try to use data that was never reques= ted.
Uninitialized Errors when directly accessing a property= that was not assigned is also desirable as it indicates a logical error in= stead of thinking the value is null. Null is considered a real value in the= database in countless situations and API can assign null to delete a value= from an object.
Additionally, since array unpacking now directly maps to named =
arguments this would also save a ton of mapping code.
//array unpacking direct=
 from the source
$dto =3D new DTO( ..=
.$sqlData<=
span style=3D"color:rgb(8,8,8);font-size:9.8pt">);
=
(FYI: SQL is way faster at mapping thousands of values to = the naming convention of the class than doing it in php so we do it in SQL.= So yes we would directly array unpack an sql result here.)

<= /div>
I have is a discussion on this in github here:

The curren= t workaround is to make the constructor take an array as its only parameter= and looping over it assigning matching array key values to class propertie= s and ignoring the rest.

This works but breaks ind= exing and prevents the use of class inheritance because not all the propert= ies can be seen from the same scope forcing every extender of the class to = copy paste the constructor code from the parent class.




Hi Bradley,

= Originally, `null` was intended to mean =E2=80=9Cno value=E2=80=9D. Today, = `null` is a value in itself, and there has been a necessity to have somethi= ng else to encode an uninitialised state, meaning=C2=A0=E2=80=9Creally, no = value=E2=80=9D. Although I understand your specific use case, I don=E2=80= =99t think that it is good long term design decision to rely on various bui= lt-in variations of general =E2=80=9Cno value=E2=80=9D states: maybe tomorr= ow there will be a request for some =E2=80=9Creally and truly, no value=E2= =80=9D state? Instead, I think one should use application-specific states. = With enums and union types, it is possible:

```php=
enum DTO_status {
=C2=A0 =C2=A0 case uninitialize= d;
=C2=A0 =C2=A0 case deleted;
}


class DTO {
=C2=A0 =C2=A0=C2=A0
=C2= =A0 =C2=A0 function __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 publ= ic int|DTO_status $id =3D DTO_status::uninitialized
=C2=A0 =C2=A0= =C2=A0 , public string|DTO_status $name =3D DTO_status::uninitialized
=C2=A0 =C2=A0 =C2=A0 , public int|null|DTO_status $age =3D DTO_status= ::uninitialized
=C2=A0 =C2=A0 ) { }
=C2=A0 =C2=A0=C2=A0=
}
```

Or, if you want to rely= on the handy error =E2=80=9Cmust not be accessed before initialization=E2= =80=9D for free, you could also write:

```php
class DTO {

=C2=A0 =C2=A0 public int $i= d;
=C2=A0 =C2=A0 public string $name;
=C2=A0 =C2=A0 pub= lic int|null $age;
=C2=A0 =C2=A0=C2=A0
=C2=A0 =C2=A0 fu= nction __construct(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 int|DTO_status $i= d =3D DTO_status::uninitialized
=C2=A0 =C2=A0 =C2=A0 , string|DTO= _status $name =3D DTO_status::uninitialized
=C2=A0 =C2=A0 =C2=A0 = , int|null|DTO_status $age =3D DTO_status::uninitialized
=C2=A0 = =C2=A0 ) {=C2=A0
=C2=A0 =C2=A0 =C2=A0 =C2=A0 foreach ([ 'id&#= 39;, 'name', 'age' ] as $var) {
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 if (! ${$var} instanceof DTO_status) {
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 $this->$var =3D = ${$var};
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 }
=C2=A0 =C2=A0 =C2=A0 =C2=A0=C2=A0
=C2=A0 =C2=A0 }
=C2=A0 =C2=A0=C2=A0
}
```

With property hooks, you can support m= ore elaborate things such as `$foo->id =3D DTO_status::deleted`, althoug= h you cannot (and should not) rely on the built-in =E2=80=9Cmust not be acc= essed before initialization=E2=80=9D error anymore, because you cannot (and= are not supposed to) return to the uninitialised state: you have to manual= ly throw the appropriate error in the getter.

=E2= =80=94Claude



<= br>




--
=

Bradley Hayes / E= ngineer / TITHE.LY




--0000000000004404e60636cb9e2a--