Создание интерфейсов к БД на Symfony 2

Часто встречающейся задачей при разработке веб-приложений является создание интерфейсов, административного и пользовательского, для редактирования и отображения некоторых записей БД. Соответственно в административном интерфейсе должна быть возможность заносить, искать и редактировать записи, а в пользовательском интерфейсе — искать и просматривать записи.

При разработке проекта на основе фреймворка Symfony 2.1 для создания такого типового решения будем использовать:

1. Разработка модели данных — Doctrine ORM, интегрированная с фреймворком Symfony 2
2. Разработка административного интерфейса — Sonata Admin Bundle
2. Разработка пользовательского интерфейса — стандартные возможности Symfony 2 + IphpCoreBundle

Таблица БД

Допустим у нас уже есть некоторые данные (таблица БД), которую нужно перенести в проект на Symfony 2. В примере используем следующую структуру таблицы БД:

CREATE TABLE `link` (
  `id` INT(10) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(200) NOT NULL DEFAULT '',
  `url` VARCHAR(100) NOT NULL DEFAULT '',
  `annotation` text NOT NULL,
  `created_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `updated_at` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY  (`link_id`)
) ;

В таблице должно быть поле id, которое по умолчанию используется как уникальный идентификатор записи (первичный ключ). Если поле называется иначе — переименуйте его. Если в таблице есть поля с датой создания и обновления записи — из имена должны быть created_at и updated_at соответственно.

Создание доменной модели Doctrine ORM

Предполагаем что в проекте уже есть бандл, который называется MyDbBundle. Для того чтобы на основе таблицы создать xml-описание сущности и php-класс сущности выполним команды:

#php app/console doctrine:mapping:import MyDbBundle xml --filter=Link
#php app/console doctrine:generate:entities MyDbBundle:Link

Первая команда создаст файл src/My/DbBundle/Resources/config/doctrine/Link.orm.xml вида

<?xml version="1.0" encoding="utf-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
  <entity name="My\DbBundle\Entity\Link" TABLE="link">
    <id name="id" TYPE="integer" COLUMN="id">
      <generator strategy="IDENTITY"/>
    </id>
    <field name="title" TYPE="string" COLUMN="title" LENGTH="200"/>
    <field name="url" TYPE="string" COLUMN="url" LENGTH="100"/>
    <field name="annotation" TYPE="text" COLUMN="annotation"/>
    <field name="createdAt" TYPE="datetime" COLUMN="created_at"/>
    <field name="updatedAt" TYPE="datetime" COLUMN="updated_at"/>
  </entity>
</doctrine-mapping>

Добавление сущности symfony

Вторая на основе xml-описания сущности создает PHP класс сущности src/My/DbBundle/Entity/Link

<?php
namespace My\DbBundle\Entity;
 
class Link
{
    /**
     * @var string $title
     */
    private $title;
 
    /**
     * @var string $url
     */
    private $url;
 
    /**
     * @var string $annotation
     */
    private $annotation;
 
    /**
     * @var \DateTime $createdAt
     */
    private $createdAt;
 
    /**
     * @var \DateTime $updatedAt
     */
    private $updatedAt;
 
    /**
     * @var integer $id
     */
    private $id;
 
 
    /**
     * Set title
     *
     * @param string $title
     * @return Link
     */
    public function setTitle($title)
    {
        $this->title = $title;
 
        return $this;
    }
 
    /* 
     ...
     Геттеры и сеттеры для cущности
     ...
    */
}

Xml-описание , PHP-класс и таблицу в БД также можно создать при помощи команды

doctrine:generate:entity

подробнее про эту команду можно посмотреть здесь

Админинистративный интерфейс с Sonata Admin Bundle

Установка и начало работы с Sonata Admin Bundle подробно описана в статье. В нашем случае нужно создать один административный класс и один сервис.

Административный класс

<?php
# src/My/DbBundle/Admin/LinkAdmin.php
namespace My\DbBundle\Admin;
 
use Sonata\AdminBundle\Admin\Admin;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\AdminBundle\Datagrid\DatagridMapper;
use Sonata\AdminBundle\Datagrid\ListMapper;
 
use My\DbBundle\Entity\Link;
 
class LinkAdmin extends Admin
{
    protected function configureDatagridFilters(DatagridMapper $datagridMapper)
    {
        $datagridMapper->add('title')
                      ->add('url');
    }
 
    protected function configureListFields(ListMapper $listMapper)
    {
        $listMapper->addIdentifier('title')
                   ->add('url')
                   ->add('updatedAt');
    }
 
    protected function configureFormFields(FormMapper $formMapper)
    {
        $formMapper->add('title')
                   ->add('url')
                   ->add('annotation');
    }
}

Описание сервиса

<!-- src/My/DbBundle/Resources/config/services.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
    <services>
      <service id="sonata.admin.my.db.link" class="My\DbBundle\Admin\LinkAdmin">
            <tag name="sonata.admin" manager_type="orm" group="Базы данных" label="Ссылки"/>
            <argument/>
            <argument>My\DbBundle\Entity\Link</argument>
            <argument>SonataAdminBundle:CRUD</argument>
        </service>
    </services>
 
</container>

После этого в административном меню Sonata Admin должен появится пункт «Базы данных» / «Ссылки» (если список пунктов административного меню формируются вручную — нужно добавить идентификатор сервиса sonata.admin.my.db.link в список отображаемых пунктов меню)

Для того чтобы названия служебных сообщений показывались на русском языке, нужно создать файл переводов

<!-- src/My/DbBundle/Resources/tranlations/messages.ru.xliff -->
<?xml version="1.0"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
    <file source-language="ru" datatype="plaintext" original="">
        <body>
            <trans-unit id="Link List">
                <source>Link List</source>
                <target>Список ссылок</target>
            </trans-unit>
           <trans-unit id="Link Edit">
                <source>Link Edit</source>
                <target>Редактирование ссылки</target>
            </trans-unit>
            <trans-unit id="Link Create">
                <source>Link Create</source>
                <target>Занесение информации о ссылке</target>
            </trans-unit>
            <trans-unit id="Link Delete">
                <source>Link Delete</source>
                <target>Удаление ссылки</target>
            </trans-unit>
        </body>
    </file>
</xliff>

Автоматическое сохранение даты создания и обновления записи

Добавим в модель данных автоматическое заполнение даты создания и обновления записи. Для этого нужно модифицировать xml-описание сущности Doctrine ORM

#src/My/DbBundle/Resources/config/doctrine/Link.orm.xml

..
  <field name="createdAt" type="datetime" column="created_at">
    <gedmo:timestampable on="create"/>
 </field>
 <field name="updatedAt" type="datetime" column="updated_at">
    <gedmo:timestampable on="update"/>
 </field>
...

В данном случае используются директивы библиотеки Doctrine Extensions, которые интегрируются в проект на Symfony 2 с помощью StofDoctrineExtensionsBundle. Для обновления даты применяется поведение Timestampable.

Для того, чтобы в административном интерфейсе отображались дата и время создание и обновления записи, изменим административный класс, унаследовав его от класса Iphp\CoreBundle\Admin\Admin:

<?php
# src/My/DbBundle/Admin/LinkAdmin.php
use Iphp\CoreBundle\Admin\Admin as IphpAdmin;
..
class LinkAdmin extends IphpAdmin
{
...

Класс Iphp\CoreBundle\Admin\Admin дополняет стандартный метод формирования меню кодом, который в случае наличия у редактируемой сущности методов getUpdatedAt и/или getCreatedAt добавляет в левое меню соответствующие записи. В результате на странице с формой редактирования записи в левом столбце отображаются дата и время создания и редактирования информации о ссылке.

Пользовательский интерфейс

При создании пользовательского интерфейса в данном примере используем возможности IphpCoreBundle: Модули и базовый EntityController.

Сначала нужно создать класс-модуль, который будет размещен в рубрике. В конструкторе класса указывается название модуля («Ссылки») и название сущности, которая будет отображаться на сайте (MyDbBundle:Link). Класс EntityModule по умолчанию заносит два маршрута — index и view для отображения списка записей и вывода одной записи.

<?php
#src/My/DbBundle/Module/LinkModule.php
namespace My\DbBundle\Module;
use Iphp\CoreBundle\Module\EntityModule;
 
class LinkModule extends EntityModule
{
    function __construct()
    {
        $this->setName('Ссылки');
        $this->setEntityName ('MyDbBundle:Link');
    }
}

Затем создаем контроллер, который будет выводить список ссылок и информацию о ссылке.
В качестве базового класса используем Iphp\CoreBundle\Controller\EntityController, в котором базовый контроллер Symfony дополнен методами, с помощью которых базовые операции (список, отображение) уже заложены в код класса.

<?php
#src/My/DbBundle/Controller/LinkController.php
namespace My\DbBundle\Controller;
use Iphp\CoreBundle\Controller\EntityController;
class LinkController extends EntityController
{
    function __construct()
    {
        $this->entityName = 'MyDbBundle:Link';
    }
}

Шаблоны для пользовательского интерфейса

Далее нужно создать шаблоны для вывода списка записей и отображения информации о записи. В базовом классе EntityModule для методов indexAction и viewAction используется аннотация /* @template */ из бандла SensioFrameworkExtraBundle которая автоматически назначает имя шаблона для действия (action). В нашем случае нужно создать шаблоны

  • src/My/DbBundle/Resources/views/Link/index.html.twig
  • src/My/DbBundle/Resources/views/Link/view.html.twig

Шаблон списка записей

{# src/My/DbBundle/Resources/views/Link/index.html.twig #}
{% extends '::base.html.twig' %}
 
{% block body %}
{% for link in entities %}
<div>
 
    <a target="blank" href="{{ link.url }}"><strong>{{ link.title }}</strong></a>
    {{ link.annotation|raw }}
    <a href="{{ entitypath(link)}}">Информация о ссылке </a>
</div>
{% endfor %}
{% endblock body %}

В шаблоне списка записей используется добавленная бандлом IphpCoreBundle функция шаблонизатора Twig entitypath, которая выводит значение, возращаемое методом getSitePath объекта, передаваемого в качестве аргумента. Соответственно, чтобы в нашем шаблоне работала ссылка «Информация о ссылке», нужно добавить в класс Link метод getSitePath. Этот метод может возвращать путь на сайте, жестко определенный в коде класса, например:

 ...
 function getSitePath()
 {
   return '/links/'.$this->getId();
 }

Однако, в нашем случае модуль LinkModule, выдающих список ссылок и информацию о ссылках, может быть размещен в любой рубрике сайта с любым адресом, и мы не можем жестко зашивать в коде класса путь к странице с информацией о ссылке. Поэтому мы будем использовать следующий код для метода Link::getSitePath:

<?php
namespace My\DbBundle\Entity;
 
class Link
{
 ...
    public function getSitePath(\Iphp\CoreBundle\Routing\EntityRouter $entityRouter)
    {
        return $entityRouter->generateEntityActionPath ($this,'view');
    }
 ...
}

Объект EntityRouter на основании данных объекта сущности ($this) и названия действия («view») автоматически сгенерирует путь на сайте к текущему объекту. Данный подход работает только если при создании модуля используется класс EntityModule — в данном классе маршруты автоматически именуются так, чтобы их мог найти EntityRouter по объекту и названию действия. Путь на сайте генерируется на основе рубрики, в котором размещен модуль и идентификатора объекта.

Шаблон вывода информации о записи

{# src/My/DbBundle/Resources/views/Link/view.html.twig #}
{% extends '::base.html.twig' %}
 
{% block body %}
<h1>{{ entity.title }}</h1>
<div><a href="{{ entity.url }}">{{ entity.url }}</a></div>
<div>{{ entity.annotation | raw }}</div>
{% endblock body %}

Наличие метода получения ссылки на объект и страницы с выводом информации об объекте позволяет при поиске объектов различного типа, например через ElasticSearch, в списке результатов давать ссылки на объекты, используя функцию twig entitypath.

Запись опубликована в рубрике Без рубрики. Добавьте в закладки постоянную ссылку.

Один комментарий: Создание интерфейсов к БД на Symfony 2

  1. Ruslan говорит:

    Здравствуйте.
    Где еще спросить не нашел (может плохо искал?), но возникла проблемка.
    Например есть сущность Product. Это товар для запчасти автомобиля.
    Продукт связан с сущностью Model, которая является моделью автомобиля.
    При добавлении с этим проблем как бы нет. Обычный выпадающий список. Sonata с этим отлично справляется.
    Но вот Model связана с сущностью марки автомобиля. И продукт связан с маркой только через модель.

    Хотелось бы в форму добавления продукта добавить не только модель, но еще и указание марки, а список моделей формировался при изменении указанной марки. Либо подгружался аяксом, либо просто фильтровался и выводились модели только выбранной марки, это не важно…

    Может есть уже готовые решения для этого? Если нет, то как можно лучше это решить?

    С симфони всего пару дней работаю, много всего нового и интересного =)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="" highlight="">