Pull to refresh

Comments 30

$array = new CallbackArrayObject([
    'foo' => function() {
        return 'foo ' . uniqid();
    },
    'bar' => function() {
        return 'bar ' . time();
    },
]);

$foo = $array['foo']; // "foo 526afed12969d"
$bar = $array['bar']; //  "bar 1382743789" 

Очень страшно!) Никак не ожидаешь что простой вывод данных из массива может потянуть за собой кучу действий, о которых я могу только гадать (запросы в БД, запись/удаление файлов и т.д.).

public function offsetGet($index)
    {
        if (!isset($this->initialized[$index])) {
            $this->initialized[$index] = $this->getCallbackResult(parent::offsetGet($index));
        }
        return $this->initialized[$index];
    }


Здесь если метод getCallbackResult вернет null, то повторно будет выполнен код, isset()
Лучше использовать array_key_exists(), так точно отработает один раз.
Количество дополнительных проверок уже зивисит от конкретной задачи, в которой планируется применять эти структуры. В каких-то случаях может понадобиться объединить CallbackArrayObject и DefaultingArrayObject/ExceptionArrayObject. В статье показаны только возможности.
Если такое встретить в коде то совсем непонятно что тут происходит
$array = new DefaultingArrayObject($array);
$foo = $array->setDefault('default for foo')['foo'];

Мне куда понятней было бы увидеть:
$array = new DefaultingArrayObject($array);
$foo = $array->get('foo', 'default for foo');
Согласен, и я указал что это не самая лучшая форма записи. То, что так можно делать, совсем не значит что так делать нужно. Насчет вашего варианта — это уже не работа с массивом, а работа с объектом и это уже немного другая история — такое можно организовать и не на базе ArrayObject, а просто классом, инкапсулирующим массив.
Каждый под себя что-то делает

Я, например, использую что-то вроде:
<?php

    function valueFromArray(array $data, $map, $default = '')
        {
        if(!is_array($map))
            {
            $map = array($map);
            }

        foreach($map as $m)
            {
            if(is_array($data))
                {
                if(array_key_exists($data[$m]))
                    {
                    $data = $data[$m];

                    continue;
                    }
                else
                    {
                    $data = $default;

                    break;
                    }
                }
            break;
            }

        return $data;
        }


echo valueFromArray($array, 'name');

echo valueFromArray($array, 'email', 'empty');

echo valueFromArray($array, array('settings', 'auth'), 'default');
?>
Отличная статья! Ничего кроме первых двух примеров не знал. Очень полезно, спасибо.
думаю лучше будет так

protected function getCallbackResult(\Closure $callback)
{
    return $callback();
}


или так

protected function getCallbackResult(callable $callback)
{
    return call_user_func($callback);
}
Если использовать тайп хинт Closure, то лаконичнее запись $callback().
Если использовать тайп хинт callable, то можно передавать не только анонимные функции, но и такие штуки:
call_user_func('my_callback_function');
call_user_func(array('MyClass', 'myCallbackMethod')); 
call_user_func(array($obj, 'myCallbackMethod'));
call_user_func('MyClass::myCallbackMethod');
Ок, согласен. Спасибо.
Хинт callable можно использовать только с 5.4+, а поскольку 5.3 будет официально поддерживаться еще до 2014-07, то, возможно, рановато отказываться от его поддержки в приложениях. Плюс к этому неплохо было бы проверить на то, а валидный ли колбек передали. С учетом сказанного, я бы переписал это вот так:

protected function getCallbackResult($callback)
{
if (is_callable($callback)) {
return call_user_func($callback);
}
throw new \Exception('Invalid callable provided');
}

Разумеется, что можно изменить Exception — на свой тип, или же просто возвращать null.

Кстати, этот метод имеет один, возможно, неприятный побочный эффект. А именно: внутри класса is_callable() будет true даже для private и protected методов. Соответственно существует возможность передать туда array($classInstance, 'privateOrProtectedMethod') и оно будет успешно выполнено. Так что нужно иметь ввиду данную особенность.
Очень спорная реализация, но идея наследования от базовых классов для заточки их под себя оправдывает написание статьи.
К моему стыду крайнему удивлению, все мои эксперименты попытки внедрить SPL/ArrayObject в реальный проект заканчивались примерно таким вот образом:

<?php
$a = new ArrayObject();
echo (int)is_array($a);

0
Да, есть такое, но оно и не удивительно — массив это отдельный тип данных.

