Массив в PHP — один из самых мощных типов данных. Он может работать как линейный массив (список), как ассоциативный (словарь) и как смешанный. В качестве ключа массива может использоваться либо целое число, либо строка, причем строка, если она представляет собой целое число (например, «5»), будет конвертирована в целое. Остальные типы, за исключением массивов и объектов, так же конвертируются в целое или строку — подробнее можно прочитать в документации.
Несмотря на мощные возможности базового типа array иногда хочется их расширить. Например, подобный кусок кода можно встретить, наверное, в большинстве php-проектов:
$foo = isset($array['foo']) ? $array['foo'] : null; $bar = isset($array['bar']) ? $array['bar'] : null;
Один из способов сделать этот код короче и элегантней — использовать короткую запись тернарного оператора:
$foo = $array['foo'] ? : null; $bar = $array['bar'] ? : null;
Но такой код выкинет PHP Notice в случе, когда ключ не определен, а я стараюсь писать максимально чистый код — на сервере разработки выставлено error_reporting = E_ALL
. И именно в подобных случаях на помощь приходит ArrayObject — класс, к объектам которого можно обращаться используя синтаксис массивов и позволяющий изменять поведение используя механизм наследования.
Рассмотрим несколько примеров изменения поведения.
В проекте, над которым я сейчас работаю, мы используем следующие базовые наследники ArrayObject
:
DefaultingArrayObject
— возвращает значение по умолчанию, если ключ не определен в массиве;ExceptionArrayObject
— бросает исключение, если ключ не определен в массиве;CallbackArrayObject
— значения массива являются функциями (замыканиями), которые возвращают некое значение.
DefaultingArrayObject
Этот тип массива ведет себя примерно как словарь в Python при вызове dict.get(key, default)
— если ключ не определен в массиве — возвращается значение по умолчанию. Это отлично работает в случае, когда значения по умолчанию у всех элементов, к которым мы обращаемся одинаковые, и не так элегантно, когда мы хотим получать разные значения в случае отсутствия ключа. Полный листинг этого класса выглядит следующим образом:
class DefaultingArrayObject extends \ArrayObject { protected $default = null; public function offsetGet($index) { if ($this->offsetExists($index)) { return parent::offsetGet($index); } else { return $this->getDefault(); } } /** * @param mixed $default * @return $this */ public function setDefault($default) { $this->default = $default; return $this; } /** * @return mixed */ public function getDefault() { return $this->default; } }
Используя этот класс, можно переписать код, который я использовал в качестве примера, следующим образом:
$array = new DefaultingArrayObject($array); $foo = $array['foo']; $bar = $array['bar'];
В случае разных значений по-умолчанию будет выглядеть не так красиво, и далеко не факт что эта запись лучше использования полной тернарной записи — просто покажу как это можно сделать (PHP 5.4+):
$array = new DefaultingArrayObject($array); $foo = $array->setDefault('default for foo')['foo']; $bar = $array->setDefault('default for bar')['bar'];
ExceptionArrayObject
Как я уже отмечал выше, PHP бросит Notice в случае отсутствия ключа в массиве, но иногда хочется контролировать это без использования кучи условных операторов, а логику выполнения контролировать при помощи исключений. Например, код вида:
if (isset($array['foo']) && isset($array['bar'] && isset($array['baz'])) { // logic that uses foo, bar and baz array values } else { // logic that does not use foo, bar and baz array values }
Можно переписать следующим образом:
$array = new ExceptionArrayObject($array); try { // logic that uses foo, bar and baz array values } catch (UndefinedIndexException $e) { // logic that does not use foo, bar and baz array values }
class ExceptionArrayObject extends \ArrayObject { public function offsetGet($index) { if ($this->offsetExists($index)) { return parent::offsetGet($index); } else { throw new UndefinedIndexException($index); } } }
class UndefinedIndexException extends \Exception { protected $index; public function __construct($index) { $this->index = $index; parent::__construct('Undefined index "' . $index . '"'); } /** * @return string */ public function getIndex() { return $this->index; } }
CallbackArrayObject
И напоследок еще один массив с нестандартным поведением. Вообще, этот тип массива вполне можно считать фабрикой, ибо элементы массива являются замыканиями (анонимными функциями), которые вызываются при обращении к соответствующим элементам массива и результат их выполнения возвращается вместо самого элемента. Думаю, что проще будет показать это на примере:
$array = new CallbackArrayObject([ 'foo' => function() { return 'foo ' . uniqid(); }, 'bar' => function() { return 'bar ' . time(); }, ]); $foo = $array['foo']; // "foo 526afed12969d" $bar = $array['bar']; // "bar 1382743789"
class CallbackArrayObject extends \ArrayObject { protected $initialized = array(); public function __construct(array $values) { foreach ($values as $key => $value) { if (!($value instanceof \Closure)) { throw new \RuntimeException('Value for CallbackArrayObject must be callback for key ' . $key); } } parent::__construct($values); } public function offsetGet($index) { if (!isset($this->initialized[$index])) { $this->initialized[$index] = $this->getCallbackResult(parent::offsetGet($index)); } return $this->initialized[$index]; } protected function getCallbackResult(\Closure $callback) { return call_user_func($callback); } }
В данном случае результат выполнения функции кэшируется и повторно функция вызвана не будет. Но сферические результаты в вакууме бывают интересны крайне редко, поэтому можно сделать их настраиваемыми, зависящими от какого-нибудь конфига:
$array = new ConfigurableCallbackArrayObject([ 'foo' => function($config) { return 'foo ' . $config['foo']; }, 'bar' => function($config) { return 'bar ' . $config['bar']; }, ]); $array->setConfig(['foo' => 123, 'bar' => 321]); $foo = $array['foo']; // "foo 123" $bar = $array['bar']; // "bar 321"
class ConfigurableCallbackArrayObject extends CallbackArrayObject { protected $config; protected function getCallbackResult(\Closure $callback) { return call_user_func($callback, $this->getConfig()); } public function setConfig($config) { $this->config = $config; } public function getConfig() { return $this->config; } }
Это все, что я хотел рассказать о примерах использовании ArrayObject. Думаю необходимо упомянуть, что как и во всем, при использовании ArrayObject нужно знать меру и понимать, когда использование изменяющих поведение массива классов оправдано, а когда проще просто вставить дополнительную проверку или пару лишних строк логики непосредственно в основной алгоритм, а не инкапсулировать их во вспомогательные классы. Иными словами — не плодить дополнительные сущности без необходимости.
ссылка на оригинал статьи http://habrahabr.ru/post/199096/
Добавить комментарий