Управление коллекциями объектов в PHP

Раздел: Объектно-ориентированное программирование в PHP -> Объекты и классы

Массив классов в PHP означает хранение экземпляров (объектов) классов в массиве. Это позволяет обрабатывать множество однотипных или разнотипных объектов единообразно. Рассмотрим несколько подходов с примерами.

Основное решение: типизированный массив объектов одного класса

Как создать массив, содержащий объекты одного класса, и обеспечить типобезопасность?

Самый эффективный способ – использовать PHP 8+ с указанием типа элементов через PHPDoc @var array<ClassName> или в аргументах функций с проверкой. Классический подход – хранить объекты в обычном массиве, но при добавлении проверять тип.


class User {
    public function __construct(
        public string $name,
        public int $age
    ) {}
}

$users = [];
$users[] = new User('Анна', 25);
$users[] = new User('Иван', 30);

foreach ($users as $user) {
    echo $user->name . ' - ' . $user->age . ' лет' . PHP_EOL;
}

массив классов php (массив классов в php)

Анна - 25 лет
Иван - 30 лет

Пояснение: создаётся пустой массив, в который добавляются объекты класса User. При итерации каждый элемент гарантированно является объектом User (если не примешивать другие типы). При попытке добавить строку возникнет ошибка только во время выполнения.

Проблема: объекты хранятся по ссылке, поэтому изменение объекта через переменную изменяет и элемент массива. Для копирования используйте clone. Другая ошибка – попытка обратиться к несуществующему свойству или методу, если массив содержит объекты разных классов.

Решение: при неоднородном массиве проверяйте класс через instanceof.

Вариант 1: Ассоциативный массив объектов

Как организовать быстрый доступ к объектам по уникальному ключу (например, ID)?


class Product {
    public function __construct(
        public int $id,
        public string $title,
        public float $price
    ) {}
}

$products = [];
$products[101] = new Product(101, 'Книга', 500);
$products[102] = new Product(102, 'Ручка', 30);

// Получение объекта по ключу
echo $products[101]->title; // Книга

Цель: использование идентификатора в качестве ключа для прямого доступа (без цикла). Удобно для поиска и обновления.

Проблема: дублирование ключа приведёт к перезаписи объекта. Решение: проверять существование ключа через isset() или array_key_exists().

Вариант 2: Массив объектов разных классов (полиморфизм)

Как хранить объекты, реализующие общий интерфейс или наследующие общий класс?


interface Animal {
    public function sound(): string;
}

class Dog implements Animal {
    public function sound(): string {
        return 'Гав';
    }
}

class Cat implements Animal {
    public function sound(): string {
        return 'Мяу';
    }
}

$animals = [new Dog(), new Cat()];

foreach ($animals as $animal) {
    echo $animal->sound() . PHP_EOL;
}
Гав
Мяу

Цель: единообразная обработка объектов разных классов через общий интерфейс. Тип элементов можно задать как array<Animal>.

Проблема: если добавить объект, не реализующий интерфейс, возникнет ошибка при вызове метода. Решение: использовать проверку instanceof Animal перед добавлением или типовые подсказки в IDE.

Вариант 3: Использование SplObjectStorage

Как хранить объекты с гарантией уникальности (без дубликатов) и возможностью привязки данных?


$storage = new SplObjectStorage();
$user1 = new User('Анна', 25);
$user2 = new User('Иван', 30);

$storage->attach($user1, 'данные1');
$storage->attach($user2, 'данные2');
// Попытка добавить тот же объект игнорируется
$storage->attach($user1);

echo $storage->count(); // 2

// Доступ к данным
$storage->rewind();
while ($storage->valid()) {
    $obj = $storage->current();
    $data = $storage->getInfo();
    echo $obj->name . ': ' . $data . PHP_EOL;
    $storage->next();
}

Цель: когда требуется коллекция объектов, где каждый объект может встречаться только один раз, и нужно сопоставить объекту произвольные данные. SplObjectStorage эффективен для управления ссылками.

Проблема: объекты не сериализуются по умолчанию, возможны утечки памяти при длительном хранении. Решение: очищать storage или использовать WeakMap.

Вариант 4: Массив строк с именами классов (динамическое создание)

Как генерировать объекты из заранее определённого списка классов?


$classNames = ['User', 'Admin', 'Guest'];
$objects = [];

foreach ($classNames as $className) {
    if (class_exists($className)) {
        $objects[] = new $className();
    }
}

Цель: гибкость для систем с подключаемыми модулями, когда классы неизвестны заранее. Требует осторожной проверки существования класса.

Проблема: если класс не существует, возникает фатальная ошибка. Решение: использовать class_exists() и try/catch. Также возможны проблемы с пространством имён.

Расширенные примеры работы с массивами объектов

Далее приведены продвинутые сценарии: фильтрация, сортировка, трансформация, итерация с генераторами.

Фильтрация массива объектов с array_filter

Пример

class User {
    public function __construct(
        public string $name,
        public int $age,
        public string $role
    ) {}
}

$users = [
    new User('Анна', 25, 'admin'),
    new User('Иван', 30, 'user'),
    new User('Ольга', 20, 'moderator'),
];

$admins = array_filter($users, fn($u) => $u->role === 'admin');

foreach ($admins as $admin) {
    echo $admin->name . PHP_EOL;
}
Анна

Пояснение: array_filter оставляет только элементы, для которых callback возвращает true. Ключи сохраняются – можно использовать array_values для переиндексации.

Сортировка объектов с usort

Пример

usort($users, fn($a, $b) => $a->age <=> $b->age);

// Результат
foreach ($users as $u) {
    echo $u->name . ' (' . $u->age . ')' . PHP_EOL;
}
Ольга (20)
Анна (25)
Иван (30)

Пояснение: usort изменяет оригинальный массив. Для неизменяемости сначала клонируйте объекты или создайте копию массива.

Трансформация объектов с array_map

Пример

$names = array_map(fn($u) => $u->name, $users);
print_r($names);
Array
(
    [0] => Ольга
    [1] => Анна
    [2] => Иван
)

Пояснение: array_map возвращает новый массив с результатами callback. Не изменяет исходный.

Использование генератора для ленивой загрузки

Пример

function getUsersFromDb(): Generator {
    $ids = [1, 2, 3];
    foreach ($ids as $id) {
        // Имитация загрузки объекта
        yield new User("User{$id}", 20 + $id, 'user');
    }
}

foreach (getUsersFromDb() as $user) {
    echo $user->name . PHP_EOL;
}
User1
User2
User3

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

Коллекция с типизацией через ArrayObject

Пример

class UserCollection extends ArrayObject {
    public function offsetSet(mixed $key, mixed $value): void {
        if (!$value instanceof User) {
            throw new InvalidArgumentException('Only User objects allowed');
        }
        parent::offsetSet($key, $value);
    }
}

$collection = new UserCollection();
$collection[] = new User('Анна', 25);
// $collection[] = 'string'; // вызовет исключение

Создание собственного класса-коллекции гарантирует тип элементов во время выполнения. ArrayObject даёт доступ как к массиву и поддерживает итерацию.

Обработка смешанных типов и безопасное обращение

Пример

$mixed = [new User('Анна', 25), 'строки', null];

foreach ($mixed as $item) {
    if ($item instanceof User) {
        echo $item->name . PHP_EOL;
    } else {
        echo 'Не пользователь' . PHP_EOL;
    }
}
Анна
Не пользователь
Не пользователь

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

Массив классов в PHP - comments

En
массив классов php (php)