Мне интересно, может ли объект, принадлежащий классу коллекции, итерируемый, знать, что он повторяется, и знать, к какому классу коллекции он принадлежит? например
<?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.
Я немного поработал с Google и ничего не смог найти, наверное, это невозможно, потому что это где-то нарушает принцип ООП, но я все равно решил спросить!
Я думаю, что мы можем разделить это на следующие проблемы:
Collection
это итеративно Collection
должен
а. иметь названия запрещенных методов жестко (плохо) или же
б. быть в состоянии извлечь имена запрещенных методов из элементов коллекции
при выполнении итерации по коллекции он должен давать прокси к исходному объекту, перехватывая вызовы методов, которые не должны вызываться при итерации по коллекции
Это легко, просто заставьте его реализовать 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;
}
}
Вместо того, чтобы жестко кодировать запрещенные методы в коллекции, я бы предложил создать интерфейс, который должен реализовываться элементами коллекции, если это необходимо, например:
<?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',
];
}
}
Как предложено в другом ответе @akond, вы можете использовать ocramius/proxymanager
, и, в частности, Прокси-держатель значения перехватчика доступа.
Бежать
$ 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()
может вернуть ранее созданные прокси, если это необходимо.
Для справки смотрите:
Я должен создать два разных класса, чтобы соответствовать принципу единой ответственности. Один был бы Collection
, а другой будет Object
сам. Object
возвращаются только как члены Collection
, Каждый раз когда Collection
повторяется, он «загружает» соответствующий Object
s.
Если это кажется целесообразным, вы можете создать Ленивая загрузка прокси объекта-призрака для каждого из тех Object
s.