Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type safe generic decorator in PHP

I'd like to create a generic logging decorator that is type safe.

I have a number of repositories (interfaces), and need a decorator for each that catches the exceptions they might throw, passes those to an instance of LoggerInterface and then rethrows them. It's possible to create a dedicated decorator and test for each, though this is quite cumbersome (especially doing the test nicely), and something I rather avoid.

Using __call to create a more generic decorator is the first approach that comes to mind. This however results in an object instance that does not implement the repository interface it decorates. That is a no go in my project. Is there some way to tell PHP that it does implement this interface, such as by use of some magic reflection?

I've read "how to implement a decorator in PHP?" and "Best way to implement a decorator pattern for method result caching in PHP" here on stackoverflow, both which outline the dedicated and the generic approach, though neither offer an indication of doing the generic approach in a type safe manner. Some time has passed since those questions where posted, so perhaps things have changed. I'm using PHP 7 and can use PHP 7.1 if needed.

PHPUnit_MockObject allows constructing an object implementing an interface, via the same code that gets invoked by the familiar getMock method in PHPUnit. That can be the basis of a generic decorator. However that would require using a mocking library in production code. Furthermore, this library internally uses eval to gets its job done. This disqualifies it for my project.

like image 756
Jeroen De Dauw Avatar asked Nov 08 '22 15:11

Jeroen De Dauw


1 Answers

Another approach would be to dynamically create the decorated classes at runtime. Unfortunately PHP doesn't allow this out of the box. If the runkit extension is not an option, you could emulate what Doctrine ORM does: Have a DecoratorFactory that does the following steps:

  • use Reflection on the original class to get all the interfaces and their methods,
  • generate a file with PHP code containing a decorated proxy class that implements all the interfaces
  • include the generated proxy class
  • instantiate the generated proxy class with the original class and return the proxy class.

See an example at http://www.doctrine-project.org/api/orm/2.4/source-class-Doctrine.ORM.Tools.EntityGenerator.html

like image 149
chiborg Avatar answered Nov 14 '22 23:11

chiborg