当作为集合的一部分迭代它们时,是否可以拦截对象的方法?

时间:2017-08-14 07:01:58

标签: php oop iterator proxy-object

我想知道属于集合类的对象在迭代时是否可以知道它正在被迭代并知道它所属的集合类? e.g。

<?php
class ExampleObject
{
    public function myMethod()
    {
        if( functionForIterationCheck() ) {
           throw new Exception('Please do not call myMethod during iteration in ' . functionToGetIteratorClass());
        }
    }
}

$collection = new CollectionClass([
    new ExampleObject,
    new ExampleObject,
    new ExampleObject
]);

foreach($collection as $item) {
    $item->myMethod(); //Exception should be thrown.
}

(new ExampleObject)->myMethod(); //No Exception thrown.

我已经做了一些谷歌并且找不到任何东西,我猜不可能,因为它打破了某个地方的OOP校长,但我认为我是这样的。无论如何,请问!

2 个答案:

答案 0 :(得分:2)

我认为我们可以将其分解为以下问题:

  1. 我们需要创建一个可迭代的Collection
  2. Collection应该

    一个。具有硬编码(坏)

    的禁止方法的名称

    湾能够从集合的元素中获取被禁方法的名称

  3. 在迭代集合时,它应该生成原始对象的代理,拦截对迭代集合时不应该被允许调用的方法的调用

  4. 1)集合应该是可迭代的

    这很容易,只需让它实现Iterator接口:

    class Collection implements \Iterator
    {
        /**
         * @var array
         */
        private $elements;
    
        /**
         * @var int
         */
        private $key;
    
        public function __construct(array $elements)
        {
            // use array_values() here to normalize keys 
            $this->elements = array_values($elements);
            $this->key = 0;
        }
    
        public function current()
        {
            return $this->elements[$this->key];
        }
    
        public function next()
        {
            ++$this->key;
        }
    
        public function key()
        {
            return $this->key;
        }
    
        public function valid()
        {
            return array_key_exists(
                $this->key,
                $this->elements
            );
        }
    
        public function rewind()
        {
            $this->key = 0;
        }
    }
    

    2)集合应该能够从元素

    中获取方法

    我建议创建一个接口,而不是将禁止的方法硬编码到集合中,如果需要,可以通过集合的元素实现,例如:

    <?php
    
    interface HasProhibitedMethods
    {
        /**
         * Returns an array of method names which are prohibited 
         * to be called when implementing class is element of a collection.
         *
         * @return string[]
         */
        public function prohibitedMethods();
    }
    

    这也具有以下优点:只要能够从元素中获取该信息,该集合将适用于所有类型的元素。

    然后让你的元素,如果需要,实现接口:

    class Element implements HasProhibitedMethods
    { 
        public function foo()
        {
            return 'foo';
        }
    
        public function bar()
        {
            return 'bar';
        }
    
        public function baz()
        {
            return 'baz';
        }
    
        public function prohibitedMethods()
        {
            return [
                'foo',
                'bar',
            ];
        }
    }
    

    3)迭代时,产生代理

    正如@akond在其他答案中所建议的那样,您可以使用ocramius/proxymanager,具体而言,使用Access Interceptor Value Holder Proxy

    运行

    $ composer require ocramius/proxymanager
    

    将其添加到您的项目中。

    按如下方式调整集合:

    <?php
    
    use ProxyManager\Factory\AccessInterceptorValueHolderFactory;
    
    class Collection implements \Iterator
    {
        /**
         * @var array
         */
        private $elements;
    
        /**
         * @var int
         */
        private $key;
    
        /**
         * @var AccessInterceptorValueHolderFactory
         */
        private $proxyFactory;
    
        public function __construct(array $elements)
        {
            $this->elements = array_values($elements);
            $this->key = 0;
            $this->proxyFactory = new AccessInterceptorValueHolderFactory();
        }
    
        public function current()
        {
            $element = $this->elements[$key];
    
            // if the element is not an object that implements the desired interface
            // just return it
            if (!$element instanceof HasProhibitedMethods) {
                return $element;
            }
    
            // fetch methods which are prohibited and should be intercepted
            $prohibitedMethods = $element->prohibitedMethods();
    
            // prepare the configuration for the factory, a map of method names 
            // and closures that should be invoked before the actual method will be called
            $configuration = array_combine(
                $prohibitedMethods,
                array_map(function ($prohibitedMethod) {
                    // return a closure which, when invoked, throws an exception
                    return function () use ($prohibitedMethod) {
                        throw new \RuntimeException(sprintf(
                            'Method "%s" can not be called during iteration',
                            $prohibitedMethod
                        ));
                    };
                }, $prohibitedMethods)
            );
    
            return $this->proxyFactory->createProxy(
                $element,
                $configuration
            );
        }
    
        public function next()
        {
            ++$this->key;
        }
    
        public function key()
        {
            return $this->key;
        }
    
        public function valid()
        {
            return array_key_exists(
                $this->key,
                $this->elements
            );
        }
    
        public function rewind()
        {
            $this->key = 0;
        }
    }
    

    实施例

    <?php
    
    require_once __DIR__ .'/vendor/autoload.php';
    
    $elements = [
        new Element(),
        new Element(),
        new Element(),
    ];
    
    $collection = new Collection($elements);
    
    foreach ($collection as $element) {
        $element->foo();
    }
    

    注意这仍然可以进行优化,例如,您可以在Collection中存储对已创建代理的引用,而不是每次都创建新代理current()如果需要,可以返回先前创建的代理。

    供参考,见:

答案 1 :(得分:1)

我应该创建两个不同的类,以遵守单一责任原则。一个是Collection,另一个是Object本身。 Object仅作为Collection的成员返回。每次Collection迭代时,它都会“加载”相应的Object s。

如果合适,您可能希望为每个Object创建a Lazy loading ghost object proxy