У меня есть класс с именем Rule, и я собираюсь создать класс RuleContainer, который на самом деле массив объектов Rule.
Интересно, есть ли альтернатива созданию нового класса. Есть ли (современный) способ решения этой проблемы? То есть что-то вроде использования SPL для определения массив, который позволяет только добавлять объекты определенного класса.
Если нет, какой интерфейс я должен реализовать в своем классе RuleContainer?
Самый подходящий класс для вашей задачи будет SplObjectStorage
, но он не учитывает типовые подсказки.
Я думаю, вы могли бы сделать следующее:
class RuleContainer extends SplObjectStorage
{
function attach(Rule $rule)
{
parent::attach($rule);
}
function detach(Rule $rule)
{
parent::detach($rule);
}
}
и так далее. Вы можете прочитать для SplObjectStorage
интерфейс на php.net
и решить, что вы будете использовать и что нужно переопределить.
В вашем случае я бы реализовал Iterator
интерфейс в RuleContainer
, как я делал несколько раз, когда мне нужно было Collection<T>
как мы знаем из других (типизированных) языков. И в add(Rule $item)
или же addItem(Rule $item)
метод, который я бы удостоверился с определением типа аргумента (или используя instanceof
) что добавляемый элемент имеет тип Rule
,
В зависимости от шаблонов использования для вашего класса контейнера вам необходимо реализовать один или несколько из этих интерфейсов:
Iterator
— использовать его как foreach($container as $key => $value)
;Countable
— за count($container)
;ArrayAccess
— за $container[$key]
(установите это, получите это, проверьте, если это isset()
, unset()
Это);Использование интерфейсов PHP массив-рутины
Вы можете достичь своей цели, например, ArrayAccess
реализация. Вместе с Iterator
это будет выглядеть так:
class ArrayStorage implements Iterator, ArrayAccess
{
private $holder = [];
private $instanceName;
public function __construct($instanceName)
{
if (!class_exists($instanceName)) {
throw new \Exception('Class '.$instanceName.' was not found');
}
$this->instanceName = $instanceName;
}
public function rewind()
{
reset($this->holder);
}
public function current()
{
return current($this->holder);
}
public function key()
{
return key($this->holder);
}
public function next()
{
next($this->holder);
}
public function valid()
{
return false !== $this->current();
}
public function offsetSet($offset, $value)
{
if (!($value instanceof $this->instanceName)) {
throw new \Exception('Storage allows only '.$this->instanceName.' instances');
}
if (is_null($offset)) {
$this->holder[] = $value;
} else {
$this->holder[$offset] = $value;
}
}
public function offsetExists($offset)
{
return isset($this->holder[$offset]);
}
public function offsetUnset($offset)
{
unset($this->holder[$offset]);
}
public function offsetGet($offset)
{
return isset($this->holder[$offset]) ? $this->holder[$offset] : null;
}
}
Procs
Так что — да, вы делаете instanceof
проверьте явно, но конечный пользователь вашего класса не знает об этом. Можно будет работать только с действительными экземплярами в контексте этого хранилища (вы можете проверить эта скрипка за образец использования). Концепция как:
$storage = new ArrayStorage('Foo'); //define what we will accept
$storage[] = new Foo; //fine, [] array-writing
$storage['baz'] = new Foo; //fine, key set
foreach ($storage as $key => $value) {
echo($key. ' => '.PHP_EOL.var_export($value, 1).PHP_EOL);
}
//invalid, will not pass. Either throw exception or just ignore:
$storage['bee'] = new Bar;
Поведение конечной проверки на отказ зависит от вас, но, на мой взгляд, выбрасывать исключения — лучший выбор, так как они доступны, поэтому конечный пользователь может решить, что делать в этом случае. Дальнейший вариант может быть добавить Countable
в хранилище, но это не изменит общей идеи.
И минусы
Недостаток — нет, вы не сможете как-то «напечатать». Хотя это и полезно, в документах вы все равно должны будете показать, какую сущность вы принимаете. С точки зрения общих языковых особенностей, есть массив RFC, Джо Уоткинс, который был предложен для PHP версии 5.6, но, к сожалению, не удалось. Может быть, это будет пересмотрено в следующих версиях релизов.
Ты можешь сделать RuleContainer
себя (как ты говоришь) и делай всякие хитрости, чтобы принудительно применять его, но ты живешь в реальном мире, я живу в реальном мире, а ты просто не для этого нужен контейнерный объект, просто используйте массив.
Если ваша проблема просто одна из enforcement
субъекта типа объекта а ля List<className>()
вы не можете сделать это в PHP и, честно говоря, он является дискуссионным в тех языках, на которых он найден (я знаю, что за него скажут, но я все равно буду прав) // исключая его, можно еще больше уточнить назначение списка // Во всех Честно говоря, мои 20 с лишним лет программирования практически на всех языках (кроме машинного кода perl и fortran), я могу сказать вам, что такие конструкции просто не стоят человеческих накладных расходов, и сами по себе могут включать косвенные непреднамеренные нагрузки, превышающие их реальную ценность:
Простой компромисс: нет лени, начать именовать массив больше, чем что-то вроде tmpList
и если вы абсолютны, решите, простое test
в http://php.net/manual/en/function.get-class.php в начале forloops вы наверняка в конечном итоге используете