Improving PHP's object ergonomics

in #php4 years ago (edited)

There have been a number of RFCs and discussions in recent months all circling a a very similar problem space, or rather some closely related problem spaces. Many of them have struggled to gain traction for various reasons, not the least of which is that their problem spaces overlap but individual, piecemeal solutions have a tendency to help one problem at the expense of another, or fall short of helping another.

To try and resolve these issues, I want to take a step back and try to consider the related problem spaces together; at the very least to document them explicitly, and hopefully to provide some guidance on how we can address as many as possible with as limited a syntax impact as possible. This writeup should be considered an "essay" in the classical sense, that is, I am trying to think through the problem by writing about it.

The problem space(s)

There are two primary overlapping problem spaces we're dealing with: The Construction Problem and the Value Object Problem. These problems are not the same, but they do overlap and influence each other due to the way PHP works today.

The constructor problem

The standard pattern for a PHP class today looks something like this:

class Foo {
   private string $aString;

   private int $anInt = 0;

   private Bar $bar;

   private Baz $baz;

   public function __construct(Bar $bar, Baz $baz, string $aString) {
       $this->bar = $bar;
       $this->baz = $baz;
       $this->aString = $aString;

       $this->anInt = strlen($this->aString);
   }
}

This is horribly verbose. Each variable is repeated four different times on three different lines. It's a lot of entirely pointless boilerplate. Some IDEs can help with much of it, but not all. This problem affects both service objects and value objects.

Desire: Some way to reduce the amount of typing necessary for this extremely common pattern that does not preclude the "additional construction" represented by the last line.

The value object problem

PHP, like Java and most other "classic OOP" languages, has a single language construct, a class, to represent both a product type (a value that is the aggregation of multiple other primitive values) and a service object (a logical routine that has been pre-initialized with some values, usually dependencies, that can then be accessed indirectly). This is quite common, but arguably (I would argue, anyway) problematic, since the behavior you want in those object types is not the same.

The primary problem with service objects is the Constructor Problem above. Value objects, however, have a number of additional challenges beyond the Constructor Problem.

First is the Bean Problem. By this I refer to the Java Bean convention of every property having a corresponding specially named getter and setter, such that the property may as well be public but isn't public Because You're Not Supposed To Have Public Properties(tm). When a value object legitimately has no real purpose other than to be two strings and an int stuck together, the perfunctory getters and setters do little but add noise.

