facade Фасад (англ. Facade) - относиться к классу структурных шаблонов. Представляет собой унифицированный интерфейс вместо набора интерфейсов некоторой подсистемы.

Фасад предназначен для разделения клиента и подсистемы. Фасад не запрещает прямой доступ к подсистеме. Он делает его проще и понятнее.

Сам шаблон очень похож на Строитель. Не стоит забывать, что Строитель порождающий, а Фасад - структурный. Когда наш клиент делает заказ, он не хочет знать что, где, когда происходит. Он просто делает заказ у кассира(CashMan). А кассир уже знает, что делать с этими данными, и с какими частями системы взаимодействовать для достижения результата.

  • BurgerWorker.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    /**
     * Class BurgerWorker
     * @package DesignPatterns\Structural\Facade
     */
    class BurgerWorker
    {
        /**
         * @var array
         */
        protected $burgers = [
            'hamburger'    => [
                'muffin'     => 'muffin',
                'meat'       => 'beef',
                'sauce'      => 'ketchup',
                'withCheese' => false,
            ],
            'cheeseburger' => [
                'muffin'     => 'muffin',
                'meat'       => 'chicken',
                'sauce'      => 'mayonnaise',
                'withCheese' => true,
            ],
        ];
    
        /**
         * @var array
         */
        protected $prices = [
            'hamburger'    => 12.5,
            'cheeseburger' => 14.25,
        ];
    
        /**
         * @param string $burgerName
         *
         * @return array
         */
        public function make(string $burgerName): array
        {
            return $this->burgers[$burgerName];
        }
    
        /**
         * @param string $burgerName
         *
         * @return float
         */
        public function getPrice(string $burgerName): float
        {
            return $this->prices[$burgerName];
        }
    }
    
                  
  • CashBox.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    use Exception;
    
    /**
     * Class CashBox
     * @package DesignPatterns\Structural\Facade
     */
    class CashBox
    {
        /**
         * @var float
         */
        protected $cash;
    
        /**
         * CashBox constructor.
         *
         * @param float $cash
         */
        public function __construct(float $cash)
        {
            $this->cash = $cash;
        }
    
        /**
         * @return float
         */
        public function getCash(): float
        {
            return $this->cash;
        }
    
        /**
         * @param $orderPrice
         * @param $cash
         *
         * @return float
         * @throws Exception
         */
        public function makeOrder($orderPrice, $cash): float
        {
            $change = $cash - $orderPrice;
    
            if ($change < 0) {
                throw new Exception('Need more money.');
            }
    
            $this->cash += $cash - $change;
    
            return $change;
        }
    }
    
                  
  • CashMan.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    use InvalidArgumentException;
    
    /**
     * Class CashMan
     * @package DesignPatterns\Structural\Facade
     */
    class CashMan
    {
        /**
         * @var BurgerWorker
         */
        protected $burgerWorker;
    
        /**
         * @var WaterWorker
         */
        protected $waterWorker;
    
        /**
         * @var CashBox
         */
        protected $cashBox;
    
        /**
         * @var array
         */
        protected $order = [];
    
        /**
         * @var float
         */
        protected $orderPrice = 0.0;
    
        /**
         * CashMan constructor.
         *
         * @param BurgerWorker $burgerWorker
         * @param WaterWorker $waterWorker
         * @param CashBox $cashBox
         */
        public function __construct(BurgerWorker $burgerWorker, WaterWorker $waterWorker, CashBox $cashBox)
        {
            $this->burgerWorker = $burgerWorker;
            $this->waterWorker = $waterWorker;
            $this->cashBox = $cashBox;
        }
    
        /**
         * @param array $order
         * @param $cash
         *
         * @return array
         */
        public function makeOrder(array $order, $cash)
        {
            if (!array_key_exists('waters', $order) && !array_key_exists('burgers', $order)) {
                throw new InvalidArgumentException();
            }
    
            if (array_key_exists('waters', $order)) {
                $this->setProductsToOrderWithPrice($this->waterWorker, $order['waters']);
            }
    
            if (array_key_exists('burgers', $order)) {
                $this->setProductsToOrderWithPrice($this->burgerWorker, $order['burgers']);
            }
    
            $change = $this->cashBox->makeOrder($this->orderPrice, $cash);
    
            $fullOrder = [
                'order'  => $this->order,
                'price'  => $this->orderPrice,
                'change' => $change,
            ];
    
            $this->resetResult();
    
            return $fullOrder;
        }
    
        /**
         * @param WaterWorker|BurgerWorker $worker
         * @param array|string $products
         *
         * @return void
         */
        protected function setProductsToOrderWithPrice($worker, $products)
        {
            if (is_array($products)) {
                foreach ($products as $product) {
                    $this->setProductAndPrice($worker, $product);
                }
            } else {
                $this->setProductAndPrice($worker, $products);
            }
        }
    
        /**
         * @param WaterWorker|BurgerWorker $worker
         * @param string $product
         *
         * @return void
         */
        protected function setProductAndPrice($worker, $product)
        {
            $this->order[] = $worker->make($product);
            $this->orderPrice += $worker->getPrice($product);
        }
    
        /**
         * @return void
         */
        protected function resetResult()
        {
            $this->orderPrice = 0;
            $this->order = [];
        }
    }
    
                  
  • WaterWorker.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    /**
     * Class WaterWorker
     * @package DesignPatterns\Structural\Facade
     */
    class WaterWorker
    {
        /**
         * @var array
         */
        protected $waters = [
            'cocaCola' => 'Coca-Cola',
            'fanta'    => 'Fanta',
            'sprite'   => 'Sprite',
        ];
    
        /**
         * @var array
         */
        protected $prices = [
            'cocaCola' => 2,
            'fanta'    => 3,
            'sprite'   => 1,
        ];
    
        /**
         * @param string $waterName
         *
         * @return string
         */
        public function make(string $waterName): string
        {
            return $this->waters[$waterName];
        }
    
        /**
         * @param string $waterName
         *
         * @return float
         */
        public function getPrice(string $waterName): float
        {
            return $this->prices[$waterName];
        }
    }
    
                  
  • Test case:

    FacadeTest.php
      
    <?php
    
    namespace DesignPatterns\Structural\Facade;
    
    class FacadeTest extends \PHPUnit_Framework_TestCase
    {
        const MONEY_IN_CASH_BOX = 1000;
    
        protected $firstOrder = [
            'burgers' => 'hamburger',
            'waters'  => 'fanta',
            'cash'    => 30,
        ];
    
        protected $secondOrder = [
            'burgers' => [
                'hamburger',
                'cheeseburger',
            ],
            'waters'  => 'cocaCola',
            'cash'    => 50,
        ];
    
        /**
         * @var CashBox
         */
        protected $cashBox;
    
        /**
         * @var CashMan
         */
        protected $cashMan;
    
        /**
         * @var BurgerWorker
         */
        protected $burgerWorker;
    
        /**
         * @var WaterWorker
         */
        protected $waterWorker;
    
        public function setUp(): void
        {
            $this->cashBox = new CashBox(self::MONEY_IN_CASH_BOX);
            $this->burgerWorker = new BurgerWorker();
            $this->waterWorker = new WaterWorker();
    
            $this->cashMan = new CashMan(
                $this->burgerWorker,
                $this->waterWorker,
                $this->cashBox
            );
        }
    
        public function testMakeFirstOrder()
        {
            $this->assertEquals($this->cashBox->getCash(), self::MONEY_IN_CASH_BOX);
    
            $firstOrder = $this->cashMan->makeOrder($this->firstOrder, $this->firstOrder['cash']);
    
            $this->assertIsArray($firstOrder);
            $this->assertArrayHasKey('price', $firstOrder);
            $this->assertArrayHasKey('change', $firstOrder);
    
            $this->assertEquals($firstOrder['change'], $this->firstOrder['cash'] - $firstOrder['price']);
    
            $this->assertEquals($this->cashBox->getCash(), self::MONEY_IN_CASH_BOX + $firstOrder['price']);
    
            return $firstOrder;
        }
    
        public function testMakeSecondOrder()
        {
            $secondOrder = $this->cashMan->makeOrder($this->secondOrder, $this->secondOrder['cash']);
    
            $this->assertIsArray($secondOrder);
            $this->assertArrayHasKey('price', $secondOrder);
            $this->assertArrayHasKey('change', $secondOrder);
    
            $this->assertEquals($secondOrder['change'], $this->secondOrder['cash'] - $secondOrder['price']);
    
            $this->assertEquals(
                $this->cashBox->getCash(),
                self::MONEY_IN_CASH_BOX + $secondOrder['price']
            );
        }
    }