Чего уж скрывать, мне нравятся объекты, и не нравятся ассоциативные массивы. И когда выбираю из базы некий набор данных, хочется получать набор объектов а не массив массивов. Причем не просто набор объектов, а нужный мне набор и именно так как я этого хочу. Раньше происходила выборка из базы в 3 этапа:
1. получить массив данных из бд
2. пройтись по результату
3. на каждой итерации создать объект и сунуть в другой массив
Ну и собственно вернуть данные наружу. Это не то чтобы напрягало сильно, но чувствовал что должен быть способ проще и удобней. И я его нашел — Коллекции.
Очень нравятся коллекции. Всегда хотелось иметь массив однотипных данных, в частности объектов. Особенно в связке с бд.
Самая банальная ситуация — хочу получить массив объектов User из бд:
Метод Db_users::getAll()
1. считываем данные из бд
2. проходимся по массиву результатов и загоняем каждую строку в объект User
3. сохраняем полученный объект User в другой массив который отдаем наружу
Как-то так это происходит:
Вот такой простенький и стандартный метод у нас получился, который берет всех пользователей из базы и возвращает массив сущностей.
И как-то так это выводится в шаблоне:
И так постоянно. Все вроде хорошо и работает, но что-то не так. Первое что бросается в глаза — два цикла. Первый при создании массива сущностей, и второй при выводе. Два цикла по (грубо говоря) одному и тому же массиву — зачем? Первая мысль — PDO!
Да у него ведь есть волшебный метод fetchObj. Он многим подойдет, но мне не нравится одна его особенность:
(Добрые люди подсказывают о PDO::FETCH_PROPS_LATE, это решает проблему установки свойств до вызова конструктора.)
Если вам это не принципиально, то на этом можно остановиться, меня же не устраивало что конструктор вызывается после установки свойств (у меня в конструкторе создавался фильтр отдельным классом, не хочется вставлять костыли с проверками на пустые переменные), да и все поля устанавливались как свойства, так что едем дальше.
Есть великолепное встроенное решение — ArrayIterator. Хороший такой себе объект, с геттерами и сеттерами как у массива и возможностью перебора в foreach. Как раз то что надо.
Немножечко меняем метод getAll:
И шаблон:
Уже лучше, но какая же это коллекция? Мы просто банально скопированный массив. Так не очень интересно. Хочется получать объект, причем полноценный.
Смотрим на методы ArrayIterator::offsetGet (доступ по ключу как в массиве) и ArrayIterator::current (доступ при переборе в foreach и получение элемента функцией current) этои методы возвращают значение по ключу и текущий элемент по внутреннему указателю соответственно, то что нужно, переопределяем:
В итоге шаблон стал таким как и хотелось:
Отлично, вроде бы все ок, избавились от лишних циклов, заменили массив объектом
Как последний штрих, можно добавлять возвращаемый класс динамически.
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; ?>
Отлично, вроде бы все ок, избавились от лишних циклов, заменили массив объектом
Как последний штрих, можно добавлять возвращаемый класс динамически.