Introduction and application of decorator in PHP design mode

Time:2021-12-2

This paper describes the introduction and application of decorator pattern of PHP design pattern. Share with you for your reference, as follows:

Usually, if we want to add functions to an object, we can either directly modify the object to add corresponding functions, or derive corresponding subclasses to extend, or use object composition. Obviously, it is not advisable to modify the corresponding class directly.

In object-oriented design, we should also try to use object composition rather than object inheritance to extend and reuse functions. Decorator mode is based on object combination, which can flexibly add required functions to objects, and its essence is dynamic combination. In a word, dynamics is a means, and combination is the purpose.

In other words, in this mode, we can adjust some contents or functions of existing objects, but we don’t need to modify the original object structure. Do you understand???

It can also be understood that we do not modify the existing class, but create another decorator class to dynamically expand the content to be modified through this decorator class. Its benefits are also obvious, as follows:

  • 1. We can ensure that the hierarchy of classes will not be confused by too many.
  • 2. When the modification of our requirements is very small, there is no need to change the original data structure.

Let’s take a look at a case in PHP design patterns:

/*** current requirements of the modified class: it is required to be able to dynamically add audio tracks to CDs and display CD audio track lists. Single line shall be used for display, and each track shall be prefixed with track good*/
class CD {
  public $trackList;
  function __construct()  {
    # code...
    $this->trackList=array();
  }
  public function addTrack($track){
    $this->trackList[]=$track;
  }
  public function getTrackList(){
    $output=" ";
    foreach ($this->trackList as $key => $value) {
      # code...
      $output.=($key+1).") {$value}. ";
    }
    return $output;
  }
}
/*Now the requirements have changed: the audio tracks output by the current instance are required to be capitalized. This requirement is not a requirement that changes greatly. There is no need to modify the base class or create a subclass of parent-child relationship. At this time, create a decorator class based on decorator pattern*/
class CDTrackListDecoratorCaps{
  private $_cd;
  public function __construct(CD $CD){
    $this->_cd=$CD;
  }
  public function makeCaps(){
    foreach ($this->_cd->trackList as $key => $value) {
      # code...
      $this->_ cd->trackList[$key]=strtoupper($value); // Convert to uppercase
    }
  }
}
//Client test
$myCD=new CD();
$trackList=array(  "what It Means",  "brr",  "goodBye" );
foreach ($trackList as $key => $value) {
  # code...
  $myCD->addTrack($value);
}
$myCDCaps=new CDTrackListDecoratorCaps($myCD);
$myCDCaps->makeCaps();
print "The CD contains the following tracks:".$myCD->getTrackList();

Let’s look at a popular but simple case:

  • Design a userinfo class, which has a userinfo array to store user name information
  • Add the user name through addUser
  • The getuserlist method prints out the user name information
  • Now we need to capitalize the added user information. We need not to change the original class and the original data structure
  • We designed a userinfodecorate class to complete the operation of this requirement. Just like decoration, we decorated the original data
  • The decorator mode is somewhat like the adapter mode, but it must be noted that the decorator is mainly the premise of not changing the existing object data structure

The code is as follows:

UserInfo.php

//Decorator mode, which adjusts some contents or functions of existing objects, but does not need to modify the original object structure. You can use decorator design mode
class UserInfo {
 public $userInfo = array(); 
 
 public function addUser($userInfo) {
 $this->userInfo[] = $userInfo;
 }
 
 public function getUserList() {
 print_r($this->userInfo);
 }
}
//Like the userinfodecorate decoration, change the user information output to uppercase format without changing the original userinfo class
<?php
include("UserInfo.php");
class UserInfoDecorate {
 
 public function makeCaps($UserInfo) {
 foreach ($UserInfo->userInfo as &$val) {
  $val = strtoupper($val);
 }
 }
 
}
$UserInfo = new UserInfo;
$UserInfo->addUser('zhu');
$UserInfo->addUser('initphp');
$UserInfoDecorate = new UserInfoDecorate;
$UserInfoDecorate->makeCaps($UserInfo);
$UserInfo->getUserList();

