Коллекции объектов в PHP

На протяжении последних 5 лет я работаю с PHP. У него есть достаточно разных проблем, но это никогда не мешало создавать отлично работающие продукты.

Не смотря на это, есть ряд вещей, которые выполняются внутри достаточно «криво». Один из вопросов, который постоянно тратил мои нервы, был вопрос работы с множествами объектов с помощью массивом данных.

На мой взгляд, массивы в PHP имеют достаточно широкую функциональность, что, кстати, является одной из проблем при работе с множествами объектов.

Что не нравилось?


В результате было принято решение о создании пакета, который должен работать с множествами однотипных объектов (коллекциями).

И так, представляю вашему вниманию пакет Rmk\Collection.

Интерфейсы


Интерфейсы коллекций предназначены для описания функциональности и классификации типов коллекций.

Базовым интерфейсом коллекций является интерфейс Collection.

Интерфейсы Map, Set, List, Queue и Deque наследуют интерфейс Collection и добавляют собственную функциональность.

Интерфейс Iterable предназначен для обхода объектов коллекции. Он поддерживается всеми интерфейсами коллекций.

image

Iterable
Данный интерфейс предоставляет возможность выполнять обход объектов коллекции и применять к каждому объекту пользовательскую функцию.

Collection
Данный интерфейс предназначен для описания базовых функций работы с множеством объектов. Он наследует интерфейсы Countable и Iterable, что позволяет получать количество объектов в коллекции и выполнять обход и применение пользовательской функции для каждого объекта коллекции. Интерфейс коллекции подразумевает, что в коллекции находятся объекты одного типа.

Map
Данный интерфейс предназначен для описания функциональности карты объектов. Карта объектов предназначена для построения ассоциативных связей между объектами, где один из объектов является ключем, а другой значением. Интерфейс карты объектов подразумевает, что в карте находятся ключи одного типа. Интерфейс карты объектов подразумевает, что в карте находятся значения одного типа. Интерфейс карты объектов наследует интерфейс Collection.

Set
Данный интерфейс предназначен для описания функциональности набора объектов, где объекты являются уникальными в рамках коллекции. Уникальность объектов осуществляется с помощью метода getIdentity(). Интерфейс набора объектов наследует интерфейс Collection.

Queue
Данный интерфейс предназначен для описания функциональности очереди объектов. Очередь объектов предназначена для описания структуры данных, когда объекты добавляются в конец очереди, а забираются с начала очереди. Интерфейс очереди объектов наследует интерфейс Collection.

Deque
Данный интерфейс предназначен для описания функциональности двунаправленной очереди объектов. Интерфейс двунаправленной очереди объектов наследует интерфейс очереди объектов Queue и добавляет дополнительную функциональность. Функциональность двунаправленной очереди объектов подразумевает работу с очередью с обеих сторон.

SequentialList
Данный интерфейс предназначен для описания функциональности списка объектов. Списком объектов является последовательность объектов, где объекты хранятся под последовательными целочисленными индексами. Кроме общей функциональности списка объектов, данный интерфейс определяет метод reverseEach() аналогичный методу Iterable::each() за исключением того, что метод reverseEach() обходит список в обратном порядке. Интерфейс списка объектов наследует интерфейс Collection.

Реализации карт



Карты представлены реализациями HashMap и HashStore.

Часть функциональности HashMap и HashStore наследуется от абстрактных классов AbstractCollection и AbstractMap.

Внутренняя структура карт HashMap и HashStore построена на основе сети ассоциативных связей. Это дает возможность реализовывать все операции карт с помощью ассоциативных выборок, что очень сильно повышает скорость их работы. Сложность работы алгоритмов карт равна O(1), что означает, что время установки/получения объектов не изменяется в зависимости от размера карт.

Карты HashMap и HashStore поддерживают любые типы данных для ключей и значений. Это является функциональным преимуществом по сравнению с стандартными Php массивами.

Ключи карты HashMap являются уникальными. Значения карты HashMap не являются уникальными, что позволяет ассоциировать одно значение с несколькими ключами.

Ключи и значения карты HashStore являются уникальными, что позволяет организовывать хранилище уникальных ассоциированных объектов.

Карта HashStore работает в среднем на 20% быстрее карты HashMap. Данное преимущество получено за счет уникальности объектов в HashStore, что требует меньшего количества ассоциативных связей.

image

Реализации наборов


Наборы представлены единственной реализацией UniqueStore.

Объекты в хранилище UniqueStore. Уникальность объектов обеспечивается за счет метода getIdentity(), который возвращает идентификаторы объектов. В хранилище UniqueStore не могут присутствовать несколько объектов с одинаковыми идентификаторами.

Внутренняя структура хранилища уникальных объектов UniqueStore построена на основе ассоциативных связей между объектами и их идентификаторами. Это дает возможность реализовывать все операции хранилища с помощью ассоциативных выборок, что очень сильно повышает скорость его работы. Сложность работы алгоритмов хранилища уникальных объектов равна O(1), что означает, что время установки/получения объектов не изменяется в зависимости от размера хранилища.

Хранилище уникальных объектов UniqueStore поддерживает любые типы данных для значений.
image

Реализации списков


