2.5. Decorator

2.5.1. Purpose

To dynamically add new functionality to class instances.

2.5.2. Examples

  • Zend Framework: decorators for Zend_Form_Element instances
  • Web Service Layer: Decorators JSON and XML for a REST service (inthis case, only one of these should be allowed of course)

2.5.3. UML Diagram

Alt Decorator UML Diagram

2.5.4. Code

You can also find this code on GitHub

Booking.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator;
  4.  
  5. interface Booking
  6. {
  7. public function calculatePrice(): int;
  8.  
  9. public function getDescription(): string;
  10. }

BookingDecorator.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator;
  4.  
  5. abstract class BookingDecorator implements Booking
  6. {
  7. /**
  8. * @var Booking
  9. */
  10. protected $booking;
  11.  
  12. public function __construct(Booking $booking)
  13. {
  14. $this->booking = $booking;
  15. }
  16. }

DoubleRoomBooking.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator;
  4.  
  5. class DoubleRoomBooking implements Booking
  6. {
  7. public function calculatePrice(): int
  8. {
  9. return 40;
  10. }
  11.  
  12. public function getDescription(): string
  13. {
  14. return 'double room';
  15. }
  16. }

ExtraBed.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator;
  4.  
  5. class ExtraBed extends BookingDecorator
  6. {
  7. private const PRICE = 30;
  8.  
  9. public function calculatePrice(): int
  10. {
  11. return $this->booking->calculatePrice() + self::PRICE;
  12. }
  13.  
  14. public function getDescription(): string
  15. {
  16. return $this->booking->getDescription() . ' with extra bed';
  17. }
  18. }

WiFi.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator;
  4.  
  5. class WiFi extends BookingDecorator
  6. {
  7. private const PRICE = 2;
  8.  
  9. public function calculatePrice(): int
  10. {
  11. return $this->booking->calculatePrice() + self::PRICE;
  12. }
  13.  
  14. public function getDescription(): string
  15. {
  16. return $this->booking->getDescription() . ' with wifi';
  17. }
  18. }

2.5.5. Test

Tests/DecoratorTest.php

  1. <?php
  2.  
  3. namespace DesignPatterns\Structural\Decorator\Tests;
  4.  
  5. use DesignPatterns\Structural\Decorator\DoubleRoomBooking;
  6. use DesignPatterns\Structural\Decorator\ExtraBed;
  7. use DesignPatterns\Structural\Decorator\WiFi;
  8. use PHPUnit\Framework\TestCase;
  9.  
  10. class DecoratorTest extends TestCase
  11. {
  12. public function testCanCalculatePriceForBasicDoubleRoomBooking()
  13. {
  14. $booking = new DoubleRoomBooking();
  15.  
  16. $this->assertEquals(40, $booking->calculatePrice());
  17. $this->assertEquals('double room', $booking->getDescription());
  18. }
  19.  
  20. public function testCanCalculatePriceForDoubleRoomBookingWithWiFi()
  21. {
  22. $booking = new DoubleRoomBooking();
  23. $booking = new WiFi($booking);
  24.  
  25. $this->assertEquals(42, $booking->calculatePrice());
  26. $this->assertEquals('double room with wifi', $booking->getDescription());
  27. }
  28.  
  29. public function testCanCalculatePriceForDoubleRoomBookingWithWiFiAndExtraBed()
  30. {
  31. $booking = new DoubleRoomBooking();
  32. $booking = new WiFi($booking);
  33. $booking = new ExtraBed($booking);
  34.  
  35. $this->assertEquals(72, $booking->calculatePrice());
  36. $this->assertEquals('double room with wifi with extra bed', $booking->getDescription());
  37. }
  38. }