proxy Замісник (англ. Proxy) — структурний шаблон проєктування, надає об’єкт, який контролює доступ до іншого об’єкта, перехоплюючи всі виклики (виконує функцію контейнера).

Найбільш частим застосуванням шаблону Проксі є ліниве завантаження (lazy load). “Важкі” об’єкти не завжди розумно завантажувати в момент ініціалізації. Більш правильним рішенням буде завантажити його на першу вимогу.

Представимо нашу кухню (** Kitchen).

У нас є якийсь склад булок(Kitchen->muffinPool) для бургерів. Замовлення йдуть – булки витрачаються. У нас є контакти перевіреного постачальника (MuffinForwarder), який надає нам булки. У постачальника також є якесь сховище і зв’язок із фабрикою (MuffinFactory), яка виробляла булки. У нашому прикладі “Заступників” буде два. Це кухня та постачальник.

Розглянемо всю суть трохи ближче.

  • Фабрика(MuffinFactory) просто виробляє булки. Вам потрібна булка? MuffinFactory->getProduct() віддасть вам новий об’єкт типу Product.
  • Постачальник(MuffinForwarder) має якесь сховище для булок. MuffinForwarder->getProduct() віддасть вам об’єкт типу Product зі свого сховища. Як тільки булки закінчуються, він звертається до Фабрики та робить закупівлю.
  • Кухня(Kitchen) як і постачальник, має сховище. Kitchen->getProduct() так само віддасть вам об’єкт типу Product зі свого сховища. Як тільки є необхідність у булках, ми звертаємося до постачальника.

