Skip to content

How to Extend a Class without Using Inheritance

Warning: You are browsing the documentation for Symfony 2.x, which is no longer maintained.

Read the updated version of this page for Symfony 7.1 (the current stable version).

To allow multiple classes to add methods to another one, you can define the magic __call() method in the class you want to be extended like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Foo
{
    // ...

    public function __call($method, $arguments)
    {
        // creates an event named 'foo.method_is_not_found'
        $event = new HandleUndefinedMethodEvent($this, $method, $arguments);
        $this->dispatcher->dispatch('foo.method_is_not_found', $event);

        // no listener was able to process the event? The method does not exist
        if (!$event->isProcessed()) {
            throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method));
        }

        // returns the listener returned value
        return $event->getReturnValue();
    }
}

This uses a special HandleUndefinedMethodEvent that should also be created. This is a generic class that could be reused each time you need to use this pattern of class extension:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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;
    }

    /**
     * Sets the value to return and stops other listeners from being notified
     */
    public function setReturnValue($returnValue)
    {
        $this->returnValue = $returnValue;
        $this->isProcessed = true;
        $this->stopPropagation();
    }

    public function getReturnValue()
    {
        return $this->returnValue;
    }

    public function isProcessed()
    {
        return $this->isProcessed;
    }
}

Next, create a class that will listen to the foo.method_is_not_found event and add the method bar():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Bar
{
    public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event)
    {
        // only respond to the calls to the 'bar' method
        if ('bar' != $event->getMethod()) {
            // allow another listener to take care of this unknown method
            return;
        }

        // the subject object (the foo instance)
        $foo = $event->getSubject();

        // the bar method arguments
        $arguments = $event->getArguments();

        // ... do something

        // set the return value
        $event->setReturnValue($someValue);
    }
}

Finally, add the new bar() method to the Foo class by registering an instance of Bar with the foo.method_is_not_found event:

1
2
$bar = new Bar();
$dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound'));
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
TOC
    Version