Skip to content

Instantly share code, notes, and snippets.

@itsjavi
Last active May 29, 2017 01:02
Show Gist options
  • Save itsjavi/782f3fa392dea55ef4eed54ba4bc0f22 to your computer and use it in GitHub Desktop.
Save itsjavi/782f3fa392dea55ef4eed54ba4bc0f22 to your computer and use it in GitHub Desktop.
PHP Service Proxy + Lazy Proxy patterns
<?php
namespace Foo;
use BadMethodCallException;
use Closure;
use DomainException;
/*
* This example encapsulates before/after method call callbacks inside a the service itself, using a proxy,
* so the object can be easily transportable.
* This is useful for example in dependency injection.
*/
interface Service
{
// some methods
}
class ServiceProxy implements Service
{
/**
* The original service
*
* @var Service
*/
protected $service;
/**
* @var Closure
*/
protected $before;
/**
* @var Closure
*/
protected $after;
public function __construct(Service $service, Closure $before = null, Closure $after = null)
{
$this->service = $service;
$this->before = $before;
$this->after = $after;
}
public function __call($method, $arguments)
{
$service = $this->getOriginalService();
if ( ! method_exists($service, $method)) {
throw new BadMethodCallException(get_class($service) . '::' . $method . ' does not exist.');
}
if ($this->before) {
$this->before->bindTo($service);
$arguments = $this->before->__invoke($service, $method, $arguments);
if ( ! array($arguments)) {
throw new DomainException('The before hook should return back the arguments array.');
}
}
$result = $service->$method(...$arguments);
if ($this->after) {
$this->after->bindTo($service);
$result = $this->after->__invoke($service, $method, $arguments, $result);
}
return $result;
}
// TODO: implement __get, __set, __isset and __unset for proxying them to the original service.
/**
* @return Service
*/
public function getOriginalService()
{
return $this->service;
}
}
// Class useful for lazy dependency injection
class LazyServiceProxy extends ServiceProxy
{
/**
* @var callable
*/
protected $factory;
/**
*
* @param callable $originalServiceFactory
*/
public function __construct(callable $factory, Closure $before = null, Closure $after = null)
{
$this->factory = $factory;
$this->before = $before;
$this->after = $after;
}
public function getOriginalService()
{
if (!$this->service) {
$this->service = call_user_func_array($this->factory, func_get_args());
}
return $this->service;
}
}
abstract class AbstractService implements Service
{
/**
* @return ServiceProxy|LazyServiceProxy|static
*/
public static function create(array $arguments = [], Closure $before = null, Closure $after = null, $lazy = false)
{
$serviceClass = static::class;
$factory = function() use($serviceClass, $arguments) {
return new $serviceClass(...$arguments);
};
if(is_null($before) && is_null($after) && !$lazy) {
// No need to proxy
return $factory();
}
return $lazy ? new LazyServiceProxy($factory, $before, $after) : new ServiceProxy($factory(), $before, $after);
}
/**
* Wraps this service into a service proxy
* @return ServiceProxy|static
*/
public function getProxy(Closure $before = null, Closure $after = null)
{
/** @var ServiceProxy|static $serviceProxy */
return new ServiceProxy($this, $before, $after);
}
}
class FooService extends AbstractService
{
public function bar($num1, $num2)
{
return $num1 * $num2 - $num1;
}
}
$beforeCall = function ($originalService, $method, $arguments) {
echo " : ";
return $arguments;
};
$afterCall = function ($originalService, $method, $arguments, $result) {
echo strval($result) . " ";
return $result;
};
$proxy = FooService::create([], $beforeCall, $afterCall, true);
/** @var FooService|ServiceProxy $proxy */
echo "PROXIED: " . PHP_EOL;
for($i = 1; $i < 10; $i++){
FooService::create([], $beforeCall, $afterCall, false)->bar($i, $i + 1);
}
echo PHP_EOL;
echo "NOT PROXIED: " . PHP_EOL;
// this won't show any output, because it will use the original services:
FooService::create([], $beforeCall, $afterCall)->getOriginalService()->bar(5, 6);
FooService::create()->bar(7, 8);
echo "END." . PHP_EOL;
@itsjavi
Copy link
Author

itsjavi commented May 28, 2017

Drawbacks of this pattern: when proxying, instanceof and type hinting won't work as expected,
e.g. (FooService::create([],null,null,true) instanceof FooService) === false

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment