Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:127576 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 68A761A00BC for ; Wed, 4 Jun 2025 09:11:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=php.net; s=mail; t=1749028153; bh=J9GFcOzx2AOA6DxCgoUPuOOqOL6K77+IUL3VxhPDHyQ=; h=From:Subject:Date:In-Reply-To:Cc:To:References:From; b=R0GjdBGokHHM7FC+2bIkztHJZ9hAyYzPlorJilmZmieMM3KIsSMq1z3IYAIzrVNeG pI6pUV+nGFpsuwmxERX0IOZCTZNHp24vYaSFL2B3p8KixjsqxWpSeRGyj5X0RD06ae qmhiMaMRr3YrzY6z1+c/wl/i7dnCzyVjFL6AA2nH1NMMpZ1bB4873VYXvfeHUpQYU4 nvF1a5BN657W2DGaka9iGMVdH87eojMVZ9lrQDOHe5CMZA2hDuYG4ANCtxqC+opQY/ 7YEALvPTZxJgCdlSoH0257FNRIDbQes1uF0iMzAhyVyu+ph4wHVq8D66YJOHQW9DiX ysSqYl+USVadQ== Received: from php-smtp4.php.net (localhost [127.0.0.1]) by php-smtp4.php.net (Postfix) with ESMTP id 1E35A1801E5 for ; Wed, 4 Jun 2025 09:09:11 +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_05,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,DMARC_PASS,FREEMAIL_FROM, HTML_MESSAGE,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_NONE, SPF_PASS 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-wr1-f51.google.com (mail-wr1-f51.google.com [209.85.221.51]) (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 ; Wed, 4 Jun 2025 09:09:10 +0000 (UTC) Received: by mail-wr1-f51.google.com with SMTP id ffacd0b85a97d-3a4fd1ba177so510373f8f.0 for ; Wed, 04 Jun 2025 02:11:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1749028273; x=1749633073; darn=lists.php.net; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:from:to:cc:subject:date:message-id:reply-to; bh=xpjTN3F9wMJdhLypv9tiTmFk2/5ordS0j+dUvllSP5s=; b=e22yzJ63Z9Q7ZroMq8hx6KVEhR0v6aqm/wiuNysA7kruYXtJ7jHrlVM+zaxVwpkfEE O+amxPsQ0xdOFeOZGJ/MJRwdtfu/ot8y/ke6N4QXoWQ5fBNAJLbyVHey9ioQsoEF4owf EzXgchLCJKUuHxfA95UuJhIuZnXWLj5+/fcCkNQ5aXe/BM2jMB5ycNbUwxHcIbCUdxDy Mtl7FEHj1/m7W+BCWtsLViPU6cFanIjqD7f63aMD67L73zCP9/zDg4ajYNUdSX0FW+Wk CwJ9u1u4XTAQIPWeTEaF5lT3u9PecKCbhahIBd7ZIG0QAjH2hgb0qRcn9XWYUYe3XQLi 4nCw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1749028273; x=1749633073; h=references:to:cc:in-reply-to:date:subject:mime-version:message-id :from:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=xpjTN3F9wMJdhLypv9tiTmFk2/5ordS0j+dUvllSP5s=; b=cgmZQS11EyB8sGPuthyDohjcFvNqI1QnC4DmiS3PZXdR9HLauUJ5Rw7FrZKFA4e2Hz 7bBpel3n363WwYe0LxiUymWhVB5AqKhoIU78ylOfUFCqQMZf3q3NOfyUBr5YIElrs8Rv bg+scVhZw+shEUz+Ym3BV7hS6pcRZsl6rBwwaz1nA+3LvoTu4aSJmI6FUHFF+diCmFK/ pG2l8QO7XfsxKUJ1pa4+v+u9PgikTXEZk+NLS4yEvSAcrRymGyXGS6TobvpnSCHI+9BQ dUWq8TZGrTulSY+eTol5bBzEHVZX0mlXBhYAgG3YNV1xAhMz6dqcrYoGgvpzcVTYJ2P/ p+Ww== X-Gm-Message-State: AOJu0Yx53rKaEcnDQc5/z4TDLquXS+qFQf4LDxut5po152gNB06dY9dK 55NqAFjQPTPvinr/JxHOFN8VtHCE3AnlpvTFRGsYWfN+qvPWSiVwWCSgzc4Jrw== X-Gm-Gg: ASbGncuM6/3NikNhWnghnAAar4AyZt7t3Tq4QUutBQK9Uo4bPIB/3awLOyNCc7Bd2Lk 4JnsdoH2krvKJ4OfgC1tz7EHlFitGrSzX6iuJcwZsVjvqkiGnIOWoM/451Fn8QwYRsu09LdP4qv HU8PcY5kVPt+Lzw/3MnufQZwgrlThrlM9XsFtfdVRYfvzai4YIcTVvgsB/bmkKJ8wPuDQmRsAiG 4JGZhwlENcPlcI1Y86xXALpqLgb8XaPWRYf+7huGBDW4EPTZx/8vhZez07Hxs+vba3jZbTL3iPk loyC4JBkZrV0Bfq+IFY6CGJndAbBFPBjBIyn2tG3gDGr4izHkmEhWdZ7evgOkSHEwh5SuKCEcjQ = X-Google-Smtp-Source: AGHT+IEIZN/BTh9Z7o2D6hOsRevBnewN0Za2Fkk5WFll7D4OfRl2X6Aq/dDo9NwlqGsBjpat8sxXwQ== X-Received: by 2002:a5d:5f96:0:b0:3a4:d038:cb9b with SMTP id ffacd0b85a97d-3a51d510f24mr1694553f8f.7.1749028273011; Wed, 04 Jun 2025 02:11:13 -0700 (PDT) Received: from smtpclient.apple ([89.249.45.14]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-3a4efe73f9csm21205195f8f.50.2025.06.04.02.11.12 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Wed, 04 Jun 2025 02:11:12 -0700 (PDT) Message-ID: Content-Type: multipart/alternative; boundary="Apple-Mail=_49601683-C5F9-4184-B2B3-FE1E741124FB" Precedence: bulk list-help: list-post: List-Id: internals.lists.php.net x-ms-reactions: disallow Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.600.51.1.1\)) Subject: Re: [PHP-DEV] Allowing class properties to remain Uninitialized as a default value. Date: Wed, 4 Jun 2025 11:11:01 +0200 In-Reply-To: Cc: internals@lists.php.net To: Bradley Hayes References: X-Mailer: Apple Mail (2.3826.600.51.1.1) From: claude.pache@gmail.com (Claude Pache) --Apple-Mail=_49601683-C5F9-4184-B2B3-FE1E741124FB Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset=utf-8 > Le 3 juin 2025 =C3=A0 06:22, Bradley Hayes a = =C3=A9crit : >=20 > 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. >=20 > 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, > ) {} > } >=20 > $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 = initialisation error >=20 > 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). >=20 > 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 = 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); > (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.) >=20 > I have is a discussion on this in github here: > https://github.com/php/php-src/issues/17771 >=20 > 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. >=20 > 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. >=20 >=20 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=9Crea= lly, 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 { =20 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 ) { } =20 } ``` 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; =20 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 ) {=20 foreach ([ 'id', 'name', 'age' ] as $var) { if (! ${$var} instanceof DTO_status) { $this->$var =3D ${$var}; } } =20 } =20 } ``` 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 --Apple-Mail=_49601683-C5F9-4184-B2B3-FE1E741124FB Content-Transfer-Encoding: quoted-printable Content-Type: text/html; charset=utf-8

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 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 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 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 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);
(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:

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.


<= /blockquote>

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

=




= --Apple-Mail=_49601683-C5F9-4184-B2B3-FE1E741124FB--