Мы часто сталкиваемся с соблазном быстрого использования структуры данных, подобной записи, предоставляемой языком или структурой. Есть много примеров сценариев, где появляется запись:
- Когда вы используете базу данных для сохранения (не только с реляционной), может быть структура данных, содержащая результаты запроса.
- когда вы используете ассоциативные массивы, часто они имеют небольшое количество фиксированных ключей.
Структуры, подобные записям, являются эквивалентом структур C или хэшей Ruby . Этот рефакторинг является обобщением Replace Array with Object : в этом случае отправной точкой является не просто массив, который всегда имеет одинаковое количество и тип полей, но любая структура данных, однородная для него:
- Zend_Db_Table_Row , реализующий Row Data Gateway и предоставляющий доступ к строке в базе данных.
- Экземпляры (или массивы) stdClass извлекаются с помощью PDOStatement .
В некоторых языках (см. C) запись представляет собой особую структуру данных, которая не является объектом; в PHP это всегда массив или объект некоторого класса вендоров.
Даже ORM, основанные на Active Record , более продвинуты, чем этот вид использования: они обычно позволяют добавлять методы в классы модели, которые заполняются данными самим ORM. В этом рефакторинге речь идет о структуре данных, управляемой языком персистентного уровня, и код которой вы не можете изменить.
Зачем заменять структуру, похожую на запись?
Универсальные классы не позволяют добавлять методы для управления их данными : при непосредственном использовании Zend_Db_Table_Row или ассоциативного массива для хранения результата запроса необходимо постоянно прибегать к сторонним методам . Каждый раз, когда вам нужна новая логика, вы должны подвергать риску инкапсуляцию на самом объекте, и эти методы дублируются в различных экземплярах клиентского кода.
Решения
Существуют разные способы устранить связь с подобной записи структурой.
Первый реорганизовать к подклассам , где структура данных становится активной записью. Вы расширяете класс vendor своим собственным: этот параметр доступен, только если структура определена как объект.
Кроме того, имя класса для создания экземпляра должно быть настраиваемым в механизме сохранения. Zend_Db_Table_Record поддерживает этот вид использования.
Второй вариант заключается в рефакторинг к композиции : существуют примеры Zend_Db и для этого случая. Этот подход можно использовать, чтобы избежать больших иерархий: классы вашей модели составляют объекты Zend_Db и скрывают базу данных; методы могут быть добавлены за один раз в ваших собственных моделях.
Третий и окончательный вариант заключается в использовании гидратации : данные копируются в вашей модели объектов и записи выбрасываются после факта. Doctrine 2 и Data Mappers в целом выбирают этот подход.
меры
Я опишу краткую процедуру рефакторинга для гидратации, поскольку это наиболее сложный подход, и он всегда применим. Другие вместо этого специфичны для конкретной структуры данных (например, с Zend_Db вы должны написать несколько подклассов и настроить некоторые защищенные поля, чтобы они содержали правильные имена классов.)
- Создайте новый класс , как одну из ваших моделей. Его состояние должно быть представлено одной строкой (или несколькими строками, объединенными в одну) в базе данных.
- Этот класс должен получить личное поле для каждого из полей записи , обычно с геттерами и сеттерами.
- Этот класс должен принимать в конструкторе или в методе Factory экземпляр записи , чтобы он мог создать новый экземпляр.
Если вы хотите отделить от постоянства, или вы хотите пойти двумя путями (также сохранить и не только визуализировать, так как это не просто модель представления), ищите Data Mapper, такой как Doctrine 2, который даже скрывает все записывать структуры от вас и менеджеров ассоциаций, где объекты составляют другие.
пример
В этом примере данные одного пользователя возвращаются в массиве. Я выбрал массив в качестве структуры записи, чтобы минимизировать внешние зависимости этого кода.
<?php /** * The test case works as an example of client code, as always. */ class ReplaceRecordWithDataClass extends PHPUnit_Framework_TestCase { public function testShowContainedData() { $users = new UsersTable(); $giorgio = $users->find(42); $this->assertEquals('Giorgio', $giorgio['name']); } } /** * This is a Fake Table Data Gateway. The machinery for making it work with * a database will be distracting for our purposes, so they will be omitted. */ class UsersTable { /** * @return mixed the returned value can be a Zend_Db_Table_Row, * an Active Record, a stdClass, an associative array... * It should just represent a single entity. */ public function find($id) { // execute a PDOStatement and fetch the data return array('id' => 42, 'name' => 'Giorgio'); } }
После рефакторинга у нас есть класс User, куда мы можем добавить все необходимые нам методы:
<?php /** * The test case works as an example of client code, as always. */ class ReplaceRecordWithDataClass extends PHPUnit_Framework_TestCase { public function testShowContainedData() { $users = new UsersTable(); $giorgio = $users->find(42); $this->assertEquals('Giorgio', $giorgio->getName()); } } /** * This is a Fake Table Data Gateway. The machinery for making it work with * a database will be distracting for our purposes, so they will be omitted. */ class UsersTable { /** * @return mixed the returned value can be a Zend_Db_Table_Row, * an Active Record, a stdClass, an associative array... * It should just represent a single entity. */ public function find($id) { // execute a PDOStatement and fetch the data return User::fromRecord(array('id' => 42, 'name' => 'Giorgio')); } } class User { private $id; private $name; public static function fromRecord(array $record) { $object = new self(); $object->id = $record['id']; $object->name = $record['name']; return $object; } public function getName() { return $this->name; } }