Когда я впервые начал изучать 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. В следующей статье, выходящей в следующий понедельник, мы пройдем функциональное тестирование и будем использовать данные, которые мы здесь увидели.
Не стесняйтесь комментировать, и мы будем рады более подробно рассмотреть эту тему, если вы заинтересованы.