Насчет внедрения — я считаю что переход от массивов к ArrayObject дело, в общем случае, не то чтобы не полезное, но даже вредное. Использовать объекты имеет смысл только в отдельных конкретных случаях, когда есть понимание, что с ними будет проще, чем без них.
Тут еще подводный камень в том, что стандартные array_XXXXX функции не будут работать с этим объектом. Нужно кастать к array, но и на выходе получим array, а не arrayObject. Так что если использовать arrayObject, то нужно использовать его везде.
Скопирую свой комментарий чуть выше:

Насчет внедрения — я считаю что переход от массивов к ArrayObject дело, в общем случае, не то чтобы не полезное, но даже вредное. Использовать объекты имеет смысл только в отдельных конкретных случаях, когда есть понимание, что с ними будет проще, чем без них.
Тут чем дальше под воду, тем толще камни.
is_array("scalar") // false;
is_array((array)"scalar") // true; счастливой отладки

В результате — с SPL/ArrayObject приходится четко мониторить, чтобы порожденные объекты не выходили за пределы «своего» кода и не пытались использоваться каким-либо другим образом.
а что собственно в вашем примере «камни»?
(array)«scalar» в данном случае преобразуется в array(0 => 'scalar')
Предположим, что переменная не объявлена парой строчек выше, а приходит извне (и мы ожидаем массив), и нам ее нужно передать в функцию array_foo().

function foo1($bar){
  return array_merge(array(1), $bar);
}

До приведения:
* если $bar — массив, все ок
* если $bar is ArrayObject — получаем варнинг
* иначе — получаем варнинг

После приведения:
* если $bar — массив, все ок
* если $bar is ArrayObject — все ок
* иначе — счастливой отладки

В итоге код начинает пестрить костылями типа ($foo instanceof ArrayObject) || is_array($foo)
<?php
    public function __call($func, $argv)
    {
        if (!is_callable($func) || substr($func, 0, 6) !== 'array_')
        {
            throw new BadMethodCallException(__CLASS__.'->'.$func);
        }
        return call_user_func_array($func, array_merge(array($this->getArrayCopy()), $argv));
    }
?>

это по поводу нерабочих array_* функций. Не панацея, но всё же)
Написать свои методы не проблема. Я про то, что array_values($obj) по прежнему не будет работать.
Нужно понимать, что в данный момент мы работаем с объектом, а не с массивом. Я это хотел сказать.
Если применить шаблон проектирования «Костылинг», то можно воспользоваться override_function и переопределить все нужные функции.
При этом нужно учеть, что оно "(PECL apd >= 0.2)".
Что нужно чтобы ArrayObject использовать в реальных проектах?

1. Нужно чтобы функции работы с массивами работали с ArrayObject также, как и с массивами
2. Обычный массив должен иметь поведение идентичное простейшей реализации ArrayObject (http://www.php.net/manual/ru/class.arrayobject.php ). Вплоть до
([] instanceof ArrayObject) === true;

3. Было бы классно, если бы это не повлияло на производительность.
4. Единственный способ отличить обычнй массив от кастомизированного: $array instanceof MyArrayObject

Но меня терзают смутные сомнения по поводу совместимости таких нововведений с существующими расширениями, т.ч. это будет не скоро, если вообще будет.
Этот код (не прм в таком виде, в расширенном) используется в реальном проекте. Но я, пожалуй, процитирую еще раз свою мысль:

Насчет внедрения — я считаю что переход от массивов к ArrayObject дело, в общем случае, не то чтобы не полезное, но даже вредное. Использовать объекты имеет смысл только в отдельных конкретных случаях, когда есть понимание, что с ними будет проще, чем без них.
Полностью согласен. Мой комментарий не столько про код из статьи, сколько про ArrayObject в общем.
А вы бенчмарки делали? Насколько медленнее работает?
UFO just landed and posted this here
Рекомендую не писать кучу иссетов, когда они в условии собираются через &&. Проще так:
if (isset($array['foo'], $array['bar'], $array['baz'])) {
}
Подумалось… а операторы сравнения можно как-то сократить?
Вроде такого: if ($val=='one' || $val=='two' || $val=='three') {… }?
Не всегда короче получается, но явно нагляднее:
in_array($val, array('one', 'two', 'three'))

или
switch ($val) {
case 'one': 
case 'two':
case 'three':
// ...
break;
default:
// ...
break;
}

в зависимости от того, есть ли elseif или else.
Sign up to leave a comment.