Когда я впервые начал изучать Symfony (1.x) с его проектом Jobeet, я думал, что возможность загружать кучу тестовых данных в базу данных очень полезна.
В этой статье мы вернемся к этой функции, которая была полностью перемоделирована и поэтому многому нас может научить.
подготовка
В этой статье у нас будет две сторонние библиотеки для дальнейшего расширения возможностей Symfony.
Первый — это DoctrineFixturesBundle
, используемый для загрузки тестовых данных с помощью Doctrine ORM , который является стандартным ORM в Symfony.
Пожалуйста, следуйте инструкциям в официальной документации Symfony для установки и настройки DoctrineFixturesBundle. Если вы знакомы с Composer, процесс должен быть простым и понятным, никаких хлопот.
Затем мы установим PHPUnit, стандартную среду тестирования, используемую Symfony. Его официальный сайт предлагает загрузить последнюю phpunit.phar
файла phpunit.phar
. Просто сохраните этот файл в корневой каталог Symfony, и все готово. Symfony поставляется со стандартным и работоспособным файлом конфигурации PHPUnit ( app/phpunit.xml.dist
). В обычных условиях мы оставим этот файл без изменений, и PHPUnit будет работать нормально. Мы будем использовать PHPUnit в продолжении этой статьи, поэтому просто убедитесь, что он у вас есть.
Конечно, чтобы две вышеупомянутые библиотеки работали в нашем приложении, нам нужно сначала настроить проект / приложение Symfony. Эта тема освещена в моей предыдущей статье «Создание веб-приложения с помощью Symfony 2: начальная загрузка» . Пожалуйста, посмотрите, если вы еще не знакомы с настройкой Symfony 2. На самом деле, эта статья довольно тесно связана с приложением Book Collection, которое мы создали ранее (хотя и с некоторыми изменениями в схеме базы данных).
Написание нашего первого файла фиксации данных
Поскольку мы создаем приложение для сбора книг, нам нужно несколько таблиц для описания связей между информацией, связанной с книгой:
-
book_book
: содержит информацию о книге, включая идентификатор книги, название, автора, ISBN, издателя (FK для другой таблицыbook_publisher
), место покупки (FK для другой таблицыbook_place
), дату покупки и т. д. -
book_place
,book_publisher
: содержит информацию о месте покупки и издателе соответственно. -
book_taglist
: содержит тег для книги и формирует отношение «многие ко многим» к таблицеbook_book
. -
book_headline
,book_review
: содержит мои мысли о чтении определенной книги. Заголовок является основной таблицей и формирует отношение один-ко-многим сbook_review
, отношение один-к-одному сbook_book
. - Другие таблицы для сбора дополнительной информации.
В этом приложении я настраиваю базу данных ( rsywx_test
) со следующей структурой. Схема рисуется с помощью этого онлайн-инструмента .
ПРИМЕЧАНИЕ . Я перечислил не все таблицы / поля на этом чертеже схемы, а только эти первичные ключи и внешние ключи, чтобы проиллюстрировать отношения между таблицами. Полный дамп SQL можно найти в репозитории Github, созданном для этой статьи.
Для начала мы создадим book_place
данных для book_place
.
<? php namespace tr\rsywxBundle\DataFixtures\ORM ;
use \Doctrine\Common\DataFixtures\AbstractFixture ;
use \Doctrine\Common\DataFixtures\OrderedFixtureInterface ;
use \Doctrine\Common\Persistence\ObjectManager ;
use \tr\rsywxBundle\Entity\BookPlace as BookPlace ;
class LoadPlaceData extends AbstractFixture implements OrderedFixtureInterface
{
/** * * {@inheritDoc} */
public function load ( ObjectManager $manager )
{
//Create a common place $place1 = new BookPlace (); $place1 -> setName ( 'Common' ); $this -> addReference ( 'commonPlace' , $place1 );
//Create a special place $place2 = new BookPlace (); $place2 -> setName ( 'Special' ); $this -> addReference ( 'specialPlace' , $place2 ); $manager -> persist ( $place1 ); $manager -> persist ( $place2 ); $manager -> flush ();
}
/** * * {@inheritDoc} */
public function getOrder ()
{
return 2 ;
}
}
Несколько вещей, чтобы выделить здесь:
- Есть 4 заявления об
use
. Первые 3 являются стандартными и требуются почти для всех файлов данных. Последнееuse
состоит в том, чтобы включить пространство имен для таблицыbook_place
. Это должно соответствовать таблице, в которую мы должны загрузить данные. - Файл
OrderedFixtureInterface
данных будет содержать настраиваемый класс (происходит отAbstractFixture
и реализуетOrderedFixtureInterface
) и содержит по крайней мере две функции:load
(для загрузки данных примера) иgetOrder
(для указания порядка загрузки и сохранения целостности ссылок среди PK / FK). ). - В функции
load
мы создаем несколько экземпляровbook_place
и присваиваем значения дляname
. Полеid
не должно быть назначено, так как это поле с автоинкрементом. - Кроме того, после создания объекта
book_place
мы добавляем ссылку на этот объект с помощью$this->addReference()
. Это ОБЯЗАТЕЛЬНО, так как нам потребуетсяbook_book
ссылка на этот объект приbook_book
данных таблицыbook_book
. Возможно, вы помните, что в таблицеbook_book
ее полем является полеid
таблицы FK tobook_place
. - Наконец, мы сохраняем объект и делаем физическую вставку в таблицу.
- В функции
getOrder
мы явно указываем, что этот файлgetOrder
должен быть загружен на втором месте (только в моем случае послеbook_publisher
).
Этот файл данных должен быть помещен в your project root/src/your bundle namespace/DataFixtures/ORM
. В моей бродячей установке Symfony каталог является /www/rsywx/src/tr/rsywxBundle/DataFixtures/ORM
а имя файла — LoadPlace.php
.
Теперь мы можем загрузить файл данных:
php app / console доктрина: светильники: нагрузка
Эта консольная команда Symfony захватит все файлы, находящиеся в указанном выше каталоге, и начнет вставлять образцы данных в порядке, указанном функцией getOrder()
каждого файла.
Выполнено! Вернитесь к своему любимому инструменту администрирования MySQL и сделайте select * from book_place
или тому подобное, мы увидим, что две записи были вставлены в таблицу book_place
.
Загрузить данные книги и ссылки на другие объекты
Далее, давайте загрузим данные книги. Этот файл фиксации будет немного сложнее, так как он будет использовать цикл для создания множества объектов книги, а также использовать ранее созданную ссылку на объект, чтобы установить значение для внешних ключей.
Выдержка из LoadBook.php
выглядит следующим образом:
<? php namespace tr\rsywxBundle\DataFixtures\ORM ;
...
use \tr\rsywxBundle\Entity\BookBook as BookBook ;
class LoadBookData extends AbstractFixture implements OrderedFixtureInterface
{
/** * * {@inheritDoc} */
public function load ( ObjectManager $manager )
{
//Now we create a 100 general book
for ( $i = 1 ; $i <= 100 ; $i ++)
{ $p = new BookBook (); $p -> setAuthor ( 'Normal' );
... $p -> setPurchdate ( new \DateTime ()); $p -> setPubdate ( new \DateTime ());
... $p -> setPage ( $i );
... $p -> setPublisher ( $this -> getReference ( 'commonPub' )); $p -> setPlace ( $this -> getReference ( 'commonPlace' )); $manager -> persist ( $p );
}
//Create a special book $s = new BookBook ();
... $s -> setPurchdate ( new \DateTime ( '1970-1-1' )); $s -> setPubdate ( new \DateTime ( '1970-1-1' )); $s -> setPrintdate ( new \DateTime ( '1970-1-1' ));
... $this -> addReference ( 'aBook' , $s ); $manager -> persist ( $s ); $manager -> flush ();
}
/** * * {@inheritDoc} */
public function getOrder ()
{
return 3 ;
}
}
В этом файле фикстера мы создали в общей сложности 101 запись. Один из них является особенным, так как он создается в самом конце (таким образом, имеет наибольший id
) и его поля, связанные с датой, установлены в EPOCH.
Обратите внимание, как мы устанавливаем поля FK ( publisher
и place
). Поскольку эти два поля являются FK для других таблиц, мы не можем просто присвоить целое число этим двум. Нам нужно будет использовать $this->getReference()
для захвата ранее созданных объектов. И да! Вот почему нам нужен getOrder()
чтобы указать последовательность загрузки, т. getOrder()
Таблица книг должна быть загружена после таблицы издателя и таблицы размещения.
Кроме того, мы создали ссылку на мою «Специальную книгу», чтобы этот объект можно было использовать позже для других таблиц ( book_headline
, book_taglist
).
В моем приложении всего 6 файлов данных. После загрузки этих 6 файлов база данных для моего приложения более или менее готова. После некоторой кодировки в Controllers, Views, сайт будет работать:
С примерами данных сайт становится более значимым и может помочь в нашей дальнейшей настройке и функциональном тестировании.
У нас есть много функций на этой странице индекса, которая не рассматривается в этой статье. Например, есть два виджета Dart, показывающие QOTD (цитата дня) и информацию о местной погоде. Это описано в моих предыдущих статьях в Sitepoint ( часть 1 и часть 2 ).
Мы почти закончили работу с Symfony DataFixturesBundle, за исключением еще одной вещи. Вспомните мою статью об уникальном индексе ? В некотором смысле, я категорически против использования целочисленных полей с автоинкрементом в качестве первичного ключа таблицы. Также как перфекционист, когда я создаю значимый уникальный индекс, я просто исключаю существование другого автоматически увеличиваемого целочисленного поля и использую этот интерфейс в качестве моего PK.
Но чтобы соблюсти ограничение пакета данных, необходимо сделать один компромисс. Symfony DataFixturesBundle говорит:
Должно быть автоматически увеличенное целочисленное поле в качестве PK для установления ссылки на связь
Конечно, вы можете создать пользовательский интерфейс и другие индексы, если хотите.
Итак, в моей таблице book_book
, хотя у меня есть самодостаточное поле ( book_id
в форме, например «12345», строка для обозначения моих книг), я должен создать другое поле ( id
, автоинкрементное целое число), чтобы действовать как ПК!
Если мы пропустим этот шаг, сама структура базы данных все еще действительна, и все отношения FK / PK все еще могут быть установлены. Тем не менее, будет по крайней мере одна странная и трудная для понимания ошибка: $this->getReference()
в любой таблице, которая ссылается на book_book
(без автоматически увеличиваемого целочисленного поля как PK), завершится ошибкой, вызывая ошибку «Неопределенный индекс» ,
Вывод
В этой статье мы рассмотрели Data Fixtures и правильный способ предоставления образцов данных для нашего приложения через Doctrine. В следующей статье, выходящей в следующий понедельник, мы пройдем функциональное тестирование и будем использовать данные, которые мы здесь увидели.
Не стесняйтесь комментировать, и мы будем рады более подробно рассмотреть эту тему, если вы заинтересованы.