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

This is only part of a thread. view whole thread
September 14, 2019 20:29 ("Larry Garfield")
On Fri, Sep 13, 2019, at 9:35 AM, Rowan Tommins wrote:
> Hi Rasmus, > > On Fri, 13 Sep 2019 at 11:18, Rasmus Schultz <> wrote: > > > All in all, I find this feature is useful or applicable only to a few, > > select patterns within the language - it isn't general enough. > > > > > I've trimmed the quote for readability, but agree with basically everything > in this message. :) > > I like the reasoning behind this RFC, but think it is unnecessarily > limited. It's not about "public properties are bad", it's just that if we > can come up with a feature that works for public properties *and* other > programming styles, we should prefer that. > > A related proposal that's come up before is short-hand constructors: > > class Foo { > private int $foo; > public function __construct($this->foo) { > // automatically assigns first parameter to $this->foo so body is > simplified > } > } > > Combine that with opt-in named parameters, and the Customer example from > the RFC might look something like this: > > class Customer > { > private $id; > private $name; > private DateTimeImmutable $createdAt; > > public function __construct(id => $this->id, name => $this->name, > createdDate => string $dateString = 'now') > { > $this->createdAt = new DateTimeImmutable($dateString); > } > }$customer = new Customer( > id => 123, > name => 'John Doe', > createdDate => '2019-01-01 12:34' > ); > > > It's slightly more verbose, but a lot more flexible. > > > As a side note, I have always thought stdClass was a bit of a kludge, and > now we have real anonymous classes I would love to see it gradually phased > out. I would much rather see syntax for capturing variables in an anonymous > class declaration than new ways to create stdClass objects. > > Regards, > -- > Rowan Tommins > [IMSoP]
I'm a big fan of using defined classes over anon arrays for struct-like data, for various reasons (cf, so I'm sympathetic toward anything that makes that easier. However, I have to agree with Rasmus and Rowan that the current RFC proposal is too "narrow" to solve that effectively. The problem to be solved is that this: class Employee { public int $age; public string $name; public ?Employee $boss; public function __construct(int $age, string $name, Employee $boss = null) { $this->age = $age; $this->name = $name; $this->boss = $boss; } } $e = new Employee(34, "Bob", $sally); // or $e = new Employee(); $e->age = 34; $e->name = 'Bob'; $e->boss = $sally; Is just annoyingly verbose and annoying to work with. I agree. However, initializers as presented solve only a subset of this problem space: The case where: * The properties are public. * The constructor can be viable with no parameters. * There is no needed validation, or we can expect a user to manually call validate() or similar afterward. While that case absolutely exists, it is only a subset of the relevant use cases. As Rasmus and Rowan note, however, breaking the problem apart into two pieces would allow it to handle a wider array of use cases while still solving the one presented. For example (and with no thought given to syntax here, 'hoist' is almost certainly the wrong word): class Employee { protected int $age; protected string $name; protected ?Employee $boss; public function __construct({hoist int $age, hoist string $name, hoist Employee $boss = null}) { if ($age < 18) throw new ChildLaborException(); } } $e = new Employee({ age = 34, name = 'Bob', boss = $sally, }); Solves the initializer use case with very similar syntax for the caller, BUT also works for: * Any function where named parameters would be useful, independent of object initialization. * Any object where the properties in question are not public. * Any object where validation is required and you don't trust the caller to remember to call it (which you shouldn't trust) * Any object where only some of the constructor parameters map to object properties, a fact that should be hidden from the caller. And probably other situations. I could envision taking it a step further and writing instead: class Employee { public function __construct({hoist private int $age, hoist protected string $name, hoist public ?Employee $boss = null}) { if ($age < 18) throw new ChildLaborException(); } } And now you have even less typing and repetition. If you wanted to go really really far, you could even: class Employee { protected int $age; protected string $name; protected ?Employee $boss; public function hoist __construct() { if ($age < 18) throw new ChildLaborException(); } } As a short hand that enables any property to be passed in, but only using a named call, and still enforce that some parameters are required. I could see that being very useful for service objects, as well as value objects, where there is almost always a 1:1 mappnig from properties to constructor parameters. That is, named arguments are useful on their own (and have been discussed before). Auto-populating properties from the constructor is useful on its own. With the combination of those, the need for a specialized initializer syntax goes away, because they are an emergent property of the other two features. So on net, we get more power and more flexibility for less new syntax. Implementing a more limited syntax (just initialization for a subset of use cases) makes implementing the more robust approach in the future harder, because we'd have to be super careful to avoid BC issues or else end up with two slightly different syntaxes for slightly different but really the same thing behaviors; that's not only bad for usability and learnability but we're running out of convenient sigils. :-) So I would be -1 on initializers as proposed, but +1 on addressing that problem space through the more robust combination of named arguments and auto-populating properties. --Larry Garfield