Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127607 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 3853B1A00BC for ; Thu, 5 Jun 2025 08:35:23 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1749112399; bh=H2zdVu1dt8SnMh7AVO5XeqUKarUnWyPFbHllsfGEaZs=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=RhN6u4qRkTj9E7462FhRnZgzmYqMhta/6BRmAVH7GfEluJXSK4smlaHSG+nH9wI6x 2CrUcevLnxGA4EeLXPxqMnOrLUR+qHuSq8INhzEpuTPcG4iOGjOtC0lZAjku1mqy07 IxOy/eiNPFAgQZmtBuYY9310y7r33wd1NZPodm9r2Tw6zPCxbSM9tiR0s+W5NeDhVi PGjXiFWSvsnWNhhHZtBudmqah7vCkaFrsv4SL2YkuvIuWaHbTbSYfg7L4AbqS93mFg JwKk0ipW5bXM2aJYBqVku/DhiNTDUDO1SlCWlXDajd1BSBmGG6YXUjUHbVw3LqruF/ fTyTuPr0bRYQw== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 6670D180053 for ; Thu, 5 Jun 2025 08:33:18 +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=0.1 required=5.0 tests=ARC_SIGNED,ARC_VALID,BAYES_40, DKIM_INVALID,DKIM_SIGNED,DMARC_MISSING,HTML_MESSAGE,RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H4,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 sender4-of-o54.zoho.com (sender4-of-o54.zoho.com [136.143.188.54]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by php-smtp4.php.net (Postfix) with ESMTPS for ; Thu, 5 Jun 2025 08:33:18 +0000 (UTC) ARC-Seal: i=1; a=rsa-sha256; t=1749112518; cv=none; d=zohomail.com; s=zohoarc; b=Qo7Jv7vlaTqUxavh3GrFiQJ6c+4WqUOmviv1K85vUmXPt8GIQCYEfB+KjXCQBLTLf21lCHjOX6+0hbKzj4R5ABEheWH7S19F90x83BUUDMiIuxmDhNES70PzYfLNWz9HbWv8Xqj+p0+ZdTf9BDaTgst3fm0Z/SsbUW2vSLPYzM4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1749112518; h=Content-Type:Cc:Cc:Date:Date:From:From:In-Reply-To:MIME-Version:Message-ID:References:Subject:Subject:To:To:Message-Id:Reply-To; bh=hVPa81sTFz6zbSLSzqE7FTiJa1qJmlcchzmEUTpc0gI=; b=g4tX7k5Ikmq2QsvjyaaHnoA/o66vvFEwVmuIcArIahrldWY/scyqbN/6NDowJQHC2rNoIYbVMcNXmfgGSi1L7Ou5NO1+AYQQ74YImHhQTtMBajALaFFoIlAuWfwc96bfothYiHEQkzQRmiF5eRd7QOVL75jf2UEsLXskyyyQbuo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass header.i=faizanakram.me; spf=pass smtp.mailfrom=hello@faizanakram.me; dmarc=pass header.from= DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; t=1749112518; s=zoho; d=faizanakram.me; i=hello@faizanakram.me; h=MIME-Version:References:In-Reply-To:From:From:Date:Date:Message-ID:Subject:Subject:To:To:Cc:Cc:Content-Type:Message-Id:Reply-To; bh=hVPa81sTFz6zbSLSzqE7FTiJa1qJmlcchzmEUTpc0gI=; b=IVIMkcD9IkkPAu6dO0m+nVU3StyV/dU3IqCzZMm34C+iXDHvxBk/kBww6wE+1+Pp ycFw7MTbxlEbLGbmB6tbXVLJEHf3nW4OkS6dQZ48QwTXfukjQHpdqNWTv9uWvmD81po Teid13lQNuAUJuviCpEE6aMkIM8UTWQueUf9QXg0= Received: by mx.zohomail.com with SMTPS id 1749112515686311.6302878856352; Thu, 5 Jun 2025 01:35:15 -0700 (PDT) Received: by mail-yw1-f173.google.com with SMTP id 00721157ae682-70e767ce72eso7464837b3.1 for ; Thu, 05 Jun 2025 01:35:15 -0700 (PDT) X-Forwarded-Encrypted: i=1; AJvYcCVsRTgfI+3meugFAuYzUv79uEsNF9KECN8g1wogOe7dN91QEfKpY4UmyDea+9CsXISN74/q/ZvKb9I=@lists.php.net X-Gm-Message-State: AOJu0Yy6i8GNfsG7q2aRs90Gq3szWDdx3+hPpqkvS19wlJEMLDyaKreZ +xCNAyhs9smAq4t8y9HQDlC+qVydIMwhPykreaJz6sc7v6zE/4WbgAg/UBAlHyY4WgV+cbjj3Gr MnGj9HzGPoQKvuoEDecmZbr9GWjlhiYM= X-Google-Smtp-Source: AGHT+IEZSHHgaEJj818dNuDrvbBA7TzHjBmmMOLxr3JA0H3iutKILFcJEYlD4GlkXcYSPVcKTJYzZX/CSxF/OZyeKUs= X-Received: by 2002:a05:6902:1009:b0:e7d:b06c:709d with SMTP id 3f1490d57ef6-e8179dac9c2mr7342507276.37.1749112514686; Thu, 05 Jun 2025 01:35:14 -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:05:03 +0530 X-Gmail-Original-Message-ID: X-Gm-Features: AX0GCFsYDWWjWVH6ghEyGHqi0h62pGW0zbkKHizlquVFuteLYs4HekWYRF-IJnc Message-ID: Subject: Re: [PHP-DEV] Allowing class properties to remain Uninitialized as a default value. To: Bradley Hayes Cc: Claude Pache , PHP internals Content-Type: multipart/alternative; boundary="0000000000001c38d40636cefe15" X-ZohoMailClient: External From: hello@faizanakram.me (Faizan Akram Dar) --0000000000001c38d40636cefe15 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable On Thu, 5 Jun 2025, 13:56 Bradley Hayes, wrote: > Here is another way to think about it, trying to make the arguments > optional not have a new value type. > > Optional arguments currently requires a default value. > > Assigning values to properties is truly optional but we cant reflect this > in the constructor parameters. > > class DTO { > public function __construct( > public string $id, > optional public string $name, > optional public null|int $age, > ) {} > } > new DTO('some-id'); > new DTO(...['id' =3D> 'some-id']); // no error for missing keys for named= arguments > > Hi Bradley, You can already achieve this, just don=E2=80=99t add those properties to th= e constructor. Yes, static analysis tools might complain, but that=E2=80=99s fine if your intention is to leave some properties uninitialized after construction. The constructor is, by definition, for properties that must be initialized during object creation. Kind regards, Faizan > On Thu, Jun 5, 2025 at 2:33=E2=80=AFPM Bradley Hayes > wrote: > >> 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 t= o >> write thousands of checks and throw errors across all the constructors o= f >> these objects. >> >> When properties have an uninitialized state you remove the need to handl= e >> 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 paramete= r. >> >> 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 initi= alisation 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 fo= r >>> 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 typ= e >>> safety and IDE indexing, but allows the uninitialized error to happen i= f >>> 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 data= base >>> in countless situations and API can assign null to delete a value from = an >>> object. >>> >>> Additionally, since array unpacking now directly maps to named argument= s 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= we >>> 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 inheritanc= e >>> 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. Tod= ay, `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 goo= d 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 accesse= d 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 uninitialise= d >>> state: you have to manually throw the appropriate error in the getter. >>> >>> =E2=80=94Claude >>> >>> >>> >>> >>> >>> >>> >> >> -- >> >> Bradley Hayes / Engineer / TITHE.LY >> >> >> >> > > -- > > Bradley Hayes / Engineer / TITHE.LY > > > > --0000000000001c38d40636cefe15 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable


On Thu, 5 Jun 2025, 13:56 Bradle= y Hayes, <bradley.hayes@tithe.ly> wrote:
Here is another way to think about it,= trying=C2=A0to make the arguments optional not have a new value type.
=
Optional argu=
ments currently requires a default value.
Assigning values to properties is truly optional but we cant r= eflect this in the constructor parameters.
class DTO {
public function __c= onstruct(
public stri= ng $id,
= optional public string $name,
optional public null|int $age,
) {}
}
new DTO('some-id');
=
new DTO(...['id&= #39; =3D> 'some-id']); // no error for missing keys for named arguments

=

Hi Bradley,


You can= already achieve this, just don=E2=80=99t add those properties to the const= ructor.

Yes, static anal= ysis tools might complain, but that=E2=80=99s fine if your intention is to = leave some properties uninitialized after construction.

The constructor is, by definition, for prop= erties that must be initialized during object creation.


Kind regards,
Faizan



On Thu, Jun 5, 2025 at 2:33=E2=80=AFPM Bradley Hayes <bradley.hayes@tithe.ly> wrote:
Hey Claude, i did think of t= he 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 che= ck each property everywhere it might be used you also have=C2=A0to write=C2= =A0thousands=C2=A0of checks and throw errors across all the=C2=A0constructo= rs of these objects.

When properties have an uninitialized state you= =C2=A0remove the need to handle any of this when loops and serializers simp= ly exclude=C2=A0them.

Uninitialized states is alre= ady=C2=A0an 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 par= ameter.

I get what you mean about null, but null is = serving a different purpose. Its a value that represents nothing, so that y= ou 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,
<= span style=3D"color:rgb(204,120,50)"> public string $nam= e =3D uninit= ialized,
public null|int $age =3D uninitialized,
) {= }
}
new DTO('some= -id');
// Would produce the same result as this one.= ..
class DTO {
public function __constru= ct(array $parameters) {
foreach ($parame= ters as $key =3D> = $value) {
$this->{$key} =3D $value;
}
= }
}
new DTO(['id&= #39; =3D> 'some-id']);

On W= ed, Jun 4, 2025 at 7:11=E2=80=AFPM Claude Pache <claude.p= ache@gmail.com> wrote:


Le 3 juin 2025 =C3=A0 06:22, Bradley Ha= yes <bradley.hayes@tithe.ly> a =C3=A9crit :
<= br>
Uninitialized properties are really usef= ul.
Being skipped in foreach loops and JSON encoded results and o= ther behaviours around uninitialized properties save a lot of time wasted o= n basic checks and uncaught logical mistakes around null values.
=
With the introduction of named arguments and promoted constr= uctor properties and read-only classes, it would be great to have=C2=A0the = true ability to not specify a value.
class DTO {
= public function __construct(
public string $i= d =3D uninit= ialized,
public strin= g $name =3D uninitialized,
= public null|int $age = =3D uninitialized,
) {}
}

$dto =3D new DTO(id: 'someid', age: <= /span>null);
if ($dto->age =3D=3D=3D null) echo "no age was given\n";
echo $dto->name, PHP_EOL; // triggers the standard access before initialisation 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 valu= es if they were fetched.
(These situations usually way more compl= ex involving multiple=C2=A0SQL joins/filters etc and nested objects/arrays = in the return DTO).

The DTO object has all the pos= sible values defined on the class for type safety and IDE indexing, but all= ows the uninitialized error to happen if you try to use data that was never= requested.
Uninitialized Errors when directly accessing a p= roperty that was not assigned is also desirable as it indicates a logical e= rror instead 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.<=
/pre>
//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 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 inheritan= ce because not all the properties can be seen from the same scope forcing e= very extender of the class to copy paste the constructor code from the pare= nt class.




Hi Brad= ley,

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, m= eaning=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 d= ecision 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=9C= really 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 uninitialized;
=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 public int|DTO_status $id =3D DTO_status::unini= tialized
=C2=A0 =C2=A0 =C2=A0 , public string|DTO_status $name = =3D DTO_status::uninitialized
=C2=A0 =C2=A0 =C2=A0 , public int|n= ull|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 $id;
=C2=A0 =C2=A0 public string $= name;
=C2=A0 =C2=A0 public int|null $age;
=C2=A0 =C2=A0= =C2=A0
=C2=A0 =C2=A0 function __construct(
=C2=A0 =C2= =A0 =C2=A0 =C2=A0 int|DTO_status $id =3D DTO_status::uninitialized
=C2=A0 =C2=A0 =C2=A0 , string|DTO_status $name =3D DTO_status::uninitiali= zed
=C2=A0 =C2=A0 =C2=A0 , int|null|DTO_status $age =3D DTO_statu= s::uninitialized
=C2=A0 =C2=A0 ) {=C2=A0
=C2=A0 =C2=A0 = =C2=A0 =C2=A0 foreach ([ 'id', 'name', 'age' ] as $= var) {
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 if (! ${$var} in= stanceof 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
}
```

W= ith 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 e= rror anymore, because you cannot (and are not supposed to) return to the un= initialised state: you have to manually throw the appropriate error in the = getter.

=E2=80=94Claude

<= br>






--

Bradley Hayes / Engineer / TITHE.LY






--

Bradley Hayes= / Engineer / TITHE.LY




--0000000000001c38d40636cefe15--