Pull to refresh

Comments 39

Это лучше в доку по php запостить чем сюда, если, конечно, это кусок еще не перевели на русский.
Я бы хотел добавить пару комментариев к вашему коду:

Класс Iteratable:

public function __construct() 
{
    $this->_position = 0;
}

public function rewind() 
{
    $this->_position = 0;
}

Стоить заменить этот фрагмент на

public function __construct() 
{
    $this->rewind();
}

public function rewind() 
{
    $this->_position = 0;
}


Это обеспечит единый интерфейс для сброса итератора. Да и вообще, код избыточен (так как задано свойство protected $_position = 0;).

Класс ArrayAccessable

public function __construct($array = null)
{
    if (!is_null($array)) {
        $this->_container = $array;
    }
}


Мне больше нравится следующий подход:


protected container;

public function __construct() 
{
    $argv = func_get_args();
    $argc = func_num_args();

    if (argc == 0)
    {
        $this->container = array();
        return;
    }

    if (        $argc == 1
        AND  is_array(argv[0])) 
    {
        $this->container = argv[0];
    } 
    else 
    {
        $this->container = argv;
    }
}


Это позволяет использовать более красивое создание объекта:

// создадим контейнер из нескольких элементов
$arrayObject = new ArrayAccessable('a', 'b', 'c', 'd', 'e' => 'assoc');
// создадим контейнер из массива
$array = array('a', 'b', 'c', 'd', 'e' => 'assoc');
$arrayObject = new ArrayAccessable($array)


Единственный недостаток заключается в том, что функции типа array_pop() не будут с ним работать.


Я для себя эту проблему решил созданием методов, которые являются обертками над функциями для работы с массивами. Например:

        /**
         * Метод возвращает контейнер со списком всех ключей объекта
         *
         * @return      Map             Контейнер со списком ключей объекта
         */
        public function get_keys()
        {
            return new self(array_keys($this->container));
        }
>>Я для себя эту проблему решил созданием методов, которые являются обертками над функциями для работы с массивами. Например:

я стремился к более нативной реализации.
Ваш пример нельзя сократить с
$tmp = $array();
array_pop($tmp);
$array($tmp);
до
$last = array_pop($array());
это вызовет ошибку уровня E_STRICT. Данный подход, разумеется, имеет место, но методы-обертки позволяют использовать более короткий код
$last = $array->pop();
и цепочки методов
$set_fields = $query_params->get_keys->implode(' = ?, ') . ' =? ';
. Кроме того, вы вольны сами выбирать интерфейс объекта, что позволяет избавиться от legacy-кода SPL (который очень хорошо заметен в функциях работы со строками), и избавляетесь от одного из параметров вызова, который необходимо передавать в функцию.
Увы, но ассоциативные массивы так создать не получится. Я про код:
Извиняюсь, отправилось раньше, чем я успел дописать это:

// создадим контейнер из нескольких элементов
$arrayObject = new ArrayAccessable('a', 'b', 'c', 'd', 'e' => 'assoc');
а вы уверенны, что
$arrayObject = new ArrayAccessable('a', 'b', 'c', 'd', 'e' => 'assoc');

сработает?
мне на
var_dump('a' => 'b');
выдает
PHP Parse error: syntax error, unexpected T_DOUBLE_ARROW in Command line code on line 1
Parse error: syntax error, unexpected T_DOUBLE_ARROW in Command line code on line 1
Да, не сработает. Это я скопировал пример массива из статьи. С одномерными массивами работает. Ждем сокращенного синтаксиса для объявления массивов в PHP 5.4
в статье, кстати, new SemiArray(array('a', 'b', 'c', 'd', 'e' => 'assoc'));

