当作为集合的一部分对其进行迭代时,对象的方法是否可以被拦截?
Can methods of objects be intercepted when iterating over them as part of a collection?
我想知道是否属于集合 class 的对象在被迭代时可以知道它正在被迭代并知道它所属的集合 class?例如
<?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'ing 但找不到任何东西,我猜这是不可能的,因为它在某处破坏了 OOP 原则,但我想我还是要问一下!
我应该创建两个不同的 类 以符合单一责任原则。一个是 Collection
,另一个是 Object
本身。 Object
s 仅作为 Collection
的成员返回。每次 Collection
迭代时,它 "loads" 对应 Object
s。
如果合适,您可能希望为每个 Object
创建 a Lazy loading ghost object proxy。
我认为我们可以将其分解为以下问题:
- 我们需要创建一个可迭代的
Collection
Collection
应该
一个。将禁止方法的名称硬编码(不好)或
b。能够从集合的元素中获取禁止方法的名称
在遍历集合时,它应该生成原始对象的代理,拦截对在遍历集合时不应被调用的方法的调用
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) 迭代时,yield proxies
正如@akond 在另一个答案中所建议的那样,您可以使用 ocramius/proxymanager
, and specifically, an 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()
可以 return 以前创建的代理,如果需要的话。
参考:
我想知道是否属于集合 class 的对象在被迭代时可以知道它正在被迭代并知道它所属的集合 class?例如
<?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'ing 但找不到任何东西,我猜这是不可能的,因为它在某处破坏了 OOP 原则,但我想我还是要问一下!
我应该创建两个不同的 类 以符合单一责任原则。一个是 Collection
,另一个是 Object
本身。 Object
s 仅作为 Collection
的成员返回。每次 Collection
迭代时,它 "loads" 对应 Object
s。
如果合适,您可能希望为每个 Object
创建 a Lazy loading ghost object proxy。
我认为我们可以将其分解为以下问题:
- 我们需要创建一个可迭代的
Collection
Collection
应该一个。将禁止方法的名称硬编码(不好)或
b。能够从集合的元素中获取禁止方法的名称
在遍历集合时,它应该生成原始对象的代理,拦截对在遍历集合时不应被调用的方法的调用
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) 迭代时,yield proxies
正如@akond 在另一个答案中所建议的那样,您可以使用 ocramius/proxymanager
, and specifically, an 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()
可以 return 以前创建的代理,如果需要的话。
参考: