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));
        }
    }