At this point, we should have a general understanding of decorator mode. Next, let’s take a look at the case of building decorator mode. Online, let’s first look at the directory structure:

|decorator   # Project root directory
|–Think   # Core class library
|—-Loder.php   # Auto load class
|—-decorator.php   # Decorator interface
|—-colorDecorator.php   # Color decorator
|—-sizeDecorator.php   # Font size decorator
|—-echoText.php   # Decorated person
|–Index.php # single entry file

After that, build the decorator interface, think / decorator.php, as follows:

<?php
/**
 *Decorator interface
 * Interface decorator
 * @package Think
 */
namespace Think;
interface decorator{
  public function beforeDraw();
  public function afterDraw();
}

Then there is the color decorator think / colordecorator.php, as follows:

<?php
/**
 *Color decorator
 */
namespace Think;
class colorDecorator implements decorator{
  protected $color;
  public function __construct($color) {
    $this->color = $color;
  }
  public function beforeDraw() {
    echo "color decorator :{$this->color}\n";
  }
  public function afterDraw() {
    echo "end color decorator\n";
  }
}

There is also the font size decorator think / sizedecorator.php, as follows:

<?php
/**
 *Font size decorator
 */
namespace Think;
class sizeDecorator implements decorator{
  protected $size;
  public function __construct($size) {
    $this->size = $size;
  }
  public function beforeDraw() {
    echo "size decorator {$this->size}\n";
  }
  public function afterDraw() {
    echo "end size decorator\n";
  }
}

And the decorated think / echotext.php, as follows:

<?php
/**
 *Decorated person
 */
namespace Think;
class echoText {
  protected $decorator = array(); // Store trimmer
  //Decoration method
  public function index() {
    //Call decorator pre operation
    $this->before();
    Echo "Hello, I'm the decorator \ n";
    //Perform trim post operation
    $this->after();
  }
  public function addDecorator(Decorator $decorator) {
    $this->decorator[] = $decorator;
  }
  //Perform decorator front operation first in first out
  public function before() {
    foreach ($this->decorator as $decorator){
      $decorator->beforeDraw();
    }
  }
  //Perform trim post operation 先进后出
  public function after() {
    $decorators = array_reverse($this->decorator);
    foreach ($decorators as $decorator){
      $decorator->afterDraw();
    }
  }
}

Another example is to automatically load think / loder.php, as follows:


<?php
namespace Think;
class Loder{
  static function autoload($class){
    require BASEDIR . '/' .str_replace('\\','/',$class) . '.php';
  }
}

Finally, the entry file index.php is as follows:

<?php
define('BASEDIR',__DIR__);
include BASEDIR . '/Think/Loder.php';
spl_autoload_register('\\Think\\Loder::autoload');
//Instantiate output class
$echo = new \Think\echoText();
//Add decorator
$echo->addDecorator(new \Think\colorDecorator('red'));
//Add decorator
$echo->addDecorator(new \Think\sizeDecorator('12'));
//Decoration method
$echo->index();

Let’s finally take another example, that is, the web service layer – providing JSON and XML decorators for rest services. Let’s look at the code:

RendererInterface.php

<?php
namespace DesignPatterns\Structural\Decorator;
/**
 *Renderinterface interface
 */
interface RendererInterface
{
  /**
   * render data
   *
   * @return mixed
   */
  public function renderData();
}

Webservice.php

<?php
namespace DesignPatterns\Structural\Decorator;
/**
 *WebService class
 */
class Webservice implements RendererInterface
{
  /**
   * @var mixed
   */
  protected $data;
  /**
   * @param mixed $data
   */
  public function __construct($data)
  {
    $this->data = $data;
  }
  /**
   * @return string
   */
  public function renderData()
  {
    return $this->data;
  }
}

Decorator.php

<?php
namespace DesignPatterns\Structural\Decorator;
/**
 *The decorator must implement the renderinterface interface, which is the main feature of the decorator mode,
 *Otherwise, it's not a decorator, but just a package class
 */
