> On Sep 13, 2020, at 3:49 PM, Olle Härstedt <olleharst...@gmail.com> wrote:
> 
> 2020-09-13 17:58 GMT, Benjamin Eberlei <kont...@beberlei.de 
> <mailto:kont...@beberlei.de>>:
>> On Sat, Sep 12, 2020 at 10:23 PM Olle Härstedt <olleharst...@gmail.com>
>> wrote:
>> 
>>> Hi internals!
>>> 
>>> Separation of data and behaviour is both a fun and hard discussion,
>>> especially considering:
>>> 
>>> * "It should be possible to add new features without touching old code";
>>> and
>>> * "Principle of Least Privilege" (never expose more than you have to)
>>> (https://en.wikipedia.org/wiki/Principle_of_least_privilege).
>>> 
>>> There should (could) be a way to add new behaviour to old data without
>>> touching the old data (class). Traits won't work in this use-case,
>>> since they assume the same internal structure for all trait-using
>>> classes. Imagine the `stringable` interface and a `toString` trait. A
>>> __toString() method needs knowledge about the internal structure of a
>>> class Foo. Yet if we want to keep adding behaviour to Foo, we'll end
>>> up with either exposing too much of Foo, or expanding the class file
>>> indefinitely. Please note that composition is not a proper solution,
>>> since it requires exposure of Foo; composition leads to lack of proper
>>> encapsulation, or representation exposure.
>>> 
>>> In Haskell it's possible to split instance implementation of
>>> type-classes into separate files. In Rust you can have a struct with
>>> private fields and put impl of behaviour in different files (but same
>>> crate).
>>> 
>>> A similar feature in PHP could look like (using new keyword `expand`
>>> but could be anything, or even `extend` in new context):
>>> 
>>> ```
>>> // File FooStringable.php
>>> expand Foo implements stringable {
>>>  public function __toString() {
>>>    // Full access to Foo's all private fields here.
>>>    // Assumes you can autoload Foo.
>>>    // Assumes usage of $foo->__toString(); will be configured with
>>> autoload to dynamically find the correct behaviour of Foo.
>>>  }
>>> }
>>> ```
>>> 
>>> If you'd use composition instead, you'd maybe have a formatter class
>>> with a method `$formatter->toString(stringable $foo)`. This has the
>>> problem I mentioned with exposing too much of $foo; it breaks
>>> encapsulation. It has the benefit of being able to provide multiple
>>> toString methods with different formats, but would have to assume
>>> similar structure of the objects passed to it (defined with an
>>> interface), which is not always possible or desirable.
>>> 
>>> The other way is inheritance, which doesn't scale over multiple
>>> behaviours. `FooWithStringable extends Foo`? No.
>>> 
>>> Was I clear here? Do you understand the issues that this design
>>> pattern is trying to solve? Its purpose is to solve "keep adding new
>>> feature to old data" in a clean and proper way, while keeping
>>> information encapsulation.
>>> 
>> 
>> Do I understand you correctly, it would be somewhat like "opening" up a
>> class and making changes to it in another file?
>> 
>> Certainly a powerful concept, but I would be very interested in the details
>> how that would interact with autoloading. If I have a class Foo loaded, and
>> its "extension" FooString with toString method not, then it would lead to
>> the "toString" code missing.
> 
> Yes, a little like opening up, *but* with clear restrictions. It was
> explained to me that this won't work without either:
> 
> 1) A module system to define which files are part of a class
> 2) Manually write in the "main" class file which other extensions to
> this class should be loaded.
> 
> The reason is again encapsulation - it should not be possible for any
> file to just get access to private fields by adding a new interface
> implementation.
> 
> Option (2) can be achieved if we allow "include <file>;" in PHP
> *inside* a class definition. Again note that this is different from a
> trait, since it gives access to private properties that are
> *different* between the classes using it, like toString() or
> toQuery().
> 
> Option (2) does not need configured autoloading. Option (1) is more
> elaborate, maybe composer.json would need to configure something, *or*
> it is assumed that a class is defined in a single folder instead of a
> single file. Which already is kind of like a module system.

Doesn't option 2 fail the very first criteria you argued for in your initial 
email?  Doesn't it fail the criteria "It should be possible to add new features 
without touching old code?" 

IOW, we should not have to modify any source code in Symfony to be able to 
expand one of its classes, for example. If modifying the original source is 
required, you really don't need a new syntax, just modify the class and add 
your toString method.

(BTW, your first criteria sounds like it would be enabling the Open/closed 
principle of S.O.L.I.D. for PHP classes.)

Also, I expect requiring a module system would eliminate this idea from even 
being considered given past discussions of "modules."

So, back to what Benjamin implied, for this to even be considered there would 
need to be an elegant and performant way to handle the autoloading of such 
class expansions. The challenge is — without annotating the class itself — how 
do you know when an expansion exists without first explicitly naming it or 
looping through the autoloader potentially many times per class?

-Mike

Reply via email to