6

我想知道一个属于集合类的对象在被迭代时是否可以知道它正在被迭代并知道它所属的集合类?例如

<?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 主体,但我想我还是会问!

4

2 回答 2

2

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

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

    一种。将禁止方法的名称硬编码(坏)

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

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

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()代理,如果需要,可以返回以前创建的代理。

供参考,请参阅:

于 2017-08-14T11:35:56.507 回答
1

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

如果看起来合适,您可能希望为每个s创建一个延迟加载 ghost 对象代理。Object

于 2017-08-14T07:43:47.443 回答