Списки представлены реализациями ArrayList и LinkedList.

Списки объектов ArrayList и LinkedList поддерживают последовательный порядок индексов при изменении своей структуры.

Производительность списков объектов ArrayList и LinkedList зависит от количества изменений их структуры. Исходя из этого, самыми «дешевыми» являются операции работы с концом списка (добавление / удаление), а самыми «дорогими» — операции работы с началом списка (добавление / удаление). Сложность работы алгоритмов списка объектов равна O(n * (count — index)), где n — операция; count — размер списка; index — индекс, по которому выполняется операция.

Списки объектов ArrayList и LinkedList поддерживают любые типы данных для значений.

Связанный список объектов LinkedList реализует интерфейс двунаправленной очереди объектов Deque и наследует функциональность от ArrayList.

image

Реализации очередей


Конкретные реализации очередей отсутствуют, так как связанный список LinkedList отлично покрывает их функциональность.

Несколько примеров использования


Примеры использования карт:
<?php

namespace Rmk\Collection;

use \UnexpectedValueException as UnexpectedValueException;
use \InvalidArgumentException as InvalidArgumentException;
use \stdClass as stdClass;

include '../../bootstrap.php';

$map = new HashMap('stdClass', 'string');

$obj1 = new stdClass();
$obj2 = new stdClass();
$obj3 = new stdClass();

// Установка ассоциаций ключ / значение.
$map->set('k1', $obj1);
$map->set('k2', $obj2);
$map->set('k3', $obj3);

try {
    $map->set(27, $obj1);
} catch (InvalidArgumentException $exc) {
    echo 'Ключ не подходит по типу.';
}

try {
    $map->set('k4', new UnexpectedValueException);
} catch (InvalidArgumentException $exc) {
    echo 'Значение не подходит по типу.';
}


// Обход карты.
$map->each(function($value, $key, $thisMap) {
            /**
             * @TODO: Обработка карты.
             */
        }
);

// Удаление по значению.
$map->remove($obj1);
$map->remove($obj2);

// Удаление по ключу.
$map->removeKey('k3');

if ($map->isEmpty()) {
    /**
     * @TODO: Что делать, если карта пуста? 
     */
}

// Преобразование в массив.
$array = $map->toArray();

// Внимание! Невозможно преобразовать в массив карту, у которой ключами 
// являются объекты.
$objectMap = new HashMap('stdClass', 'stdClass');

try {
    $objectArray = $objectMap->toArray();
} catch (UnexpectedValueException $exc) {
    echo 'Объекты не могут являться ключами массива.';
}


Примеры использования наборов:
<?php

namespace Rmk\Collection;

use \UnexpectedValueException as UnexpectedValueException;
use \InvalidArgumentException as InvalidArgumentException;
use \stdClass as stdClass;

include '../../bootstrap.php';

$set = new UniqueStore('stdClass');

$obj1 = new stdClass();
$obj2 = new stdClass();
$obj3 = new stdClass();

// Добавление объектов в хранилище.
$set->add($obj1);
$set->add($obj2);
$set->add($obj3);

// Повторно объекты в хранилище добавлены не будут.
$set->add($obj3);

try {
    $set->add(new UnexpectedValueException);
} catch (InvalidArgumentException $exc) {
    echo 'Значение не подходит по типу.';
}

// Обход хранилища.
$set->each(function($value, $thisSet) {
            /**
             * @TODO: Обработка хранилища.
             */
        }
);

// Удаление объектов из хранилища.
$set->remove($obj1);
$set->remove($obj2);
$set->remove($obj3);

// Преобразование в массив.
$array = $set->toArray();


Примеры использования списков:
<?php

namespace Rmk\Collection;

use \UnexpectedValueException as UnexpectedValueException;
use \InvalidArgumentException as InvalidArgumentException;
use \OutOfRangeException as OutOfRangeException;
use \stdClass as stdClass;

include '../../bootstrap.php';

$list = new LinkedList('stdClass');

$obj1 = new stdClass();
$obj2 = new stdClass();
$obj3 = new stdClass();

// Добавление объектов в список.
$list->add(0, $obj1);
$list->add(1, $obj2);
$list->add(2, $obj3);

try {
    $list->add(4, $obj1);
} catch (OutOfRangeException $exc) {
    echo 'Индекс находится за пределами списка дальше, чем на единицу.';
}

// Обход списка.
$list->each(function($value, $index, $thisList) {
            /**
             * @TODO: Обработка списка.
             */
        }
);

// Обход списка в обратном порядке.
$list->reverseEach(function($value, $index, $thisList) {
            /**
             * @TODO: Обработка списка.
             */
        }
);

// Удаление из списка.
$list->remove($obj1);
$list->removeIndex(0);
$list->removeFirst();

if ($list->isEmpty()) {
    echo 'Список пуст.';
}


Преимущества и недостатки


  • +
    • Увереность в типе объектов в коллекции.
    • ОО интерфейс вместо «функций работы с массивами» (основная причина написания данного пакета).
    • Уверенность в последовательности индексов в коллекциях типа SequentialList.

  • -
    • Условно-низкая производительность. То, что можно было выжать из PHP реализации, я старался выжать. Но если бы данный пакет был реализован на С, как PECL модуль, то он бы работал значительно быстрее.


