Created
July 3, 2023 11:22
-
-
Save brzuchal/89e9481bbd34a6ce3d95a68eabff038b to your computer and use it in GitHub Desktop.
Interface Default Method Usecase
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * @template T | |
| */ | |
| class EntityResult { | |
| public readonly $hasNextPage; | |
| public function __construct( | |
| public readonly array $entities, | |
| public readonly int $page, | |
| public readonly int $perPage, | |
| public readonly int $total, | |
| ) { | |
| $this->hasNextPage = $total > ($page * $perPage); | |
| } | |
| } | |
| /** | |
| * @template T | |
| */ | |
| interface EntityResource { | |
| /** | |
| * @return EntityResult<T> | |
| */ | |
| public function list(int $page = 1, int $perPage): EntityResult | |
| /** | |
| * @return iterable<T> | |
| */ | |
| public function stream(): iterable; // here the trait method can be implemented as default | |
| } | |
| trait IteratesEntityResult { // if interface has default method the trait is no longer needed | |
| public function stream(): iterable { | |
| $page = 1; | |
| do { | |
| $list = $this->list(page: $page++); | |
| yield from $list->entities; | |
| } while ($list->hasNextPage); | |
| } | |
| } | |
| class GuzzleEntityResource implements EntityResource { | |
| use IteratesEntityResult; | |
| /** | |
| * @return EntityResult<T> | |
| */ | |
| public function list(int $page = 1, int $perPage): EntityResult { | |
| $entities = []; // fetch entities, and populate $page, $perPage and $total | |
| return new EntityResult($entities, $page, $perPage, $total); | |
| } | |
| } | |
| class DecoratedEntityResource implements EntityResource { | |
| use IteratesEntityResult; | |
| public function __construct(private EntityResource $decorated) {} | |
| /** | |
| * @return EntityResult<T> | |
| */ | |
| public function list(int $page = 1, int $perPage): EntityResult { | |
| return $this->decorated->list($entities, $page, $perPage, $total); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I am well aware of that.
Adding implementation details to abstract contract definitions though is not the solution to that as it reduces interfaces to an implementation detail. That violates the separation of concerns and architectural principles. It also encourages sloppy design of interfaces to name a few drawbacks.
The current approach of using interfaces for the contract definition and backing these with traits for default implementations is the best option we have to separate the concerns of clean and abstract architectural design and business-specific implementation details.
An alternative to me would for example be a different way of handling traits in PHP. Like
The difference here would be that the Interface would not be implemented by the class but by the Trait and thereby also implicitly by the class that uses that trait.
I'd rather favour explicitness and that way would still mean the interface needs to be declared somewhere but at least on the class level there is no need to think about two different entitities.