многомерный массив сработает на ура.
пардон, $array = new ArrayAccessable(array('a', 'b', 'c', 'd', 'e' => 'assoc'));
Естественно, так как параметр конструктора напрямую передается в свойство класса. В моем примере для одномерных массивов можно использовать синтаксис
$array = new ArrayAccessable('a', 'b', 'c', 'd', 'e');
для многомерных
$array = new ArrayAccessable(array('a', 'b', 'c', 'd', 'e' => 'assoc'));
Кажется 100% обкатывалась эта тема. Много воды… лучше бы привели просто примеры использования.
оно то как бы массив, но как только чуть глубже начать с этим работать начинается ад
$a = new ArrayAccessable;
$a['x'] = array('foo' => 'bar');
$a['x']['foo'] = 1;
это только то что сверху
очень полезного применения я этим интерфейсам не нашел, для чего то простого не плохо, что-то посерьезней — костыль на костыле, костылем погоняет
т.е. эти интерфейсы мало того что хуже по функциональности стандартных структур данных, так они даже не нормальное ООП
Outro-то я и не заметил :) Но образовательные цели тоже сомнительны, в php не так много стандартных интерфейсов чтобы посвящать им целые статьи.
Afaik, он значительно медлительнее, чем собственная реализация на php
Не, на самом деле это не так, «своя реализация» чего-бы то ни было на php — всегда медленнее чем встроенный вариант на уровне си, и последний раз когда я проверял — ArrayObject не был исключением, хотя это тоже может варьироваться от версии к версии, может и были с ним когда-то какие-нибудь проблемы.
Из того, что мне лично не нравится в это классе:
0. В NetBeans в отладчике не видно, что содержится в контейнере.
1. Слишком мало встроенных методов, при добавлении новых начинают возникать некоторые проблемы, приводящие к костылям:
        /**
         * Метод возвращает первый элемент контейнера
         *
         * @return      mixed|null    Первый элемент контейнера. Если контейнер пуст - null
         */
        public function get_first()
        {
            foreach($this as $value)
            {
                return $value;
            }
        }

        /**
         * Метод возвращает последний элемент контейнера
         *
         * @return      mixed|null    Последний элемент контейнера. Если контейнер пуст - null
         */
        public function get_last()
        {
            if ($this->count() == 0)
            {
                return;
            }

            $iterator = $this->getIterator();

            // перейдем к последнему элементу
            $iterator->seek($this->count() - 1);

            return $iterator->current();
        }
Замечу, что использование метода getArrayCopy() приводит к тормозам, в зависимости от размера контейнера.
2. Разработчики PHP не отходят от старых традиций. Метод, который стоило бы назвать push(), как в других языках, назвали append().
0. Не аргумент, в eclipse видно :)
1. Ну вот это конечно аргумент, добавить в него свои методы порой проблематично, действительно приходится дергать getArrayCopy(), с другой стороны те что уже есть работают быстрее.
2. Весь PHP такой, что уж поделать.
Уже достаточно долго пользуемся данной мулькой. В целом в некоторых моментах она очень полезна и удобна.

Очень сильно упрощает код во View скриптах:
— легко позволяет подкладывать фикстурные массивы вместо объекта, в тестах и отладке просто незаменим.
— удобная работа с кешами. В контроллере берем из кеша массив данных и отдаем вьюхе, если в кеше все устарело — берем объект, кешируем его в виде массива и отдаем вьюхе. Девелопер нафиг вырубает кеш и спокойно работает, на продакшене врубаем кеш и девелопер вообще не заморачивается.

В контроллерах и моделях редко этим делом пользуемся.
— так как offsetGet не позволяет нормально передавать ссылку на данные, а тупо копирует и возвращает данные (из-за этого возникают проблемы с $a['abc']['var'] = 1; Т.е. берутся данные по ключу 'abc', возвращается копия массива, а затем к этой копии применяется установка значения 'var' = 1. Можно лечит возвратом не массива как это в примере этой статьи, а объекта подложки, который внутри себя хранит ссылку на единый источник данных. Т.е. все вложенные массивы, необходимо подменить своим классом. Вобщем страшный костыль.)

Так что в целом MUST USE в задачах где объекты нужны в режиме READ ONLY.
Вы, кстати, почитайте другие статьи по теме, и немножечко поменяйте код, ато и недо массив и пере класс получается (http://habrahabr.ru/blogs/personal/48697/) =)
И еще одно замечание =) Фореч забирает при переборе эеземпляр отданого ему обьекта/массива, чтоб чтоб обьект ну уж точно вел себя как массив нужно еще переопределить метод getIterator (да, его нет в интерфейсах), тогда он будет поддерживать и вложенный проход по одному и тому же обьекту.
getIterator это из другого интерфейса просто — IteratorAggregate, который то же наследуется от Traversable
Traversable — пустой интерфейс (маркер), он лишь говорит о том что по обьекту можно пройтись, а каким способом уже зависит от примененного интерфейса. Если Вы зайдете в статью что я привел, там в каментах человек жалуется что вложенные циклы не работают с обьектом так как с массивом.
Я к тому, что кроме Iterator есть IteratorAggregate.

