On Thu, Jun 5, 2025 at 10:41 AM Faizan Akram Dar <he...@faizanakram.me> wrote:
> > > On Thu, 5 Jun 2025, 13:56 Bradley Hayes, <bradley.ha...@tithe.ly> 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' => 'some-id']); // no error for missing keys for named >> arguments >> >> > > Hi Bradley, > > > You can already achieve this, just don’t add those properties to the > constructor. > > Yes, static analysis tools might complain, but that’s 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 PM Bradley Hayes <bradley.ha...@tithe.ly> >> 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 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 = uninitialized, >>> public string $name = uninitialized, >>> public null|int $age = uninitialized, >>> ) {} >>> } >>> new DTO('some-id'); >>> // Would produce the same result as this one... >>> class DTO { >>> public function __construct(array $parameters) { >>> foreach ($parameters as $key => $value) { >>> $this->{$key} = $value; >>> } >>> } >>> } >>> new DTO(['id' => 'some-id']); >>> >>> >>> On Wed, Jun 4, 2025 at 7:11 PM Claude Pache <claude.pa...@gmail.com> >>> wrote: >>> >>>> >>>> >>>> Le 3 juin 2025 à 06:22, Bradley Hayes <bradley.ha...@tithe.ly> a écrit >>>> : >>>> >>>> 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 = uninitialized, >>>> public string $name = uninitialized, >>>> public null|int $age = uninitialized, >>>> ) {} >>>> } >>>> >>>> $dto = new DTO(id: 'someid', age: null); >>>> if ($dto->age === 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 = 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 >>>> 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 “no value”. Today, `null` is a >>>> value in itself, and there has been a necessity to have something else to >>>> encode an uninitialised state, meaning “really, no value”. Although I >>>> understand your specific use case, I don’t think that it is good long term >>>> design decision to rely on various built-in variations of general “no >>>> value” states: maybe tomorrow there will be a request for some “really and >>>> truly, no value” 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 = DTO_status::uninitialized >>>> , public string|DTO_status $name = DTO_status::uninitialized >>>> , public int|null|DTO_status $age = DTO_status::uninitialized >>>> ) { } >>>> >>>> } >>>> ``` >>>> >>>> Or, if you want to rely on the handy error “must not be accessed before >>>> initialization” 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 = DTO_status::uninitialized >>>> , string|DTO_status $name = DTO_status::uninitialized >>>> , int|null|DTO_status $age = DTO_status::uninitialized >>>> ) { >>>> foreach ([ 'id', 'name', 'age' ] as $var) { >>>> if (! ${$var} instanceof DTO_status) { >>>> $this->$var = ${$var}; >>>> } >>>> } >>>> >>>> } >>>> >>>> } >>>> ``` >>>> >>>> With property hooks, you can support more elaborate things such as >>>> `$foo->id = DTO_status::deleted`, although you cannot (and should not) rely >>>> on the built-in “must not be accessed before initialization” 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. >>>> >>>> —Claude >>>> >>>> >>>> >>>> >>>> >>>> >>>> >>> >>> -- >>> >>> Bradley Hayes / Engineer / TITHE.LY <http://tithe.ly/> >>> >>> <http://tithe.ly> >>> >>> >> >> -- >> >> Bradley Hayes / Engineer / TITHE.LY <http://tithe.ly/> >> >> <http://tithe.ly> >> >> Hi all! This discussion seems related to the one that I started last year: https://news-web.php.net/php.internals/123338 Maybe you can find some additional feedback in that discussion. Regards, Luigi