How to implement a decorator in PHP?
Asked Answered
R

5

34

Suppose there is a class called "Class_A", it has a member function called "func".

I want the "func" to do some extra work by wrapping Class_A in a decorator class.

$worker = new Decorator(new Original());

Can someone give an example? I've never used OO with PHP.

Is the following version right?

class Decorator
{
    protected $jobs2do;

    public function __construct($string) {
        $this->jobs2do[] = $this->do;
    }

    public function do() {
        // ...
    }
}

The above code intends to put some extra work to a array.

Raglan answered 4/6, 2009 at 3:24 Comment(5)
You should initialize your variable: protected $jobs2do = array();Overturf
But that's still not the standard version,maybe should use something like 'call_user_func'Raglan
Oh, didn't see that! Yes of course, there are no closures before PHP 5.3. You should definitely have a look at callbacks in PHP: us.php.net/call-user-funcOverturf
I wrote this decorator abstract to support flexible decorators: gist.github.com/CMCDragonkai/6896642 derived from jrgns.net/decorator-pattern-implemented-properly-in-php/…Bloomer
sourcemaking.com/design_patterns/decorator/php fits the bill. Would answer but I don't want to just copy and paste his work.Communize
O
46

I would suggest that you also create a unified interface (or even an abstract base class) for the decorators and the objects you want decorated.

To continue the above example provided you could have something like:

interface IDecoratedText
{
    public function __toString();
}

Then of course modify both Text and LeetText to implement the interface.

class Text implements IDecoratedText
{
    // same implementation as above
}

class LeetText implements IDecoratedText
{    
    protected $text;

    public function __construct(IDecoratedText $text) {
        $this->text = $text;
    }

    public function __toString() {
        return str_replace(['e', 'i', 'l', 't', 'o'], [3, 1, 1, 7, 0], $this->text->toString());
    }

}

Why use an interface?

Because then you can add as many decorators as you like and be assured that each decorator (or object to be decorated) will have all the required functionality.

Overweigh answered 4/6, 2009 at 4:51 Comment(7)
That's why I said it is especially easy in PHP: You don't need such things there :) If you really want type-safety, you should be using another language.Overturf
I don't see the benefits of using an interface hereKacerek
Thanks,but how to pile a new member function to an array?seems my current version will cause warning.Raglan
The interface is not about type safety, per se. It helps debugging and provides clarity. Debugging example: you will get a "object does not implement the IDecoratedText interface" as opposed to the more generic "method does not exist" error. Better design: 1. This design also clearly communicates to future developers (without extraneous comments) what type of objects this decorator works on (as opposed to a generic $text attribute). 2. The interface makes it clear which methods future objects (either decorators or "decoratees") need to implement.Overweigh
There are also IDEs (e.g. PhpStorm) that provide semantic checks. By adding type information (e.g. IDecoratedText) to a variable, PhpStorm will point out whether you're calling methods provided by the type.Chazan
What if when you stack decorators? How can you make each decorator implement an interface?Bloomer
the interface is there to enforce class integrity. A method then accepts the interface instead of direct implementation and teh interface ensures those methods are implemented. You simply can't write a decent OOP application without knowing thisAllerie
O
33

That is pretty easy, especially in a dynamically typed language like PHP:

class Text {

    protected $string;

    /**
     * @param string $string
     */
    public function __construct($string) {
        $this->string = $string;
    }

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

class LeetText {

    protected $text;

    /**
     * @param Text $text A Text object.
     */
    public function __construct($text) {
        $this->text = $text;
    }

    public function __toString() {
        return strtr($this->text->__toString(), 'eilto', '31170');
    }
}

$text = new LeetText(new Text('Hello world'));
echo $text; // H3110 w0r1d

You may want to have a look at the wikipedia article, too.

Overturf answered 4/6, 2009 at 4:16 Comment(5)
Thanks,but how to pile a new member function to an array?seems my current version will cause warning.Raglan
Why $string and $text are not private?Buckles
public function __construct($text) is not clear. What does $text mean? How does one know from looking at it what should go in there? Mistakes are made too easy.Ops
Sorry, looks like I forgot to document my code snippet. What's the standard here on SO, anyway? Where can I find the coding guide for this forum? Do we use doxygen-style doc comments or javadoc? Or are there different guidlines for different languages?Overturf
For a Decorator, shouldn't you be able to call any Text methods directly from LeetText? It should add functionality, which yours does, but your example also takes away functionality. E.g. if you had a reverse() method on Text, how would you call it from $text? I think you need to work with __call() and pass method calls back to protected $text for this to be a Decorator.Congest
C
27

None of these answers implements Decorator properly and elegantly. mrmonkington's answer comes close, but you don't need to use reflection to enact the Decorator pattern in PHP. In another thread, @Gordon shows how to use a decorator for logging SOAP activity. Here's how he does it:

class SoapClientLogger
{
    protected $soapClient;

    // this is standard. Use your constuctor to set up a reference to the decorated object.
    public function __construct(SoapClient $client)
    {
        $this->soapClient = $client;
    }

    ... overridden and / or new methods here ...

    // route all other method calls directly to soapClient
    public function __call($method, $args)
    {
        // you could also add method_exists check here
        return call_user_func_array(array($this->soapClient, $method), $args);
    }
}

And he's a slight modification where you can pass the desired functionality to the constructor:

class Decorator {

    private $o;

    public function __construct($object, $function_name, $function) {
        $this->o = $object;
        $this->$function_name = $function;
    }
    public function __call($method, $args)
    {
        if (!method_exists($this->o, $method)) {
            throw new Exception("Undefined method $method attempt in the Url class here.");
        }
        return call_user_func_array(array($this->o, $method), $args);
    }   
}
Congest answered 11/3, 2013 at 15:29 Comment(2)
You should improve the __call() method to support objects which implement the fluent-api pattern. something like $result = call_user_func_array(array($this->o, $method), $args); return $result === $this->o ? $this : $result;Nevermore
please explain the usage and workflow of $function_nameTrilly
B
6

I wanted to use decoration to encourage colleagues to use caching more, and inspired by the nice Python syntax experimented with PHP reflection to fake this language feature (and I have to stress 'fake'). It was a useful approach. Here's an example:

class MrClass { 
  /**
   * decoratorname-paramname: 50
   * decoratorname-paramname2: 30
   */
   public function a_method( $args ) {
     // do some stuff
   }
}


class DecoratorClass {
  public function __construct( $obj ) {
     $this->obj = $obj;
     $this->refl = new ReflectionClass( $obj );
  }
  public function __call( $name, $args ) {
    $method = $this->refl->getMethod( $name );
    // get method's doccomment
    $com = trim( $method->getDocComment() );
    // extract decorator params from $com
    $ret = call_user_func_array( array( $this->obj, $name), $args );
    // perhaps modify $ret based on things found in $com
    return $ret;
}

Better examples with caching examples here: https://github.com/gamernetwork/EggCup

Bifoliolate answered 17/8, 2011 at 18:7 Comment(1)
This is more kind of Proxy Pattern!!Clover
F
-3

A key and unique feature of the Decorator is that one abstract class extends another. The Decorator participant is both a wrapper for and extends the Component participant.

<?php
abstract class Decorator extends IComponent
{
    //public function getDescription() { }
}
?>

I believe that this is the only pattern where this happens in the Gang of Four catalog. The Decorator makes it easy to add properties to an object without changing the object. For a simple, accurate and clear example see:

http://www.php5dp.com/php-decorator-design-pattern-accessorizing-your-classes/#more-32

Filling answered 31/7, 2014 at 1:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.