Pull to refresh

Массивы или Объекты? Хочу коллекции в пхп!

Reading time4 min
Views21K
Чего уж скрывать, мне нравятся объекты, и не нравятся ассоциативные массивы. И когда выбираю из базы некий набор данных, хочется получать набор объектов а не массив массивов. Причем не просто набор объектов, а нужный мне набор и именно так как я этого хочу. Раньше происходила выборка из базы в 3 этапа:
1. получить массив данных из бд
2. пройтись по результату
3. на каждой итерации создать объект и сунуть в другой массив
Ну и собственно вернуть данные наружу. Это не то чтобы напрягало сильно, но чувствовал что должен быть способ проще и удобней. И я его нашел — Коллекции.

Очень нравятся коллекции. Всегда хотелось иметь массив однотипных данных, в частности объектов. Особенно в связке с бд.
Самая банальная ситуация — хочу получить массив объектов User из бд:
Метод Db_users::getAll()
1. считываем данные из бд
2. проходимся по массиву результатов и загоняем каждую строку в объект User
3. сохраняем полученный объект User в другой массив который отдаем наружу
Как-то так это происходит:
static public function getAll()
{
    $result = array();
    $data = $this->db->fetchAll();
    foreach( $data as $row )
    {
        $user = new User( $row );
        $result[] = $user;
    }
    return $result;
}

Вот такой простенький и стандартный метод у нас получился, который берет всех пользователей из базы и возвращает массив сущностей.
И как-то так это выводится в шаблоне:
<html>
    <head>
        <title>Users list</title>
    </head>
    <body>
        <table>
            <th>
                <td>User ID</td>
                <td>User login</td>
                <td>User email</td>
                <td>User name</td>
            </th>
            <?php foreach( $users as $user ): ?>
            <tr>
                <td><?php echo $user->id; ?></td>
                <td><?php echo $user->login; ?></td>
                <td><?php echo $user->email; ?></td>
                <td><?php echo $user->name; ?></td>
            </tr>
            <?php endforeach; ?>
        </table>
    </body>
</html>

И так постоянно. Все вроде хорошо и работает, но что-то не так. Первое что бросается в глаза — два цикла. Первый при создании массива сущностей, и второй при выводе. Два цикла по (грубо говоря) одному и тому же массиву — зачем? Первая мысль — PDO!
Да у него ведь есть волшебный метод fetchObj. Он многим подойдет, но мне не нравится одна его особенность:
class A
{
    public function __construct()
    {
        echo "<br>construct";
    }
   
    public function __set($key, $val)
    {
        echo "<br>setter";
    }
}

$pdo = new PDO('mysql:dbname=home;host=127.0.0.1', 'root');
$sql = 'SELECT id FROM `table`';
$res = $pdo->query($sql);
var_dump( $res->fetchObject('A') );

Вывод будет:

setter
setter
constructobject(A)#3 (0) {
}

(Добрые люди подсказывают о PDO::FETCH_PROPS_LATE, это решает проблему установки свойств до вызова конструктора.)
Если вам это не принципиально, то на этом можно остановиться, меня же не устраивало что конструктор вызывается после установки свойств (у меня в конструкторе создавался фильтр отдельным классом, не хочется вставлять костыли с проверками на пустые переменные), да и все поля устанавливались как свойства, так что едем дальше.
Есть великолепное встроенное решение — ArrayIterator. Хороший такой себе объект, с геттерами и сеттерами как у массива и возможностью перебора в foreach. Как раз то что надо.
class Collection extends ArrayIterator
{
}

Немножечко меняем метод getAll:
static public function getAll()
{
    $result = array();
    $data = $this->db->fetchAll();
    $result = new Collection( $data );
    return $result;
}

И шаблон:
<?php foreach( $users as $user ): ?>
    <tr>   
        <td><?php echo $user['id']; ?></td>
        <td><?php echo $user['login']; ?></td>
        <td><?php echo $user['email']; ?></td>
        <td><?php echo $user['name']; ?></td>
    </tr>
<?php endforeach; ?>

Уже лучше, но какая же это коллекция? Мы просто банально скопированный массив. Так не очень интересно. Хочется получать объект, причем полноценный.
Смотрим на методы ArrayIterator::offsetGet (доступ по ключу как в массиве) и ArrayIterator::current (доступ при переборе в foreach и получение элемента функцией current) этои методы возвращают значение по ключу и текущий элемент по внутреннему указателю соответственно, то что нужно, переопределяем:
public function offsetGet( $index )
{
    if( empty( $this->_cache[$index] ) )
    {
        // по просьбам трудящихся
        $this->_cache[$index] = new User( parent::offsetGet[$index] );
    }
    return $this->_cache[$index];
}

public function current(  )
{
    $index = parent::key();
    if( empty( $this->_cache[$index] ) )
    {
        // по просьбам трудящихся
        $this->_cache[$index] = new User( parent::current() );
    }
    return $this->_cache[$index];
}

В итоге шаблон стал таким как и хотелось:
<?php foreach( $users as $user ): ?>
    <tr>   
        <td><?php echo $user->id; ?></td>
        <td><?php echo $user->login; ?></td>
        <td><?php echo $user->email; ?></td>
        <td><?php echo $user->name; ?></td>
    </tr>
<?php endforeach; ?>

Отлично, вроде бы все ок, избавились от лишних циклов, заменили массив объектом
Как последний штрих, можно добавлять возвращаемый класс динамически.
Tags:
Hubs:
Total votes 33: ↑16 and ↓17-1
Comments87

Articles