(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