However, that runs smack into the second problem, which is the Immutability Problem. Conceptually, most value objects (but not quite all) really should be externally immutable. That has a large number of benefits (which I won't diverge into here), but is syntactically quite hard right now. An only-getter object is easy:

class Point {
   private int $x;
   private int $y;

   public function __construct(int $x, int $y) {
       $this->x = $x;
       $this->y = $y;
   }

   public function getX(): int {
       return $this->x;
   }

   public function getY(): int {
       return $this->y;
   }
}

But that is very verbose and makes creating a new object based on the previous object (say, shift it to the left) needlessly difficult, as you'd have to repeat the entire constructor. When a value object legitimately has a non-trivial number of properties that becomes annoying. The current state-of-the-art is "wither" methods, as popularized by PSR-7, like so:

class Point {
   // ..

   public function withX(int $newX): self {
       $new = clone($this);
       $new->x = $newX;
       return $new;
   }

   public function withY(int $newY): self {
       $new = clone($this);
       $new->y = $newY;
       return $new;
   }
}

This works, but is also a not small amount of boilerplate for the common case.

However, plenty of value objects are not strictly "records" in this sense. They may have additional logic and validation needed beyond simply carrying properties. PHP 7.4 makes type checking possible on the property and eliminates the need to do so in a setter method, but other validation is often still needed. For example, requiring that both X and Y be positive values, or that a string is one of a specific set of enumerated values, or that one DateTimeImmutable property be after some other DateTimeImmutable property, etc. I will call this the Immutable Validation Problem.

Another challenge is what I will call the Materialized Value Object Problem. This is where you have values that are computed off of other properties, and only available externally, but you'd like to be able to cache. The trivial example here is $firstName and $lastName real properties, and a $fullName materialized property that is just concatenating the two together with a space.

Methods make materialized values straightforward to implement, but also cumbersome and verbose:

class Person {
   private $firstName;
   private $lastName;
   private $fullName;

   public function fullName(): string {
       if (empty($this->fullName) {
           $this->fullName = $this->firstName . ' ' . $this->lastName;
       }
       return $this->fullName;
   }
}

Also none of that is helpful for value objects that are not "records", like PSR-14 event objects. (Those likely can't be helped much anyway.)

Finally, there is (for both service objects and value objects, but mostly value objects) the Documented Property problem. When a value object has many properties (which for a service object may be a code smell, but is not necessarily for a value object), keeping track of which constructor argument is which can become a chore. That's especially true when some of the properties may be optional. For example, the PSR-7 Uri class has 8 properties. If all were placed in the constructor, it would look like this:

class Uri implements UriInterface {

   public function __construct(string $scheme = '', string $authority = '', string $userInfo = '', string $host = '', int $port = 80, string $path = '', array $query = [], string $fragment = '');
}

$u = new Uri('', '', '', 'example.com', 80, '', [], 'anchor');

This is very poor on the self-documenting front.

Summary

In total, then, I count the following overlapping problems of object property management:

  • Verbose Constructor - It's just too much typing.
  • Beans - Redundant and pointless getters/setters for no reason other than:
  • Immutability - requires all manual enforcement and is very verbose.
  • Evolution - Evolving an immutable object requires lots of boilerplate.
  • Immutable Validation - There's more to a valid product type than just property types.
  • Materialized Value - Some things are only "properties" when viewed from the outside.
  • Documented Property / Property Order - Non-trivial constructors are a pain.

Naturally we probably all disagree as to the severity of each, but I don't think anyone can disagree with their existence.

Our goal, then, is to devise solutions that improve as many of those problems as possible without making the others worse, and without harming performance.

Solutions to date

I have likely missed some here, and I'm not going to go into detailed specifics of each, but I will try to at least not misrepresent them entirely. Some of them have many possible syntax variations so please don't get into those weeds; I won't. These are in no particular order.

Write-once properties

A flag on a property to indicate that it can only be written to when in the uninitialized state. After that, it cannot be written to.

Example:

class Person {
   readonly public string $firstName;
}

Pros: Once written to, a property can be made public without violating immutability. That is, it solves the Bean problem and partially Immutability.

Cons: Because the write-once state is preserved across cloning, it makes Evolution worse. It also has no impact on Validation or Materialized Value. Also, if a property is not written to in the constructor then it could still be written to by something else afterward, creating unknown and potentially unknowable results.

Compact Object Property Assignment

A short-hand way to assign to public properties from external to the object.

Example:

$f = (new Foo)->[
   a = 1,
   b = 2,
   c = 3,
];

Pros: Helps with the verbose constructor and Documented Property problems.

Cons: Because it only works on public properties, it makes Immutability and Immutable Validation worse. Pairing it with write-once properties would help with that a little, but that's only applicable on value objects.

Associative array arguments

Pass an associative array to a constructor (or other method) that corresponds to the properties to be assigned.

Example:

class Person {
   private string $firstName;
   private string $lastName;

   public function __construct(array $properties) {
       $this->firstName = $properties['firstName'];
       $this->lastName = $properties['lastName'];
   }
}

Pros: Mostly solves the Documented Property problem. It can already be done in user-space.

Cons: It does so at the cost of type safety, and the implementation code within the object is still more boilerplate. Required-property enforcement is entirely manual.

Contextual access control

Allow a property be readable externally but writeable from within the class, and possibly its child classes.

Example:

class Person {
   privatewrite publicread string $firstName;
   privatewrite publicread string $lastName;

   public function __construct(string $firstName, string $lastName) {
       $this->firstName = $firstName;
       $this->lastName = $lastName;
   }
}

Pros: Reduces the Bean problem while preserving Immutability. It may also help with Materialized Value.

Cons: The syntax could get clunky fast. It could also conflict with other strategies in this list. However... I think it has some merit.

Named parameters

Any method (or maybe only methods that are marked as accepting it) can take its parameters as a named list in any order. This is essentially a type-safe version of Associative array arguments.

Example:

class Person {
   private string $firstName;
   private string $lastName;

   public function __construct(string $firstName, string $lastName) {
       $this->firstName = $firstName;
       $this->lastName = $lastName;
   }
}

$p = new Person({lastName: 'Garfield', firstName: 'Larry'});

Pros: Solves the Documented Property problem.

Cons: If properties are nested objects it could get rather fugly and verbose. It also does nothing to solve any of the other problems. It implicitly makes the variable names part of the interface of a function, when they have not been in the past. That's a very subtle BC break potential.

Object initializer syntax

Provide a dedicated syntax for constructors that maps parameters to properties.

Example (from https://wiki.php.net/rfc/object-initializer):

$customer = new Customer {
 id = 123,
 name = "John Doe",
};

Pros: Solves the common case of redundant constructor syntax.

Cons: Very narrowly focused, so it doesn't help with any other problems. The RFC in particular only talked about public properties which has the same flaws as Compact Object Property Assignment. (Arguably it's the same proposal.)

"use default" flag for function parameter

Have a keyword that can be passed for any parameter that has a default value, in which case the default value gets used.

Example:

$u = new Uri(default, default, default, 'example.com', 80, default, default, 'anchor');

Pros: Tightly-focused to improve Documented Property, but only a little. Doesn't make any other problem worse.

Cons: It really has limited benefit, as it's still a lot of typing and it's not clear which argument refers to which parameter.

Constructor promotion

Offer a short-hand way to "take a parameter and assign it to the same named property", which is an extremely common pattern. Using the syntax Hack supports, the example at the start of this document would become:

class Foo {
   private int $anInt = 0;

   public function __construct(private Bar $bar, private Baz $baz, private string $aString) {
       $this->anInt = strlen($this->aString);
   }
}

That is logically identical to the far more verbose version above. (cf: https://docs.hhvm.com/hack/classes/constructors)

It would also allow for "pure record" objects (those that are really nothing more than a set of public properties) to be written like this:

class Point {
   public function __construct(public int $x, public int $y) {}
}

Pros: Greatly reduces code verbosity. Does not, as far as I can tell, negatively impact any other problem space.

Cons: May take a while for people to get used to the syntax. If we start adding more modifiers to properties (readonly, publicread, etc.) it could get odd, unless formatted vertically, which is uncommon in practice these days.

Rust-like cloning

Rust is a quite different language than PHP, but that's never stopped PHP from stealing ideas before. :-) Rust structs have the ability to take their properties from another struct of the same type.

Example:

struct Point {
   x: f32,
   y: f32,
}

let p1: Point = Point { x: 10.3, y: 0.4 };
let p2 = Point { y: 5, x: p1.x }

Can be shortened to:

let p2 = Point { y: 5, .. p1 }

Which reads as "creat a new Point struct, with y value 5, and all other values copied from p1". There's some additional handling around Copy vs Clone and Rust's memory system that are not relevant here as PHP's memory model is different. A PHP equivalent could be something like:

$p2 = new Point({y: 5, .. $p1});

Pros: Essentially a "Wither" method combined with a constructor that can be used externally. Would complement Named Parameters or constructor hoisting well.

Cons: Only works in cases where a property is a direct map for a constructor property, which limits its usefulness. Doesn't help with Validation and may confuse Materialized properties. Making it only usable internally, though, could make Wither methods easier to write.

Property accessors

Allow a syntactic get/set method for a property that gets called when a property is read/written, essentially a named version of __get/__set. Many other languages have such a feature, including Python and Javascript.

Example from the most recent RFC (https://wiki.php.net/rfc/propertygetsetsyntax-v1.2):

class TimePeriod {
   private $Seconds;  // <-- Traditional Property

   public $Hours {    // <-- Guarded Property
       get() { return $this->Seconds / 3600; }      // <-- Accessor, more specifically a getter
       protected set($x) { $this->Seconds = $x* 3600; }       // <-- Accessor, more specifically a setter
       isset() { return isset($this->Seconds); }    // <-- Accessor, more specifically an issetter
       unset() { unset($this->Seconds); }           // <-- Accessor, more specifically an unsetter
   }
}

Pros: This has a long list of benefits. It makes it possible to have "public" properties (thus avoiding the Bean problem) that can be blocked from external writing (Immutability problem). It allows a place to insert Validation without changing the API. Properties can be lazily-derived (Materialized value). It can even be put into an interface, making "property like things" now declarable via an interface. Write-once logic, if needed, could be implemented in userspace (though likely it would be less needed since write-once properties' core use case would be solved a different way). This is the "kitchen sink" approach to property handling, which would solve a huge number of problems. The only thing it doesn't solve is Evolution and the Constructor-specific problems.

Cons: The major, and I would argue only, drawback to property accessors is that it becomes unclear if $foo->bar refers to a property or a stealth method. That's a debatable issue for users, but primarily is a challenge for the engine as it introduces a C-level if statement on every single dereference, creating a non-trivial performance hit. I believe that was the main strike against this proposal before, plus bikeshedding over the syntax (as there are a dozen ways structure it).

This approach would only be viable if we can resolve the performance issue in some way. I am not enough of an engine developer (0%) to offer useful insight into how that could be done.

Analysis

I think I've captured most of the outstanding proposals floating about now. If I missed one let me know and I'll try to include it. Based on the above summary, I would offer the following analysis and recommendations:

The compact object assignment proposals are, IMO non-starters. They are too narrowly focused and incompatible with many of the other options in the list, plus only work for public properties. That renders them only marginally useful at best.

Similarly, the "use default" function parameter is very narrowly targeted and I believe is better solved as a fallout of a more robust change. It doesn't really hurt anything, but it only marginally helps anything.

Associative array arguments have nothing in their favor whatsoever beyond the fact that they already are possible. They're what we want to get away from.

The write-once property proposal has promise, but it has enough limitations and negative impact on other problems that I believe it is also a non-starter.

The really promising proposals, I believe, are constructor promotion, named parameters, contextual access control, and property accessors, possibly with a little Rust thrown in. Allow me to explain:

The constructor-related problems are really the combination of two issues: Redundant syntax and linear-only parameters. Solving both of those independently would provide their own benefits, plus dovetail together nicely to make constructors vastly easier.

To that end, without getting hung up on specific syntax, consider this:

class Uri implements UriInterface {

   public function __construct({
       private string $scheme,
       private string $authority,
       private string $userInfo,
       private string $host,
       private int $port,
       private string $path,
       private array $query,
       private string $fragment,
   }) {
       if ($port < 0) throw new \LogicException();
   }
}

This syntax is borrowed mostly from Hack, but with the addition of {} in the argument list. That is an opt-in to indicate that, yes, the parameter names should be considered part of the interface and thus they can be named. Then, either of the following construction styles becomes possible:

$u1 = new Uri('', '', '', 'example.com', 80, '', [], 'anchor');
$u2 = new Uri({host: 'example.com', fragment: 'anchor',});

Benefits:

  • We can eliminate the unnecessary redundancy for direct constructor->property mapping.
  • It preserves the ability to have additional constructor logic.
  • It preserves type safety.
  • It makes object construction more self-documenting if necessary.
  • It works with any visibility of property, not just public properties.
  • Because it comes in two separate parts, implementers can leverage one or the other or both.
  • It is transparent to the caller. That is, the syntax for the linear constructor call is unchanged from today, and works regardless of whether the class uses auto-promotion and/or named parameters.
  • Named parameters may be used even if the constructor logic has no auto-promotion potential at all. It's independent of whether the object is a struct, a service object, or has arbitrarily complex constructor logic.
  • Named parameters may be additionally used on any method or function. It is a feature with value unto itself.

Drawbacks: None that I can see, frankly.

Optional:

  • We could debate if we want to make named constructors opt-in or not. I would be comfortable either way, and don't think it's a make-or-break for this functionality.
  • We can consider mixing positional and named parameters the way Python does, but I don't think that's necessary.
  • If we're really nervous about it, we could limit named parameters to constructors for the time being.

The combination of these two features would resolve nearly all constructor-related issues currently identified.

What it would not do on its own is resolve the Evolution case. However, I believe a slight Rust-inspired addition could. Consider this constructor-only addition:

$u2 = new Uri({host: 'example.com', fragment: 'anchor',});
$u3 = new Uri({port: 443, .. $u2});

That would translate to "pass to a named constructor of Uri a port parameter of 443, and read the other parameters off of the accessible properties of $u2".

Now, with $u2 being all private properties that would fail if called externally. However, from within a wither method it would be viable. That is, most wither methods would be reduced to:

public function withPort(int $port): self {
   return new static({port: $port, .. $this});
}

Which is a small but nice improvement. It also makes me really want to figure out a syntax to automate that, but I dont' know of one. A topic for another time.

To repeat, this is an optional addition, after constructor promotion and named parameters land on their own.

As a complement, the more I think on it the more I think some variation of contextual access control would be the best near term solution, as long as its syntax could be done in a way that does not make full on property accessors viable more difficult in the future.

Which, actually, suggests this possible syntax:

class Person {
   string $firstName {
       public get;
       private set;
   };
   public string $lastName {
       private set;
   };

   public function __construct(string $firstName, string $lastName) {
       $this->firstName = $firstName;
       $this->lastName = $lastName;
   }
}

That syntax is a strict subset of that proposed for property accessors, but provides a very clear way to indicate separate access control for different contexts. In the example above both $firstName and $lastName are publicly readable but only writable from within the class, and not from a subclass. A protected set keyword would accomplish that. This syntax also is very naturally extensible to full accessor methods should the performance issues be resolved.

I do not know if that would imply any performance hit on the engine the way full on accessor methods would; I suspect no more than the access checks that already exist, but someone with more engine knowledge than I can answer that better.

Allowing public properties to be defined in an interface is an optional component here. I would support it even if we went just this far with it, but that's also a separate, not-necessary feature.

The one downside of this syntax is that it may make the promoted version a bit clunky with the extra {}. To wit:

class Uri implements UriInterface {

   public function __construct({
       string $scheme = '' {public get, private set},
       string $authority = '' {public get, private set},
       string $userInfo = '' {public get, private set},
       string $host = '' {public get, private set},
       int $port = 80 {public get, private set},
       string $path = '' {public get, private set},
       array $query = [] {public get, private set},
       string $fragment = '' {public get, private set},
   }) {
       if ($port < 0) throw new \LogicException();
   }
}

which is not optimal, but is also not terrible. I am slightly concerned about the interaction here, especially if full property accessors are added in the future, but I think it's promising and with a little refinement we could get the order of keywords right.

To be clear, that means the class defined above would be:

  • constructable with linear parameters or named parameters.
  • Exposes every property publicly, but read-only.
  • Exposes every property internally, read and write.
  • Can still support any methods you want.
  • Can still be safely cloned, so that Wither methods are no different than today.
  • Still supports public Setter methods if you are so inclined.
  • Still supports arbitrary constructor logic.
  • Does not make any of the identified problems today any worse.
  • Tastes great and is less fillng.

It would also make the possible Rust-inspired "construct from" syntax more useful, as you could make properties public-read (and thus usable for this syntax) without making them public-write.

Conclusion / tl;dr

That was an interesting essay through several of PHP's syntactic challenges today. It did not quite end where I expected it to, which is a sign that it was beneficial. In summation, then, and based on the above analysis, I believe the following four RFCs would get us the most bang-for-the-buck in terms of making the ergonomics of PHP's object system considerably better. Each one brings benefits all on its own, but they also complement each other such that the net effect is greater than the sum of their parts.

Constructor Promotion

The syntax can be borrowed directly from Hack. This would greatly reduce the amount of boilerplate constructors at minimal cost.

Named parameter functions

This could be restricted to just constructors, but I don't believe that's necessary. I'd prefer to see them supported on all functions/methods, whether opt-in or not.

Compound property visibility

Using the syntax borrowed from property accessors, this would give us a clean way to offer value objects that are trivially usable externally but internally as complex as they need to be (and no further), without forcing people toward public properties when they shouldn't. The syntax proposed would also be naturally extensible to full property accessors in the future should its performance challenges become solvable. Even if it doesn't, though, the syntax itself is pretty self-explanatory.

Construct-from syntax

Inspired by Rust, this would make "wither" methods more compact, and complement compound property visibility nicely so that public "cloning with one change" was also viable.. I consider this an optional extension, and it would definitely come last. (The other 3 can be worked on in parallel, I believe.)

Problems solved

In total, these four changes would address nearly all of the identified problems:

  • Verbose Constructor - Fully solved by constructor promotion and named parameters.
  • Beans - No longer necessary due to compound property visibility.
  • Immutability - Still a user-space concern to enforce, but now vastly easier.
  • Evolution - Either unchanged or somewhat improved, depending on the construct-from syntax's fate.
  • Immutable Validation - Setters and Withers can still implement arbitrary logic.
  • Materialized Value - Largely unchanged. Would have to wait for full property accessors.
  • Documented Property / Property Order - Resolved by named parameters.

As noted, my skill at working on the PHP engine itself is microscopic. However, I would be happy to advise, assist, or collaborate in whatever way I can with someone who knows the engine well enough to do so, in whatever capacity would achieve these goals. That includes someone just taking the proposals here and running with it; as long as it goes done I don't much care who does it.

With that, I now don my flame retardant suit. Thank you for your time.

Sort:  

This article seems to be implying that there are deficiencies in the PHP language which are causing problems for some programmers, and that the language should be changed to eliminate those deficiencies. I disagree with this viewpoint entirely as I consider the real problem to be with the way that some programmers use the language - their choice of programming style - and not the language itself.

An important lesson I learned at the start of my programming career was the KISS principle which can be translated as “A good programmer is one who writes simple code to perform complex tasks, not complex code to perform simple tasks.”

You complain that PHP forces you to write verbose code in your constructor in order to load the object with data, but there is no rule which says that the load operation be performed ONLY in the constructor. There are other ways, better ways, so don’t blame PHP for your poor choice.

You complain that PHP forces you to write too much boilerplate code, in which case I suggest you look at the Template Method Pattern where the invariant/fixed methods are defined in an abstract class and variant/customisable methods are defined in subclasses. In my framework all boilerplate code is defined in the abstract class, which is why I never have to write such code any more.

You complain that there is a problem with value objects, but PHP has never supported such objects, so what is there to complain about? While it is accepted that an object must contain one or more properties and one or more methods, there has never been a rule in OOP that each property within an object must itself be an object. PHP, like so many other languages, uses scalars, not objects, so if you are saying that you cannot write cost-effective software without using value objects, then it is you who should change to a language which suits your preferences, and not PHP which should change to suit you.

You complain that there is a problem with getters and setters, but again there is no rule in OOP which states that each property of an object must be declared separately, and that each of these properties must have its own pair of getters and setters. A relational database deals with datasets where each set contains any number of rows, and each row contain any number of columns, and I have found it far easier to pass this dataset around as a single argument called $fieldarray instead of having to deal with separate methods and arguments for each field with the array. In this way I achieve loose coupling which is far more desirable than tight coupling.

You complain about something called the Materialized Value Object Problem, but yet again that problem is the result of your bad choice and not a fault of the language. It is possible to construct the materialised value within the SELECT query itself, which means that it then becomes just another column in $fieldarray and does not require any additional PHP code to obtain its value.

You complain about something called the Documented Property problem where an object has many properties and some of those properties may be optional. Thus keeping track of which constructor argument is which can become a chore. If you choose to write code where each property has its own argument in a method call then you have to deal with the consequences of your choice. If you now realise that there are problems with the choice you made then you should choose a different option instead of trying to change the language to deal with your poor choice.

You say that there is a problem with immutable objects, but PHP does not support such objects so how can they be a problem? Immutable properties and objects were not specified in any original definitions of OO, or in any early languages which supported OO, so I regard them as an optional extra which were created by someone who thought he was being clever when the result proved to be the exact opposite. I have read several discussions regarding the introduction of immutable objects into PHP, but these discussions identify a large number of complications, and there is never any agreement of how each individual complication can be solved.

Because I consider all these “problems” to be the fault of the programmer and not the language, I consider all of your proposed “solutions” which involve a change to the language to be totally unnecessary. This follows another basic principle which states “Prevention is better than Cure” which means that it is far better to eliminate a disease than to mask its symptoms.

So there you have it. If your choice of programming style causes problems then you should first look into changing to a less problematic style instead of changing the language to deal with your mistakes. Remember that changing the language to suit a particular programming style may have detrimental effects on those you use a more common, simpler and less problematic style.

You claim the problems are not with PHP but with the way people are using it, but I am not convinced you are really offering better ways of using the language as is that bypass these problems.

Specifically about constructors you say there is a better way of loading classes than through there constructor. Um, that is the whole point of a constructor. If there is a need for a better way, that means constructors are flawed and need to be fixed. You also fail to say what that better way is. I don't see how there can be a better way as using anything other than the constructor to finish getting the class into its correct, initial (constructed) state breaks encapsulation and forces you to make poorly designed classes. Properties should be typed as much as possible, and made to not allow null as much as possible. But if after it is constructed, the properties are set to null, this means the type of those properties must be allowed to be null, or else the type is a lie (and PHP will throw an error).

KISS is not something to beat people over the head with. Just because there is not an absolute need for something doesn't mean it is not a real improvement. PHP is continuing to change and evolve. That is not going to change. The community is not about to decide that they will only evolve the language in ways that are absolutely necessary. Instead they have been proposing one RFC at a time, only thinking about if that one RFC is an improvement on its own or not. This could be taking PHP down a dangerous path where they make changes to the language they later want to reverse because they are in conflict with some desirable goal that appears later down the line. At least this article is trying to think ahead and see the bigger picture instead of just tacking on one RFC at a time without a general direction. If you really want to be true to the idea of KISS as you seem to understand it, you should be writing all your code in assembly or some other super low level way, not PHP.

You said, "You say that there is a problem with immutable objects, but PHP does not support such objects so how can they be a problem?" The fact that PHP doesn't support something doesn't mean it is not a problem or that there is no room for improvement. What if PHP didn't support functions or variables, would that mean this is not a problem?

You used the word "complain" seven times and the expression "You complain" six times. He is not complaining, he is writing a serious blog post that he hopes will lead to either himself or other people improving the PHP language (or both working together). You are the one that is complaining.

I don't see how there can be a better way as using anything other than the constructor to finish getting the class into its correct, initial (constructed) state breaks encapsulation and forces you to make poorly designed classes.

I disagree. The purpose of a constructor is to leave the object in a state in which it can subsequently respond to calls on any of its public methods. In this context the word "state" means "condition" and not "with data" as in "stateful". Using other methods to load data into the object certainly does NOT break encapsulation nor produce a poorly designed class.

Properties should be typed as much as possible, and made to not allow null as much as possible.

That is just your personal preference and not a concrete rule to which everyone is, or should be, bound. PHP has always been a dynamically typed language and millions of programmers have written millions of programs which have not suffered because of this. I don't have to make any effort to tell the language that a property can have a null value as that is the default value anyway.

Just because there is not an absolute need for something doesn't mean it is not a real improvement.

I am afraid that what constitutes "improvement" is a matter of opinion, not fact. You may think that changing the language to be strictly typed is an improvement but I do not. I was drawn to PHP in the first place because it was NOT strictly typed, and I have seen my productivity increase because of it. I will not take advantage of the ability to turn on any strict typing option as it would require a great deal of effort to change my existing code base with absolutely no visible reward.

You said, "You say that there is a problem with immutable objects, but PHP does not support such objects so how can they be a problem?" The fact that PHP doesn't support something doesn't mean it is not a problem or that there is no room for improvement.

The lack of support for immutable objects is only a problem for those who see a need for immutable objects. I have been writing OO applications since 2003 and I have never seen a need for immutable objects. If they were to become available I would still not use them because I have no need for them.

What if PHP didn't support functions or variables, would that mean this is not a problem?

That'a a silly question. If PHP didn't support functions or variables then I, and probably many others, wouldn't be using it.

He is not complaining, he is writing a serious blog post that he hopes will lead to either himself or other people improving the PHP language

He is complaining that PHP does not currently support his programming style and so should be changed. But what about those millions of developers who do not follow the same style? What benefit is there for them if the language is changed? Should they be forced to change their style just to be consistent with him? As far as I am concerned there are many ways in which the components provided by the language can be assembled into meaningful programs, and different programmers have their own personal preferences, their own style. I do not think that the language should be changed to support one style over another, it should allow the developer to adopt whatever style they see fit. In other words it should be "free style" just as it supports both the procedural and object oriented styles without promoting one over the other.