vyshkant
@vyshkant
developer

Как реализовать интерфейс на уровне базы данных (Symfony3, Doctrine ORM, SonataAdminBundle)?

Добрый день.

Я использую Symfony 3, Doctrine ORM, SonataAdminBundle.

Возникла задача, решением которой мне видится внедрение определённого интерфейса, однако я пока что не представляю, как правильно хранить данные такой структуры в базе и править в админке.

Предположим, есть такие исходные условия (прошу прощения за искусственность построения).

Есть сущность "Film", которая обладает какими-то своими свойствами (нам они не интересны). Для фильмов есть таблица в БД `film`.
Класс Film
/**
 * @ORM\Table(name="film")
 * @ORM\Entity
 */
class Film
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    ...
}

Есть сущность "User", которая означает пользователя. Таблица `user` в БД.
Класс User
/**
 * @ORM\Table(name="user")
 * @ORM\Entity
 */
class User
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    ...
}

Есть сущность "Cinema". У кинотеатра, среди прочего, есть поле "Город", которое определяет, в каком городе расположен кинотеатр. Для кинотеатров есть таблица в БД `cinema`.
Класс Cinema
/**
 * @ORM\Table(name="cinema")
 * @ORM\Entity
 */
class Cinema
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var Town
     *
     * @ORM\ManyToOne(targetEntity="Town")
     * @ORM\JoinColumn(name="town_id", referencedColumnName="id", nullable=false)
     */
    private $town;

    /**
     * @return Town
     */
    public function getTown()
    {
        return $this->town;
    }

    ...
}


Ну и есть, собственно, сущность "Город":
Класс Town
/**
 * @ORM\Table(name="town")
 * @ORM\Entity
 */
class Town
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    ...
}

Далее возникает необходимость создать из этих данных "лог просмотров", т.е. объединить данные о каждом просмотре в сущности "View" (т.е. "Просмотр", ничего общего с view из MVC). Таблица `view` в БД:
Класс View
/**
 * @ORM\Table(name="view")
 * @ORM\Entity
 */
class View
{
    /**
     * @var User
     *
     * @ORM\ManyToOne(targetEntity="User")
     * @ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
     */
    private $user;

    /**
     * @var Film
     *
     * @ORM\ManyToOne(targetEntity="Film")
     * @ORM\JoinColumn(name="film_id", referencedColumnName="id", nullable=false)
     */
    private $film;

    /**
     * @var Cinema
     *
     * @ORM\ManyToOne(targetEntity="Cinema")
     * @ORM\JoinColumn(name="cinema_id", referencedColumnName="id", nullable=false)
     */
    private $cinema;

    /**
     * @return Cinema
     */
    public function getCinema()
    {
        return $this->cinema;
    }

    ...
}

Довольно простая схема, дающая довольно простую структуру таблиц и связей между ними.

Предположим, что в нашей бизнес-логике очень важным является то, в каком городе пользователь осуществил просмотр. Для этого мы, имея объект класса "View" без проблем вызываем $view->getCinema()->getTown(), двигаясь по цепочке many-to-one.

Но вдруг оказывается, что пользователь мог посмотреть фильм не только в кинотеатре (ВНЕЗАПНО), но и:
  • по телевизору
  • по компьютеру


На уровне кода решение кажется предельно простым: в классе "View" вместо поля "cinema" должно быть поле "viewPlace" (место просмотра), которое содержит объект, реализующий интерфейс ViewPlaceInterface, который обладает методом getTown(), что позволит нам вызвать $view->getViewPlace()->getTown():
Новый класс View, интерфейс ViewPlaceInterface, классы Cinema, TV, Computer
class View
{
    ...

    /**
     * @var ViewPlaceInterface
     */
    private $viewPlace;

    /**
     * @return ViewPlaceInterface
     */
    public function getViewPlace()
    {
        return $this->viewPlace;
    }

    ...
}

interface ViewPlaceInterface
{
    /**
     * @return Town
     */
    public function getTown();
}

class Cinema implements ViewPlaceInterface
{
    /**
     * @return Town
     */
    public function getTown()
    {
        ...
    }
}

class TV implements ViewPlaceInterface
{
    /**
     * @return Town
     */
    public function getTown()
    {
        ...
    }
}

class Computer implements ViewPlaceInterface
{
    /**
     * @return Town
     */
    public function getTown()
    {
        ...
    }
}

Вопрос: как реализовать это в условиях Symfony 3, Doctrine ORM, SonataAdminBundle?

Какие должны быть сущности, какие таблицы, какие аннотации?

Как должна быть сконфигурирована админка, чтобы всё это можно было заполнять через неё?

Спасибо.
  • Вопрос задан
  • 329 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы