I'd like to address the examples - and why I think they don't demonstrate
that this feature is really useful in practice.

There are several examples similar to this one:

class Car
{
  public int $yearOfProduction;
  public string $vin;
}

This is either lacking a constructor to initialize the properties to the
defined types - or it's lacking nullable type-hints. As it is, this class
doesn't guarantee initialization according to it's own property type
constraints.

Assuming the fields of this entity are required, you would probably prefer
to add a constructor - but then property initializers aren't really useful
anymore. This seems to be the case for most of the examples.

Models often have other constraints besides just the type - in those cases,
your models would likely have private/protected fields and setter-methods
that implement those constraints. The visibility example demonstrates the
use of a static factory method:

class Customer
{
  private string $name = '';
  protected ?string $email = null;

  public static function create(string $name, ?string $email = null): self
  {
    return new self {
      name = $name, // assign private property within the same class
      email = $email, // assign protected property within the same class
    };
  }
}

This may be fine for some use-cases - but many model types are only going
to have one valid way to construct an instance, and constructors are the
idiomatic way to do that. Unfortunately, this language feature works for
the new-statement only, so a desire to use this language feature will drive
architecture.

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.

In my opinion, language features should be as general as possible - a
feature like this "looks nice", being very abbreviated and clean-looking,
and, as I believe the examples in the RFC itself demonstrates, this will
provide the wrong kind of motivation to make what are, effectively,
architectural decisions.

Some models are immutable by design. Those cases don't seem to be well
supported by this feature.

My strong preference over this feature would be named parameters, which can
provide the same abbreviated initializations, but works more consistently
with the language, e.g. for all of the use-cases I cited above.

It works for constructors:

class Car
{
  public int $yearOfProduction;
  public string $vin;

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

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

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

It works for static factory-methods:

$car = Car::create({ yearOfProduction = 1975, vin = "12345678"});

It works for models with private/protected fields, classes with accessors,
classes with validations in constructors or factory-methods, and so on.

In other words, it works more generally with all common patterns and
practices - in many ways, it just seems like a better fit for the language.

The common criticism against named parameters, is they create coupling to
parameter-names. Object initializers create coupling to property-names - if
you can live with that, I don't think named parameters or object
initializers are very different in that regard.

The only problem I see with named parameters as an alternative to object
initializers, is in terms of versioning - renaming an argument, today, is
not a breaking change, and now it would be. That could be addressed, for
example, by making named parameters explicit at the declaration site - for
example, use curly braces in the declaration to designate named arguments:

  function makeCar({ int $yearOfProduction, string $vin }) {
    // ...
  }

This way, adding named parameters is not a breaking change - it's an opt-in
feature for those cases where it's useful and meaningful, but it's still
applicable to all the patterns and cases that someone might want to use it
for.

Named parameters is just one possible alternative - I'm just naming it for
practical comparison, to demonstrate how some features may have more
general applications than others.

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.


On Thu, Sep 12, 2019 at 4:00 PM Michał Brzuchalski <
michal.brzuchal...@gmail.com> wrote:

> Hi internals,
>
> I'd like to open discussion about RFC: Object Initializer.
>
> This proposal reduces boilerplate of object instantiation and properties
> initialization in case of classes without required constructor arguments as
> a single expression with initializer block.
>
> https://wiki.php.net/rfc/object-initializer
>
> I appreciate any feedback you all can provide.
>
> Thanks,
> --
> Michał Brzuchalski
> brzuc...@php.net
>

Reply via email to