Re: [PHP-DEV] Add interface implementation to class in separate file

This is only part of a thread. view whole thread
September 16, 2020 19:28 (=?UTF-8?Q?Olle_H=C3=A4rstedt?=)
2020-09-16 3:30 GMT, Mike Schinkel <>:
> > >> On Sep 13, 2020, at 3:49 PM, Olle Härstedt <> >> wrote: >> >> 2020-09-13 17:58 GMT, Benjamin Eberlei < >> <>>: >>> On Sat, Sep 12, 2020 at 10:23 PM Olle Härstedt <> >>> 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) >>>> ( >>>> >>>> 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 ;" 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?"
Yeah, kind of. Just trying to find the shortest path to make it work in PHP. At least it would be possible to split classes in this case, and also split data from behaviour if you want, while keeping full encapsulation.
> > 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."
I had a thought of namespace-private class properties, but I'll start another thread for that. Swift has access level "internal" for this purpose.
> 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?
Yeah, good points. I don't have an answer right now.
> > -Mike
Someone told me about extensions in Swift language, which is a good example of what I mean: Olle