Исходные коды


https://github.com/rmk135/Rmk-Framework
Share post

Comments 85

    +40
    Очередная попытка из букв P, H и P собрать слово Java?

    В этот раз хотя бы грамотная, поэтому +.
      –5
      минусуйте, но мне показалось это чистым костылем превращения php в java
        +4
        я считаю не корректным призывать минусовать/плюсовать.
        если есть что сказать — надо говорить или голосовать молча.
          +20
          Причем тут java или другие языки? Нужен функционал, он делается, независимо от того какой язык. Или если язык называется php, то в него нельзя добавить нужного функционала? Или может если в php добавить коллекцию, то он автоматически станет java? Не нужно говорить глупостей.
            +3
            Если какое-то направление программирование развилось в среде разработчиков, использующих определенный язык, это не значит, что оно должно там и остаться навеки и умереть. Точно также многие MVC-фреймворки (именно веб-MVC-фреймворки) вдохновлены Рельсами, но никто же не говорит, что Yii или ASP.Net — это попытка превращения PHP или C# в Ruby. Это переносится на другие языки потому что это красиво, удобно и в этом есть определенная выгода.
              0
              немного уточню, что ASP.Net имел в виду ASP.Net MVC…
                0
                я с вами бесспорно согласен. мое сообщение относилось конкретно к этой реализации… в данном случае упрощения чего либо я не знаметил
              +5
              На протяжении последних 5 лет я работаю с PHP. У него есть достаточно разных проблем, но это никогда не мешало создавать отлично работающие продукты.

              Может ваш комментарий не уместен? Это занудство, какой PHP плохо, уже достало, ну не нравиться PHP, даже не открывай заметки с тегом PHP, не пиши, не читай, вообще забудь про существование.

              У каждого языка есть свое предназначение, и у PHP есть своя ниша, то что люди его хотят сделать лучше, не есть плохо. Сравнение PHP и Java это как выбор между T-34 и ферари, и присутствием двух задач — уличные гонки и перепахать поле 40га, думаю вы долго не будете думать какой инструмент для какой задачи выбрать!?
                –1
                Получается, поле вы будете перепахивать на Т-34? o_O
                  +2
                  Расскажите эти сказки Цукербергу.
                    0
                    Если у меня есть Т-34 и есть поле, которое надо перепахать прямо сейчас, лучше продать танк, купить трактор и перепахать поле через несколько месяцев?
                    –6
                    Если продолжать вашу аналогию, вы собираетесь поставить на T-34 колеса и двигатель от феррари.

                    В такой конфигурации вы и не погоняетесь со сфетофора, и неприхотливость, универсальность и проходимость оставите за бортом.

                    Переключаясь к предмету разговора — данное решение не только навязывает пользователю инородный для языка синтаксис, но и не дает буста производительности ( который, в яве довольно существеннен при использовании типизированных структур данных)

                    –2
                    Что то не вижу прикрученной JVM :)
                      0
                      Хм… Сделать JVM, портировать все библиотеки Java…

                      Вон, на JS библиотеки аналогичным способом уже портируют, чем PHP хуже.
                      0
                      Каково же было мое удивление, когда в процессе чтения статьи я подумал ровно то, что Вы материализовали в первый коммент.
                      +15
                      Реквестирую в тред jpg-нацистов!
                        +13
                        В java или C#, коллекции были заложены в язык изначально, и основной профит от их использования это то, что некоторые коллекции дают упорядоченность при помещении в них элементов, какие-то оптимальны для случайного доступа, какие-то для последовательного…

                        Для php коллекции не свойственны и принято использовать массивы, по моему мнению приведенный вами основной профит — увереность в типе объектов в коллекции, легко решается использованием массива и проверкой типа вставляемого значения, так что в минусы можно записать как бесполезное усложнение архитектуры.
                          +6
                          Именно количество требуемых проверок и смущало меня при работы с стандартными массивами. Маленькое, но дублирование.

                          Опять же не забывайте о человеческом факторе, можно элементарно забыть поставить проверку.

                          На практике важным моментом оказалась терминология. Больше не нужно говорить «сделай вот как вчера или как в том самом месте...».
                            +4
                            Так вам же и плюсуют в общем-то за грамотность реализации. Код на гитхабе выглядит красиво и осмысленно, мне придраться не к чему.
                            Просто ошибка похоже была в базовом планировании — вовсе не обязательно было делать такую сложную обвязку из кучи файлов для относительно простого функционала.
                            Достаточно было одного простого инклуда с более простой обвязкой вокруг обычного массива, и использованием хешей(тех же дополнительных ассоциативных массивов) для производительности.
                            Ну еще малость убивает использование array_search() внутри. Это медленная функция, ибо даже не смотря на ее нативность и Си-шную реализацию, она ищет тупым перебором, без использования хеширования.

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

                            То есть в данном случае претензии в комментах — это вовсе не минусы. Это просто здравая критика. Воспринимайте правильно )
                            Как-то так.
                              +3
                              Согласен с вами, что сделано грамотно, сразу видно что человек понимает как коллекции работают изнутри
                              +2
                              Я подобное для себя решил проще. Добавить в движок объект который реализует Iterator, Countable и ArrayAccess интерфейсы. Получаем обычный с виду массив (разработчикам удобно), но вот просто так в него левых данных не вставить (забывчивый джуниор не пройдет). Имхо, так рано или поздно приходится делать в любой проекте с долгим периодом жизни.
                              +1
                              Не в язык заложены, а в стандартлиб. Это в Groovy, например, коллекции заложены в язык.
                              0
                              А чем ваша реализация отличается от SplObjectStorage?
                                +1
                                Какая именно реализация?

                                Если речь идет о интерфейсе Set и его реализации UniqueStore, то у SplObjectStorage нет встроенного контроля типов объектов.
                                  +1
                                  Но ведь его можно расширить :)
                                    0
                                    Можно, но картина все равно не станет целостной.

                                    Расширение SplObjectStorage позволит нам реализовать набор объектов, но никак не повлияет на реализацию карт и последовательных списков.
                              +6
                              Было бы удобнее работать с коллекциями нативным способом. Т.е. заменить
                              $list->each(function(){...});
                              

                              на
                              foreach ($list as $k=>$v) {...}
                              


                              Иначе не понятно, как в итерабельную функцию передать внешнюю переменную? Неужели через $GLOBALS?
                                +2
                                use?
                                  0
                                  Забыл про это.
                                  Но всё равно, зачем так сложно?
                                  1. Я не смогу это объяснить своим верстальщикам, которые прикручивают вёрстку и знают php на уровне if/else/foreach.
                                  2. Вопрос в полиморфизме: где-то надо использовать foreach, а где-то $list->each(). Почему бы везде не использовать одну и ту же конструкцию?
                                    0
                                    Я и не говорил, что это лучшая практика.
                                    php.net/manual/en/language.oop5.iterations.php — можно было бы прикрутить реализацию Iterator.
                                      0
                                      Прошу прощения, не прочел комментарий автора ниже, где он говорит, что foreach некорректно работает с ключами-объектами. Просто никогда не экспериментировал :)
                                  0
                                  function() use ($a, $b) {}
                                    0
                                    А где про это можно почитать? Что-то в мануале никак это не отрою…
                                    0
                                    волшебное слово «use»
                                      +3
                                      Если использовать стандартный интерфейс Itertator, то и в foreach можно будет обходить такие объекты.
                                        0
                                        *Iterator
                                          0
                                          Практически каждый писал что-то подобное ведь. А тут все получилось собрано и аккуратно. Уж что что, а написано действительно хорошо. Однако, мне кажется, вы правы в том, что не нужно плодить сущностей:
                                          // Iterable.php
                                          interface Iterable {
                                            // ...
                                          }
                                          

                                          В PHP уже имеются необходимые интерфейсы. И еще свои пять копеек вставить на счет следующего замечания:
                                          $objectMap = new HashMap('stdClass', 'stdClass');
                                          
                                          try {
                                              $objectArray = $objectMap->toArray();
                                          } catch (UnexpectedValueException $exc) {
                                              echo 'Объекты не могут являться ключами массива.';
                                          }
                                          

                                          Добавить сюда «внутреннее» расширение такого плана. Если объект реализует интерфейс, к примеру, IForMapHash — то пожалуйста, пусть будут ключами.
                                          +1
                                          Отличный вопрос.

                                          На первый взгляд для реализации итерации путем использования foreach() достаточно реализовать интерфейс Iterator или его производные (любой наследник Traversable).

                                          foreach ($list as $k=>$v) {...}


                                          Так и планировалось сделать. Но на этапе анализа вылез подводный камень — foreach() неадекватно реагирует на ключ в виде объекта.

                                          Исходя из этого я сделал единый вариант для всех коллекций, оставляя возможность реализовывать интерфейсы итераторов на более низких уровнях.
                                            +3
                                            Я даже не представляю, кому и зачем может потребоваться использовать объекты в качестве ключей. Быть может, имеет смысл оставить обе возможности и в 99% случаев использовать нативный foreach?
                                              0
                                              Ну вполне просто. Допустим, у нас есть карта 100*100, на ней 200 юнитов.
                                              $map->setUnit( $unit, new Point(43, 12) );
                                              


                                              Нам надо сделать очень быстрое получение координат юнита:
                                              $map->getUnitCoord( $unit );
                                              


                                              Можно, конечно, реализовать как-то так:
                                              function setUnit (Unit $unit, Point $coord) {
                                                $this->units[$unit->id] = $coord;
                                              }
                                              


                                              А можно сделать так:
                                              function setUnit (Unit $unit, Point $coord) {
                                                $this->units->set( $unit, $coord );
                                              }
                                              


                                              Пример, конечно, притянуто за уши, но идея может иметь смысл.

                                                0
                                                Именно из этого и расчет.

                                                Данная ситуация может осложниться, если Unit не имеет уникально идентифицирующего его строкового или числового свойства.

                                                function setUnit (Unit $unit, Point $coord) {
                                                  $this->units[$unit->id] = $coord;
                                                }
                                                  0
                                                  А если юнит перемещается по карте, то его придётся постоянно в перемещать в коллекции? Т.е. перестраивать массив.

                                                  По моему, это не правильно. Должна быть коллекция юнитов и у каждого юнита должно быть свойство $coords. Если надо найти всех юнитов в клетке, то придётся пробежаться по всей коллекции точно так же как SQL БД пробегаются по таблицам.

                                                  Кстати, поиск юнитов в конкретной координате, наверное, не самая частая задача. Подозреваю, что более распространённая задача это поиск всех юнитов в регионе (от 23х23 до 48х56). Ключи не решат эту проблему и придётся пробегать по всей коллекции.

                                                  Мне кажется, что вы путаете ключи массива с индексами из БД. Если Вам надо по какому-то полю быстро находить записи, значит Вам стоит создать отдельный индекс, что бы быстро его перебирать. Вы можете сделать его в формате b-tree или ещё как-то оптимизировать под свои нужны. Но не надо использовать для этого ключи массива так как круг решаемых задач очень не велик, зато коллекцию придётся постоянно перестраивать во время перемещения юнитов.
                                                    0
                                                    А если юнит перемещается по карте, то его придётся постоянно в перемещать в коллекции? Т.е. перестраивать массив.

                                                    Зачем?
                                                    $collection->rm($unit);
                                                    $collection->set($unit, new Point(2,3));
                                                    

                                                    По моему, это не правильно. Должна быть коллекция юнитов и у каждого юнита должно быть свойство $coords. Если надо найти всех юнитов в клетке, то придётся пробежаться по всей коллекции точно так же как SQL БД пробегаются по таблицам.

                                                    Я обычно так и делаю, но это нарушает принцип единственной отвественности. Не юнит должен контролировать своё положение.
                                                      0
                                                      $collection->rm($unit);
                                                      $collection->set($unit, new Point(2,3));
                                                      

                                                      Это и есть перестроение коллекции: сначала удаляете элемент, а потом создаёте новый. По моему, это сложнее, чем:
                                                      $collection->move($unitKey, new Point(2,3));
                                                      // или
                                                      $unit->move(new Point(2,3));
                                                      


                                                        +1
                                                        Ну, ок. Там может быть так:

                                                        $collection->get($unit)->moveTo(4, 1)
                                                        


                                                        У точки ведь может быть метод «moveTo»
                                                    0
                                                    Действительно притянуто, потому что в качестве ключа логичнее использовать не весь объект, а его ID.
                                                  0
                                                  Но на этапе анализа вылез подводный камень — foreach() неадекватно реагирует на ключ в виде объекта.

                                                  Что происходит? Ошибка вылетает?
                                                    0
                                                    E_WARNING, который невозможно подавить.
                                                      0
                                                      Если реализация Iterator::key() возвращает объект, то вылетает такой warning:
                                                      PHP Warning: Illegal type returned from A::key() in /home/sam/sims/q.php on line 44
                                                      PHP Stack trace:
                                                      PHP 1. {main}() /home/sam/sims/q.php:0
                                                  0
                                                  Отсутствие возможности построить ассоциативный массив, где ключами будут объекты.
                                                  странное желание. Мне всегда казалось что у массива/«хеш массива» должна быть одна функция: быстрое извлечение данных по ключу.
                                                  Всякие накрутки типа: ключами могут быть объекты — это удар ниже живота по производительности.

                                                  Если есть такое, ну очень большое желание, то за 5 мин можно написать простую обертку, которая сериализует объект(часть объекта) и использует результат в качестве ключа.
                                                    +1
                                                    Сериализация объекта тоже не самая быстрая операция. Вместо нее использую spl_object_hash().

                                                    Как показала практика — быстрое извлечение по ключу не говорит о том, что ключ не должен быть объектом.
                                                      0
                                                      да, я в курсе, я упомянул про суть, так сказать идею, а конкретное воплощение может быть любым
                                                      и все же спасибо за комментарий.
                                                      0
                                                      Зачем использовать в качестве ключа сериализованный объект, если можно использовать его уникальный идентификатор (id)?
                                                      +3
                                                      Зачем так

                                                      use \UnexpectedValueException as UnexpectedValueException;

                                                      если можно просто так

                                                      use UnexpectedValueException;
                                                        –3
                                                        Потому что выше используется пространство имен Rmk\Collection, и use UnexpectedValueException будет искать этот класс в этом пространстве имен. use \UnexpectedValueException; сработает.
                                                        +1
                                                        Я думаю, что

                                                        use UnexpectedValueException
                                                        

                                                        можно вообще не использовать и писать в коде сразу

                                                        try {
                                                            $objectArray = $objectMap->toArray();
                                                        } catch (\UnexpectedValueException $exc) {
                                                            echo 'Объекты не могут являться ключами массива.';
                                                        }
                                                        

                                                        И так далее, для всех стандартных исключений из глобального namespace.
                                                        0
                                                        Самостоятельный контроль типов объектов в массиве.

                                                        а не лучше ли использовать для этой цели собственный враппер, тоже пишется за 5 мин :).
                                                        Мне, например, часто требуется использовать массив, как универсальный стораж, в котором могут буть разные типы. Не забываем, что РНР идеалогически разрабатывался как язык с НЕ СТРОГОЙ типизацией!
                                                        Да, в этом есть плюсы и минусы. Не нравится не срогая типизация — перелезайте на рельсы!
                                                          0
                                                          Самостоятельный контроль последовательности индексов в списке.

                                                          К моим двум предыдущим комментариям могу только добавить, что массив реализован как hashtable, а. hashtable изначально не предполагает контроль последовательности индексов: Это прямой доступ по индексу, смещение для доступа к данным вычисляется по результату хешфункции. Есть ключ, есть карзина с данными, есть смещение, указывающее на расположение данных в карзине, которое вычисляется на основе ключа.

                                                          Возможно разработчикам РНР и нужно было сделать две отдельных сущности: массивы и хештайблы? Но, разработчики пошли по пути универсализации. Все довольны, все в шоколаде!
                                                            0
                                                            в целом проект грамотный и интересный
                                                              +5
                                                              Дал плюс за креативность и грамотную реализацию, но тем не менее пользу вижу только в возможности работы по объекту как по ключу. Однако учитывая реализацию строго на PHP, эта польза нивелируется — можно было ограничиться реализацией лишь пары вспомогательных функций к стандартному массиву.
                                                              Итого(уж извините за критику, но взгляд со стороны часто находит простое в сложном): вы сделали, как справедливо заметил выше hom, бесполезное усложнение архитектуры.
                                                              Обычно, если нужно работать с объектами столь гибко, делается так: пишется класс обвязки к стандартному массиву, оперирующий самогенерирующимися ключами, строковыми или числовыми, и внутренним объектом, который является обычным массивом.
                                                              То есть когда вы потом напишете например $col->remove($obj), у вас $obj будет(и должен быть) не самим объектом, а его ключем в коллекции $col. Фактически это разновидность псевдопоинтера.
                                                              Соответственно работать с такими объектами вы будете не как $obj->method(), а как $col->get($obj)->method() или подобным образом. Компилятор сам вам подскажет, если вы забудетесь и попытаетесь вызвать $obj->method().
                                                              Контролировать типы объектов можно либо самому, либо включая в каждый объект метод либо свойство, отвечающее за тип. Таким образом при желании вообще можно в коллекцию помещать как ООП-объекты, так и обычные структуры, и работать с ними, не путая типы. Кстати работать можно будет как полностью с процедурным подходом, так и с объектным.

                                                              Плюс такого подхода в том, что аналоги перлового хеша в стандартный PHP-массив уже встроены — это ассоциативный массив. А следовательно, поиск значений по простому ключу будет гораздо быстрее любой другой реализации, написанной на самом PHP, иными словами: быстрее любого нативного перебора. То есть суть класса обвязки сводится лишь к группировке функций обслуживания такой «коллекции»(большинство из которых в языке уже есть нативно, а соответственно, работают быстрее) и скрытие внутри вспомогательных массивов-хешей.
                                                                +6
                                                                >>Исторически сложившиеся имена функций и отсутствие ОО интерфейса
                                                                Это бесит только первые пять лет :)

                                                                А если серьезно, то когда встречаешь что-то подобное в проекте который нужно доделать — сталкиваешься с необходимостью потратить время, а иногда значительное, на то чтобы разобраться. В результате весь профит пропадает. Все-таки я склоняюсь к использованию базового функционала массивов, если они покрывают весь необходимый функционал.
                                                                  +8
                                                                  Отсутствие возможности построить ассоциативный массив, где ключами будут объекты.

                                                                  Use SplObjectStorage, Luke.
                                                                    +3
                                                                    и еще капля дегтя…
                                                                    Очереди и списки интересны, если они производительны. Может я однобоко гляжу в сторону разработки, просто я последние пять лет сильно упираюсь в производительность.
                                                                    При необходимости использования этих структур, я бы реализовал их в в виде РНР-расширения. Кажется я даже встречал такую реализацию. Можно покапаться в просторах инета…
                                                                    У меня самого пару лет назад была такая идея, но не нашлось проекта для применения. И я бы туда еще добавил деревья. Как смотришь на это?

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

                                                                      Другими словами, буду этим заниматься, но быстрых результатов дать не получится.

                                                                      Если бы кто-то реализовал эти интерфейсы в качестве PECL модулей, я уверен, что работали бы они гораздо быстрее и доверия к ним было бы больше. Собственно, писал об этом в конце статьи.
                                                                        0
                                                                        1) надо поискать на предмет реализации, кажется я это где-то уже встречал
                                                                        2) деревья не на много сложнее списков, можно найти уже готовые библиотеки, над которые просто написать РНР-враппер. Если такие найдешь, стукнись в приват.
                                                                          0
                                                                          вот одна из них: judy.sourceforge.net/
                                                                            +1
                                                                            Хорошая штука когда должно работать быстро и кушать мало памяти и мгновенно печалит, когда такой массив невозможно ни куда сериализовать.
                                                                              0
                                                                              на мой взгляд
                                                                              1) сериализацию можно реализовать, это совсем не трудно, но надо понимать, что всякая сериализация/десерриализация — это время… как минимум O(n)
                                                                              2) если хранилище сделать персистентным, то смысла в сериализации я не вижу, так как в основном сериализация используется, как передача данных между циклами обработки как правило одного приложения. Как раз тема персистентного хранилища. Если приложений несколько, и на разных языках (не удивительно и такое встречается), то использование данного вида сериализации вообще не имеет смысла, так как эта хреновина будет либо PHP-only. Либо если делать в виде отдельного демона — то это уже велосипед аналогов Тарантула или Токио. А оно надо? Лучше все равно не сделаешь. Хотя у меня были попытки www.slideshare.net/akalend/treedb-keyvalue-nosql-database
                                                                              и даже не плохие: см. слайд 17 (производительность). Но уперся в ряд прочих проблем…

                                                                              так что из всего вышеизложенного словоблудия я вижу следующий смысл:
                                                                              — если реализовывать, то только как персистентное хранилище shmap
                                                                              — реализовать как класс (несколько классов: map, hash, queue, list,? )
                                                                              — Implements Irerator или ArrayIterator, чтоб можно было использовать в конструкции foreach
                                                                              — Serialize/Unserialize ????? или hash2array/array2hash, map2array/array2map etc…

                                                                                0
                                                                                Я на досуге проанализирую все комментарии и составлю план будущего рефакторинга.
                                                                                  0
                                                                                  Легкая реализация judy массивов? У каждого конечно свой опыт, но мне оно видится не таким простым. В PHP сериализаторах judy ни в одном нет.

                                                                                  Я себе запиливал key-value хранилище на shm (по принципу slab-ов) в ipc на PHP и оно пахало раза в 4 быстрее, чем mamcached по unix socket. На сях как расширение нужно думать получилось бы выжать больше. В итоге все забросилось в довольно черновом варианте. На этой стадии вылезла только одна проблема. При записи лочить весь сегмент совершенно не хотелось, хочется лочить только конкретный key и связанные с ним куски памяти. Но если использовать семафоры, то их просто не хватает под мою задачу. А лок сервер который мог бы жить в однопоточном режиме внутри php-fpm мастер процессе я найти не смог.
                                                                                    0
                                                                                    да с локами еще тот геморой, я бы лочил весь слаб. А слабы делал небольшими, элементов на 64 -128 не больше. А в принципе ты прав — от задачи зависит.
                                                                                      +1
                                                                                      Не всегда можно юзат слабы небольшого размера. Проблему лока решал через финт ушами. Что нужно для быстро лока? Имхо — атомарность, гарантирующая консистентность данных (актуально когда у нас размер данных хранящихся по key изменился и его нужно перетаскивать в другой slab) и отсутствие блокирования в коде приложение на вызове. Единственный адекватный вариант к которому удалось прийти, это shmop_open с флагом n (получение лока — создание сегмента, снятие лока — удаление сегмента). Если создаваемый сегмент уже есть, то управление в PHP код возвращается тут же. И можно либо сгенерировать ошибку, либо немного обождать и повторить попытку залочится. После n попыток генерим ошибку и завершаемся. А когда лочимся, то в созданный сегмент пишу unix timestamp. Нужно для автоматического снятия лока (сравнивая текущее время с записанным в сегменте) если процесс его создавший сдох и не удалил сегмент.

                                                                                      Минус тут в жесткой схеме. С каждым значимым куском памяти в shm я ассоциирую определенный shm_id и при локе использую именно его. А это int, т.е. диапазон не очень на самом деле большой. Другой минус — приложение по сути эксплуатирует shm единолично, но в системе ipc нужен и другим сервисам, postgresql к примеру. Но более адекватной схемы из самого PHP найти не удалось. Поэтому как ни крути нужно выходит на уровень си и лок сервер в мастер процессе.

                                                                                      А какие проблемы получились по материалам изложенным в презентации?
                                                                                        0
                                                                                        конкретно по сторажу проблем не было, так как использоват реализацию tree из Tokyo
                                                                                        если не считать проверки на балансировку дерева по истечения интервала или выполнение DELETE > 10%

                                                                                        заморочки получились в правильной обязке libevent работающей на n потоков. в общем до ума на 100% не довел, а познакомился с Тарантулом, понял что он именно то что мне нужно и эффективнее моего сторожа и забил. В итоге приобрел хороший опыт.

                                                                                        Что касается блокировок, то в случае INSERT/DELETE просто делал блокировку через mutex.
                                                                                        на очень больших нагрузках не тестировал, но синтетические тесты показали не плохую производительность. Возможно и стоило довести до ума…

                                                                                          0
                                                                                          может руки дойдут
                                                                          0
                                                                          Ваши коллекции — вещи сами по себе. Создать, заполнить последовательно по одному ключику, вынуть по ключику. И всё.

                                                                          Как предлагается обрабатывать коллекции как единое целое: сортировать, преобразовывать, конвертировать, фильтровать? Стандартные функции массивов принимать ваши коллкции не будут. Конвертировать туда-сюда? Не выйдет из-за объектов-ключей, да и окончательно убьёт производительность. Или предлагаете ради каждой новой функции лезть в сорцы коллекции и реализовывать всё вручную? Так теряется весь профит от использования «библиотеки»: сам дописывай, сам тестируй, сам исправляй баги.

                                                                          По вашим коллекциям даже нельзя пройтись с помощью foreach — нужно использовать изобретённые вами же методы. Отдать в какой-то код «снаружи» коллекции нельзя, они из станданртных интерфейсов только Countable поддерживают (коллекция, у которой можно вычислить размер, но нельзя получить элементы — лолшто?).

                                                                          Похапэ и так производительностью не отличается, так вы ещё реализуете немало методов с помощью полного прохода, весь код array_search утыкан.

                                                                          После просмотра функции getHash сразу возникают вопросы.

                                                                              public function getHash($value)
                                                                              {
                                                                                  if (is_object($value)) {
                                                                                      return spl_object_hash($value);
                                                                                  }
                                                                          
                                                                                  if (is_string($value)) {
                                                                                      return md5($value);
                                                                                  }
                                                                          
                                                                                  if (is_array($value)) {
                                                                                      $value = serialize($value);
                                                                                  }
                                                                          
                                                                                  return md5($value);
                                                                              }
                                                                          

                                                                          Почему для объектов всегда используется spl_object_hash? А если у меня два объекта считаются равными, если их id совпадает?

                                                                          Зачем для строк вычислять md5? Похапэшные массивы — и так хэштаблицы, не нужно вычислять хэши на двух уровнях. Только память и вычислительные ресурсы впустую расходуются.

                                                                          Вызов serialize забавен. Понимаю, «нужно» все типы поддерживать, но не такой же ценой.

                                                                          Насчёт осмысленности вызова md5 при целочисленных ключах вообще без комментариев. Это всё ради сохранения ручного порядка элементов что ли?

                                                                          В целом: любопытная библиотека, написана весьма недурно, но совершенно бесполезна в реальной разработке.

                                                                          Ах да, и тестов нет. Для такого рода библиотек это критично.
                                                                            0
                                                                            >> Почему для объектов всегда используется spl_object_hash? А если у меня два объекта считаются равными, если их id совпадает?

                                                                            — Это прикладная логика. Наследуйте карту и реализуйте getHash().

                                                                            >> Зачем для строк вычислять md5? Похапэшные массивы — и так хэштаблицы, не нужно вычислять хэши на двух уровнях. Только память и вычислительные ресурсы впустую расходуются.

                                                                            — Действительно как-то не подумал. Есть только небольшой вопрос: что будет, если не преобразовывать в md5 достаточно длинную строку? Например, в несколько десятков килобайт? Если это не страшно, то действительно есть смысл переделать.

                                                                            >> Вызов serialize забавен. Понимаю, «нужно» все типы поддерживать, но не такой же ценой.

                                                                            — В точку, «нужно». Более элегантного способа не нашел. Предложите что-нибудь?

                                                                            >> Ах да, и тестов нет. Для такого рода библиотек это критично.
                                                                            — Тесты действительно нужно разработать. Займусь этим.

                                                                            Теперь о вопросах работы с фильтрацией, сортировкой и т. д.
                                                                            Я считаю, что отсутствие данных функций есть моя вина. Я должен был их включить в пакет, но не включил на текущий момент. Думаю, что их наличие существенно увеличит полезность и привлекательность пакета. Я займусь этим в будущем. На текущий момент был собран минимальный функционал, который позволяет работать.
                                                                              0
                                                                              Наследуйте карту и реализуйте getHash()

                                                                              По коллекции на каждый тип объекта? Шутить изволите? :) Почему в конструктор не передать ссылку на функцию или ещё как?

                                                                              Есть только небольшой вопрос: что будет, если не преобразовывать в md5 достаточно длинную строку? Например, в несколько десятков килобайт? Если это не страшно, то действительно есть смысл переделать.

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

                                                                              Более элегантного способа не нашел. Предложите что-нибудь?

                                                                              Если для поддержки массивов это единственный способ, то лучше их не поддерживать вообще. Тот, кто пользуется библиотекой, должен выбирать подходящие инструменты для задачи, и ваша коллекция в таких условиях — очевидно, не вариант. Ну или сделать для массивов передачу своей функции получения хэша обязательной — чтобы человек осознанно шёл на кривое решение с serialize.
                                                                                0
                                                                                >> По коллекции на каждый тип объекта? Шутить изволите? :) Почему в конструктор не передать ссылку на функцию или ещё как?

                                                                                — Шутить, нет :) Я бы, пожалуй, все таки делал специализации, само собой, в умеренных количествах.

                                                                                На счет предложения с конструктором — тоже неплохая идея. На сколько я понимаю, то в общем виде предложение выглядит как:
                                                                                «Сделать IoC для функции getHash()»?
                                                                                  0
                                                                                  На счет строк — нужно будет попробовать. Я тоже надеюсь…
                                                                                    0
                                                                                    Извините за оффтоп, но подскажите, пожалуйста, в какие тэги брать текст комментариев?
                                                                                      0
                                                                                      О-о-о… Комментарии на хабре под весьма изощрённым тегом: <blockquote>цитата</blockquote>. Почему нет кнопки для цитирования — одному НЛО известно.

                                                                                      Да, при написании комментария есть ссылка «html-теги» — там всё перечислено.
                                                                                        0
                                                                                        +1 За такую кнопку!

                                                                              Only users with full accounts can post comments. Log in, please.