Pull to refresh

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

Reading time 7 min
Views 29K
На протяжении последних 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
Tags:
Hubs:
+95
Comments 85
Comments Comments 85

Articles