Could be handy to allow this in normal functions too (but this it not the main use case and could be disallowed):
public static function get(string $name, mixed $default = uninitialized): string { $value = getenv($name); if ($value === false) { return $default; // throws uninitialized error. } return $value; } Not specifying a default value can now be done without creating a class or 'some highly specific string to avoid conflicts' to check for it just throws an error when it cant return a value. However, the uninitialized error is too generic for this situation and youd want a more specific error message. This would naturally make you think to check for uninitialized instead of using a try->catch block. To avoid treating it as a type of value with === a helper function could be provided changing the function like so: public static function get(string $name, mixed $default = uninitialized): string { $value = getenv($name); if ($value === false) { if (isInitialized($default)) return $default; throw new \Exception("Environment variable '$name' not found."); } return $value; } On Tue, Jun 3, 2025 at 2:22 PM Bradley Hayes <bradley.ha...@tithe.ly> wrote: > 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. > > > -- > > Bradley Hayes / Engineer / TITHE.LY <http://tithe.ly/> > > <http://tithe.ly> > > -- Bradley Hayes / Engineer / TITHE.LY <http://tithe.ly/> <http://tithe.ly>