Documentação do Symfony2
Renderizada do repositório symfony-docs-pt-BR no Github
Para permitir que várias classes adicionem métodos para uma outra, você pode definir o método mágico __call() na classe que você deseja que seja estendida da seguinte forma:
class Foo
{
// ...
public function __call($method, $arguments)
{
// cria um evento chamado 'foo.method_is_not_found'
$event = new HandleUndefinedMethodEvent($this, $method, $arguments);
$this->dispatcher->dispatch($this, 'foo.method_is_not_found', $event);
// nenhum listener foi capaz de processar o evento? O método não existe
if (!$event->isProcessed()) {
throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
}
// retorna o valor retornado pelo listener
return $event->getReturnValue();
}
}
Ela utiliza um HandleUndefinedMethodEvent especial que também deve ser criado. Esta é uma classe genérica que poderia ser reutilizada cada vez que você precisa utilizar esse padrão de extensão de classe:
use Symfony\Component\EventDispatcher\Event;
class HandleUndefinedMethodEvent extends Event
{
protected $subject;
protected $method;
protected $arguments;
protected $returnValue;
protected $isProcessed = false;
public function __construct($subject, $method, $arguments)
{
$this->subject = $subject;
$this->method = $method;
$this->arguments = $arguments;
}
public function getSubject()
{
return $this->subject;
}
public function getMethod()
{
return $this->method;
}
public function getArguments()
{
return $this->arguments;
}
/**
* Define o valor de retorno e pára a notificação para outros listeners
*/
public function setReturnValue($val)
{
$this->returnValue = $val;
$this->isProcessed = true;
$this->stopPropagation();
}
public function getReturnValue($val)
{
return $this->returnValue;
}
public function isProcessed()
{
return $this->isProcessed;
}
}
Em seguida, crie uma classe que vai ouvir o evento foo.method_is_not_found e adicionar o método bar():
class Bar
{
public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
{
// queremos somente responder as chamadas do método 'bar'
if ('bar' != $event->getMethod()) {
// permite que outro listener cuide deste método desconhecido
return;
}
// o objeto (a instância foo)
$foo = $event->getSubject();
// os argumentos do método bar
$arguments = $event->getArguments();
// faz algo
// ...
// define o valor de retorno
$event->setReturnValue($someValue);
}
}
Por fim, adicione o novo método bar na classe Foo para registrar uma instância de Bar com o evento foo.method_is_not_found:
$bar = new Bar();
$dispatcher->addListener('foo.method_is_not_found', $bar);