А зараз давайте зрозуміємо, навіщо нам все це потрібно? Якщо постачальник має булки, час їх доставляння на кухню займає якийсь час. Наприклад, 10 хвилин. Якщо у постачальника їх немає, він замовляє їх у фабрики. Виробництво однієї займає, наприклад, 1 хвилину. Час це ресурс, який споживають наші об’єкти. І ініціалізація їх потрібна лише за необхідності. Даний шаблон дуже схожий на Декоратора. Що б вловити суть, уявімо, що клієнту недоступна ні фабрика, ні постачальник. Він просто не зможе отримати продукт. Він знає, що є кухня, і все.

  • Factory
      Product
      • Product.php
                              
        <?php
        
        namespace DesignPatterns\Structural\Proxy\Factory\Product;
        
        /**
         * Class Product
         * @package DesignPatterns\Structural\Proxy\Factory\Product
         */
        class Product
        {
            public function __construct()
            {
            }
        }
        
                              
    • FactoryInterface.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Proxy\Factory;
      
      use DesignPatterns\Structural\Proxy\Factory\Product\Product;
      
      /**
       * Interface FactoryInterface
       * @package DesignPatterns\Structural\Proxy\Factory
       */
      interface FactoryInterface
      {
          /**
           * @return Product
           */
          public function getProduct(): Product;
      }
      
                    
    • MuffinFactory.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Proxy\Factory;
      
      use DesignPatterns\Structural\Proxy\Factory\Product\Product;
      
      /**
       * Class MuffinFactory
       * @package DesignPatterns\Structural\Proxy\Factory
       */
      class MuffinFactory implements FactoryInterface
      {
          /**
           * @return Product
           */
          public function getProduct(): Product
          {
              return new Product();
          }
      }
      
                    
  • Forwarder
    • MuffinForwarder.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Proxy\Forwarder;
      
      use DesignPatterns\Structural\Proxy\AbstractPurchase;
      use DesignPatterns\Structural\Proxy\Factory\FactoryInterface;
      use DesignPatterns\Structural\Proxy\Factory\MuffinFactory;
      use DesignPatterns\Structural\Proxy\Factory\Product\Product;
      
      /**
       * Class MuffinForwarder
       * @package DesignPatterns\Structural\Proxy\Forwarder
       */
      class MuffinForwarder extends AbstractPurchase
      {
          const PARTY_SIZE = 20;
      
          /**
           * @var FactoryInterface
           */
          protected $factory = null;
      
          /**
           * @var Product[]
           */
          public $muffinPool = [];
      
          /**
           * @return FactoryInterface
           */
          protected function getFactory(): FactoryInterface
          {
      
              if (is_null($this->factory)) {
                  $this->factory = new MuffinFactory();
              }
      
              return $this->factory;
          }
      
          /**
           * @return Product
           */
          public function getProduct(): Product
          {
              if (!count($this->muffinPool)) {
                  $this->purchaseProducts();
              }
      
              return array_pop($this->muffinPool);
          }
      
          /**
           * @return void
           */
          public function purchaseProducts()
          {
              for ($i = 0; $i < self::PARTY_SIZE; $i++) {
                  array_push(
                      $this->muffinPool,
                      $this->getFactory()->getProduct()
                  );
              }
      
              $this->incrementCountPurchase();
          }
      }
      
                    
  • Kitchen
    • Kitchen.php
                    
      <?php
      
      namespace DesignPatterns\Structural\Proxy\Kitchen;
      
      use DesignPatterns\Structural\Proxy\AbstractPurchase;
      use DesignPatterns\Structural\Proxy\Factory\Product\Product;
      use DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder;
      
      /**
       * Class Kitchen
       * @package DesignPatterns\Structural\Proxy\Kitchen
       */
      class Kitchen extends AbstractPurchase
      {
      
          const PARTY_SIZE = 5;
      
          /**
           * @var Product[]
           */
          public $muffinPool = [];
      
          /**
           * @var MuffinForwarder
           */
          protected $forwarder = null;
      
          /**
           * @return Product
           */
          public function getProduct(): Product
          {
              if (!count($this->muffinPool)) {
                  $this->purchaseProducts();
              }
      
              return array_pop($this->muffinPool);
          }
      
          /**
           * @return MuffinForwarder
           */
          public function getForwarder(): MuffinForwarder
          {
              if (is_null($this->forwarder)) {
                  $this->forwarder = new MuffinForwarder();
              }
      
              return $this->forwarder;
          }
      
          /**
           * @return void
           */
          public function purchaseProducts()
          {
              for ($i = 0; $i < self::PARTY_SIZE; $i++) {
                  array_push(
                      $this->muffinPool,
                      $this->getForwarder()->getProduct()
                  );
              }
      
              $this->incrementCountPurchase();
          }
      }
      
                    
  • AbstractPurchase.php
                  
    <?php
    
    namespace DesignPatterns\Structural\Proxy;
    
    /**
     * Class AbstractPurchase
     * @package DesignPatterns\Structural\Proxy
     */
    abstract class AbstractPurchase
    {
        public $muffinPurchaseCount = 0;
    
        /**
         * @return void
         */
        abstract public function purchaseProducts();
    
        /**
         * @return void
         */
        protected function incrementCountPurchase()
        {
            $this->muffinPurchaseCount++;
        }
    
        /**
         * @return int
         */
        public function getPartySize(): int
        {
            return static::PARTY_SIZE;
        }
    }
    
                  
  • Test case:

    ProxyTest.php
      
    <?php
    namespace DesignPatterns\Tests\Structural\Proxy;
    
    use DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder;
    use DesignPatterns\Structural\Proxy\Kitchen\Kitchen;
    
    class ProxyTest extends \PHPUnit_Framework_TestCase
    {
        /**
         * @var Kitchen
         */
        protected static $kitchen;
    
        public static function setUpBeforeClass(): void
        {
            self::$kitchen = new Kitchen();
        }
    
        public function testKitchenMuffinPoolEmpty()
        {
            $this->assertEquals(0, count(self::$kitchen->muffinPool));
        }
    
        public function testKitchenNotPurchaseMuffins()
        {
            $this->assertEquals(0, self::$kitchen->muffinPurchaseCount);
        }
    
        public function testKitchenGetForwarder()
        {
            $forwarder = self::$kitchen->getForwarder();
            $this->assertInstanceOf('DesignPatterns\Structural\Proxy\Forwarder\MuffinForwarder', $forwarder);
            $this->assertSame(self::$kitchen->getForwarder(), $forwarder);
    
            return $forwarder;
        }
    
        /**
         * @depends testKitchenGetForwarder
         *
         * @param MuffinForwarder $forwarder
         */
        public function testForwarderNotPurchaseMuffins(MuffinForwarder $forwarder)
        {
            $this->assertEquals(0, $forwarder->muffinPurchaseCount);
        }
    
        /**
         * @depends testKitchenGetForwarder
         *
         * @param MuffinForwarder $forwarder
         */
        public function testPurchase(MuffinForwarder $forwarder)
        {
            self::$kitchen->purchaseProducts();
            $this->assertEquals(self::$kitchen->getPartySize(), count(self::$kitchen->muffinPool));
            $this->assertEquals(1, self::$kitchen->muffinPurchaseCount);
            $this->assertEquals(1, $forwarder->muffinPurchaseCount);
        }
    
        public function testKitchenGetProduct()
        {
            $product = self::$kitchen->getProduct();
            $this->assertInstanceOf('DesignPatterns\Structural\Proxy\Factory\Product\Product', $product);
            $this->assertNotEquals(self::$kitchen->getPartySize(), count(self::$kitchen->muffinPool));
            $this->assertEquals(self::$kitchen->getPartySize() - 1, count(self::$kitchen->muffinPool));
        }
    }