/**
 *Decorator class
 */
abstract class Decorator implements RendererInterface
{
  /**
   * @var RendererInterface
   */
  protected $wrapped;
  /**
   *The decorator component must be declared as a type so that the renderdata () method can be called in a subclass
   *
   * @param RendererInterface $wrappable
   */
  public function __construct(RendererInterface $wrappable)
  {
    $this->wrapped = $wrappable;
  }
}

RenderInXml.php

<?php
namespace DesignPatterns\Structural\Decorator;
/**
 *Renderinxml class
 */
class RenderInXml extends Decorator
{
  /**
   * render data as XML
   *
   * @return mixed|string
   */
  public function renderData()
  {
    $output = $this->wrapped->renderData();
    // do some fancy conversion to xml from array ...
    $doc = new \DOMDocument();
    foreach ($output as $key => $val) {
      $doc->appendChild($doc->createElement($key, $val));
    }
    return $doc->saveXML();
  }
}

RenderInJson.php

<?php
namespace DesignPatterns\Structural\Decorator;
/**
 *Renderinjason class
 */
class RenderInJson extends Decorator
{
  /**
   * render data as JSON
   *
   * @return mixed|string
   */
  public function renderData()
  {
    $output = $this->wrapped->renderData();
    return json_encode($output);
  }
}

Tests/DecoratorTest.php

<?php
namespace DesignPatterns\Structural\Decorator\Tests;
use DesignPatterns\Structural\Decorator;
/**
 *Decoratortest is used to test decorator mode
 */
class DecoratorTest extends \PHPUnit_Framework_TestCase
{
  protected $service;
  protected function setUp()
  {
    $this->service = new Decorator\Webservice(array('foo' => 'bar'));
  }
  public function testJsonDecorator()
  {
    // Wrap service with a JSON decorator for renderers
    $service = new Decorator\RenderInJson($this->service);
    // Our Renderer will now output JSON instead of an array
    $this->assertEquals('{"foo":"bar"}', $service->renderData());
  }
  public function testXmlDecorator()
  {
    // Wrap service with a XML decorator for renderers
    $service = new Decorator\RenderInXml($this->service);
    // Our Renderer will now output XML instead of an array
    $xml = '<?xml version="1.0"?><foo>bar</foo>';
    $this->assertXmlStringEqualsXmlString($xml, $service->renderData());
  }
  /**
   * The first key-point of this pattern :
   */
  public function testDecoratorMustImplementsRenderer()
  {
    $className = 'DesignPatterns\Structural\Decorator\Decorator';
    $interfaceName = 'DesignPatterns\Structural\Decorator\RendererInterface';
    $this->assertTrue(is_subclass_of($className, $interfaceName));
  }
  /**
   * Second key-point of this pattern : the decorator is type-hinted
   *
   * @expectedException \PHPUnit_Framework_Error
   */
  public function testDecoratorTypeHinted()
  {
    if (version_compare(PHP_VERSION, '7', '>=')) {
      throw new \PHPUnit_Framework_Error('Skip test for PHP 7', 0, __FILE__, __LINE__);
    }
    $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
  }
  /**
   * Second key-point of this pattern : the decorator is type-hinted
   *
   * @requires PHP 7
   * @expectedException TypeError
   */
  public function testDecoratorTypeHintedForPhp7()
  {
    $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass()));
  }
  /**
   * The decorator implements and wraps the same interface
   */
  public function testDecoratorOnlyAcceptRenderer()
  {
    $mock = $this->getMock('DesignPatterns\Structural\Decorator\RendererInterface');
    $dec = $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array($mock));
    $this->assertNotNull($dec);
  }
}

Well, that’s all for this record.

For more information about PHP, readers who are interested can see the topics on this site: introduction to PHP object-oriented programming, complete operation skills of PHP array, introduction to PHP basic syntax, summary of PHP operation and operator usage, summary of PHP character string usage, introduction to PHP + MySQL database operation, and Summary of common database operation skills in PHP

I hope this article will help you in PHP programming.