Re: [PHP-DEV] [RFC] Object Initializer

  107087
September 14, 2019 08:52 mike@newclarity.net (Mike Schinkel)
(Sorry for sending three replies to one message, but the list server said my reply was too long to send as just one. 
I could have made it shorter but then I would have had to omit all the example code.)

@rasmus:

HOWEVER, I see no reason we must pit object initializers and named parameters against each other as competing features. 

I think there is benefit to both, and ideally could actually be two aspects of the same new feature. 

Let me explain using one of your examples.

$car = new Car({ yearOfProduction => 1975, vin => "12345678"});

Conceptually let me ask, How is { yearOfProduction = 1975, vin = "12345678"} really any different from an instance of an anonymous class with the properties yearOfProduction and vin? 

From the recognition that named parameters that are collected as a group with braces ({}) are conceptually identical to anonymous class instances lets us to the insight we could use something like func_get_args(ARGS_OBJECT) to allow us to capture the grouped parameters as an instance of an object of anonymous class containing properties for each named parameter?

public function __construct(int $yearOfProduction, string $vin) {
   $args = func_get_args(ARGS_OBJECT);
   if ($args->yearOfProduction < 1900 ||  $args->yearOfProduction > date("Y")) {
      $msg = "year of production out of range:{$yearOfProduction}";
      throw new InvalidArgumentException($msg);
   }

   $this->yearOfProduction = $args->yearOfProduction;
   $this->vin = $args->vin;
}

Now assume we later realize we would be better passing in an instance instead of hardcoding the properties? We could change the signature, leaving all the rest of the code the same:

public function __construct(Customer $args) {
   if ($args->yearOfProduction < 1900 ||  $args->yearOfProduction > date("Y")) {
      $msg = "year of production out of range:{$yearOfProduction}";
      throw new InvalidArgumentException($msg);
   }

   $this->yearOfProduction = $args->yearOfProduction;
   $this->vin = $args->vin;
}

This is a critical capability in my book; the ability to refactor code to a more enlightened architecture without breaking it:

Of course if someone were to have used ordinal parameters when calling that constructor we could match their parameters to the properties of Customer in order of their declaration. 

So the following maps 2019 to $yearOfProduction and "abc123xyz789" to $vin:

class Car 
{
   public int $yearOfProduction;
   public string $vin;
   ...
}
$car = new Car(2019,"abc123xyz789"); 

The above could also now throw a warning so developers can find and replace all of those calls with calls that use the actual named parameters, but being a warning the code would still working until they update the calls.

> I'd prefer to see new features that work everywhere, all the time, for > everyone - and for existing code. Rather than adding more features and > syntax for very specific (even relatively rare) use-cases.
I completely agree with this statement, outside the context of your prior argument against object initializers. Hopefully however, I have made the case that object initializers combined with named parameters would address a much broader range of use-cases than either of them would on their own? -Mike