Re: Add interface implementation to class in separate file

From: Date: Sun, 13 Sep 2020 19:49:37 +0000
Subject: Re: Add interface implementation to class in separate file
References: 1 2  Groups: php.internals 
Request: Send a blank email to internals+get-111852@lists.php.net to get a copy of this message
2020-09-13 17:58 GMT, Benjamin Eberlei <kontakt@beberlei.de>: > On Sat, Sep 12, 2020 at 10:23 PM Olle Härstedt <olleharstedt@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. In Rust, it's possible to spread out behaviour (impl) to a certain data (struct) in the same _crate_ while keeping access to private fields: https://users.rust-lang.org/t/implement-private-struct-in-different-files/29407 C# has something called "partial class": https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods Olle

« previous php.internals (#111852) next »