Если вы про комментарий «Не поддерживается несколько обходов одновременно», то это очевидно. Для такой поддержки можно использовать IteratorAggregate и каждый раз возвращать новый объект итератора. Тот же ArrayObject замечательно справляется с поставленной задачей (он как раз используется getIterator)
Странная реализация методов rewind() и next(). Как Вы полагаете заставит их работать на ассоциированных массивах? У меня не получилось:

$array = new SemiArray(array('a'=>'a', 'b'=>'b', 'c'=>'c', 'd'=>'d', 'e'=>'assoc'));
$array->rewind();
echo $array->current(); // Notice: Undefined offset: 0

// В массиве же нет элемента с индексом 0
Наверное, по этой причине встроенный ArrayObject не содержит итераторов. Но не смотря на отсутствие итераторов, его можно прогнать через foreach:

$array = new ArrayObject(array('a'=>'a', 'b'=>'b', 'c'=>'c', 'd'=>'d', 'e'=>'assoc'));

foreach ($array as $k=>$v) { // Отработает правильно
echo $k. '='. $v .'';
}
$array->current(); // Fatal error: Call to undefined method ArrayObject::current()

Либо это магия, либо костыль на костыле.
Как не содержит?
ArrayObject implements IteratorAggregate,...
Я имел ввиду, что не содержит описанного в статье итератора Iterator и нет методов rewind(), current() и next(). И показанные методы не позволяют работать с ассоциированными массивами (а это даже не отмечено в статье).

Вместо этого, получается некоторая магия когда я использую foreach. Например, если я использую описанные здесь класс semiArray добавив вот это:
public function next()
{
echo 'next()'; // Это
++$this->_position;
}

Я вижу, что конструкция foreach для каждой итерации вызывает метод next(). Однако, встроенный ArrayObject не имеет метода next(), но foreach как-то работает! Получается, что foreach подстраивается под переданный ему тип вместо того, что бы использовать полиморфизм. Имеено такое поведение foreach мне кажется костылём.
Встроенный ArrayObject использует IteratorAggregate вместо Iterator, поэтому там нет всяких next.
foreach умеет работать с 2мя типами: массив и объект реализующий Traversable (наследниками от которого являются Iterator & IteratorAggregate)
/*
foreach умеет работать с 2мя типами: массив и объект реализующий Traversable (наследниками от которого являются Iterator & IteratorAggregate)
*/
Прочитав этом, можно сделать вывод, что Traversable заставляет оба класса реализовывать одни и те же методы (предоставить api), которое потом используются в foreach. И это было бы логично.

Но это не так. Traversable — интерфейс не содержащий ни одного метода. Это что же за интерфейс такой? А foreach использует разные методы для объектов $semiArray и $arrayObject. Т.е. нет никакого полиморфизма. Разве это не костыль?

P.S. Я бы написал так:
foreach умеет работать с 3мя типами: 1) массив, 2) объект реализующий Iterator и 3) объект реализующий IteratorAggregate.
Это правильно. Но в «обучающей» статье почему-то этого нет. Ассоциированные массивы не работают и об этом в статье умалчивается, хотя в разделе «тестируем» есть ассоциированный массив:

$array = new SemiArray(array('a', 'b', 'c', 'd', 'e' => 'assoc'));

Не внимательный читатель может сделать вывод, будто описанный в статье класс может работать с ассоциированными массивами.
Для сравнения примерно такая-же функциональность в Python:

Итератор

def Iterator():
    for elem in ['item1', 'item2', 'item3']:
        yield elem

В простейшем случае даже
iterator=(elem for elem in ['item1', 'item2', 'item3'])


Доступ по индексу:

class ArrayAccess(object):

    def __getitem__(self, key):
        return self.container[key]

Доступ по ключу
class ArrayAccess(object):

    def __getitem__(self, key):
        return self.container[key]

    def __setitem__(self, key, value):
        self.container[key]=value

    def __delitem__(self, key, value):
        del self.container[key]

длина
class Countable(object):

    def __len__(self):
        return len(self.container)
UFO just landed and posted this here
Я тоже привык использовать isset и empty. Надо просто не забывать, что в PHP нет строгой типизации. А в большинстве случаев функциональности этих двух конструкций вполне хватает.
Еще полезная функция array_filter без коллбэка, лишний цикл иногда можно не писать :)
И еще немного про isset и unset.
Можно писать так isset($array[1]) && isset($array[1][2]) => isset($array[1], $array[1][2]) => isset($array[1][2])
и аналогично unset($array[1]); unset($array[2]); => unset($array[1], $array[2]);
Sign up to leave a comment.

Articles