Re: [RFC] Object Initializer
| From: | Mike Schinkel | Date: | Sat, 14 Sep 2019 08:52:32 +0000 |
| Subject: | Re: [RFC] Object Initializer | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-107087@lists.php.net to get a copy of this message | ||
(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