img
scroll
#

Реалізація структури бази продуктів з сервісу sql-ex.ru з підтримкою Doctrine2

Uk Ru En
Стаття
#0002

Особливості використання Doctrine2 для реалізації успадкування

Часто деякі сутності володіють загальними властивостями, які слід залишити в базовій сутності (таблиці). Хорошим прикладом цього може служити база продуктів з сервісу sql-ex.ru, з яким багато хто стикався при вивченні SQL. Спробуємо реалізувати структуру пропонованої бази даних за допомогою Symfony і Doctrine2.

Ця база цікава тим, що служить добрим прикладом успадкування. В її основі лежить сутність "продукт", у якій є поля Виробник, Модель і Тип. В оригінальній базі даних як зовнішній ключ використовувалося поле Модель, але в Doctrine дублювання цього поля в таблицях буде зайвим. Крім того, має сенс створити окреме поле Id для деякої систематизированности і розширюваності. Для описаного вище створимо Entity Product.

Implementation of the structure of the product database from the sql-ex.ru service using Doctrine2
<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\InheritanceType("JOINED")
 * @ORM\DiscriminatorColumn(name="type", type="string")
 * @ORM\DiscriminatorMap({"product" = "Product", "pc" = "PC"})
 */
class Product
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $maker;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $model;

    public function getId(): ?int {
        return $this->id;
    }

    public function getMaker(): ?string {
        return $this->maker;
    }

    public function setMaker(string $maker): self {
        $this->maker = $maker;
        return $this;
    }

    public function getModel(): ?string {
        return $this->model;
    }

    public function setModel(string $model): self {
        $this->model = $model;
        return $this;
    }
}

Крім того створимо дочірній клас PC

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */

class PC extends Product
{
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $code;

    /**
     * @ORM\Column(type="integer")
     */
    private $speed;

    /**
     * @ORM\Column(type="integer")
     */
    private $ram;

    /**
     * @ORM\Column(type="integer")
     */
    private $hd;

    /**
     * @ORM\Column(type="integer")
     */
    private $cd;

    /**
     * @ORM\Column(type="float", scale=10, precision=2)
     */
    private $price;

    public function getCode() {
        return $this->code;
    }

    public function getSpeed() {
        return $this->speed;
    }

    public function getRam() {
        return $this->ram;
    }

    public function getHd() {
        return $this->hd;
    }

    public function getCd() {
        return $this->cd;
    }

    public function getPrice() {
        return $this->price;
    }

    public function setCode($value) {
        $this->code = $value;
        return $this;
    }

    public function setSpeed($value) {
        $this->speed = $value;
        return $this;
    }

    public function setRam($value) {
        $this->ram = $value;
        return $this;
    }

    public function setHd($value) {
        $this->hd = $value;
        return $this;
    }

    public function setCd($value) {
        $this->cd = $value;
        return $this;
    }

    public function setPrice($value) {
        $this->price = $value;
        return $this;
    }
}

Тепер звернемо увагу на три останніх анотації ддя класу Product
@ORM\InheritanceType("JOINED") - тут ми вказуємо тип спадкування JOINED, тим самим вказуючи, що зберігати кожен Entity потрібно в окремій таблиці зі своїми колонками, не перевантажуючи, наприклад, Laptop колонками Printer і т.д.

@ORM\DiscriminatorColumn(name="type", type="string") - тут ми вказуємо, що саме поле type буде використовуватися для визначення потрібної таблиці, а значення для колонки type беруться з подальшою анотації.

@ORM\DiscriminatorMap({"product" = "Product", "pc" = "PC"}) - тут видно, що для класу Product у властивості type буде зберігатися значення product, а для PC - pc. Пізніше для Laptop і Printer ми додамо їх значення.
Після настройки підключення до бази даних, можна засобами Doctrine створити міграцію наступною командою php bin/console doctrine:migrations:diff і, якщо все буде добре ;), то мігрувати, - php bin/console doctrine:migrations:migrate.

В цілому основа для подальшого розширення проекту вже зроблена, крім цього можна додати анотації геттерам і сеттерам і типи прийнятих/повернутих значень.

Для реалізації інших таблиць досить додати класи:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */

class Laptop extends Product
{
    /**
     * @ORM\Column(type="string", length=255)
     */
    private $code;

    /**
     * @ORM\Column(type="integer")
     */
    private $speed;

    /**
     * @ORM\Column(type="integer")
     */
    private $ram;

    /**
     * @ORM\Column(type="integer")
     */
    private $hd;

    /**
     * @ORM\Column(type="integer")
     */
    private $screen;

    /**
     * @ORM\Column(type="float", scale=10, precision=2)
     */
    private $price;

    public function getCode() {
        return $this->code;
    }

    public function getSpeed() {
        return $this->speed;
    }

    public function getRam() {
        return $this->ram;
    }

    public function getHd() {
        return $this->hd;
    }

    public function getScreen() {
        return $this->screen;
    }

    public function getPrice() {
        return $this->price;
    }

    public function setCode($value) {
        $this->code = $value;
        return $this;
    }

    public function setSpeed($value) {
        $this->speed = $value;
        return $this;
    }

    public function setRam($value) {
        $this->ram = $value;
        return $this;
    }

    public function setHd($value) {
        $this->hd = $value;
        return $this;
    }

    public function setScreen($value) {
        $this->screen = $value;
        return $this;
    }

    public function setPrice($value) {
        $this->price = $value;
        return $this;
    }
}

and

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 */

class Printer extends Product
{
    /**
     * @ORM\Column(type="boolean")
     */
    private $color;

    /**
     * @ORM\Column(type="string")
     */
    private $printerType;

    /**
     * @ORM\Column(type="float", scale=10, precision=2)
     */
    private $price;

    public function getColor() {
        return $this->color;
    }

    public function setColor($value) {
        $this->color = $value;
    return $this;
    }

    public function getPrinterType() {
        return $this->printerType;
    }

    public function setPrinterType($value) {
        $this->printerType = $value;
        return $this;
    }

    public function getPrice() {
        return $this->price;
    }

    public function setPrice($value) {
        $this->price = $value;
        return $this;
    }
}

Залишається змінити рядок

@ORM\DiscriminatorMap({"product" = "Product", "pc" = "PC"})

на

@ORM\DiscriminatorMap({"product" = "Product", "pc" = "PC", "laptop" = "Laptop", "printer" = "Printer"})

Після цього знову мігруємо і отримуємо чотири таблиці: product - базова, і три похідних, об'єднання яких з базової відбувається по зовнішньому ключу Id.

Якщо ми зробимо запит за допомогою QueryBuilder в одному з методів контролера, то отримаємо об'єкти потрібного класу:

   /**
     * @Route("/printer/list")
     */
    public function printerListAction() {

        $em = $this->getDoctrine()->getManager();

        /** @var QueryBuilder $qb */
        $qb = $em
            ->getRepository(Printer::class)
            ->createQueryBuilder('p')
        ;

        $results = $qb
            ->setMaxResults(20)
            ->getQuery()
            ->getArrayResult()
        ;

        return new JsonResponse(['status' => 'success', 'data' => [$results]]);
    }

де всі необхідні класи вже імпортовані:

use App\Entity\Printer;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;

Тепер розширення проекту на міцній основі з використанням вбудованого функціоналу Doctrine2 відбуватиметься швидше і легше, і ви можете спробувати додати свої класи і таблиці.

Блог
#0002
Статті, які можуть вас також зацікавити
Поради щодо підключення до сервісу API ShipStation
Використовуйте API ShipStation як агрегатор для вашого сервісу, а такж як відмінний інструмент для економії коштів на відправленнях посилок. Опис особливостей сервісу
Рекомендації по підключенню до API Amazon Marketplace Web Service (MWS)
Рекомендації по підключенню до API Amazon Marketplace Web Service (MWS)
Як підключитися до Amazon MWS API? Процес отримання списку замовлень і формування підпису запиту з використанням декількох параметрів
Оптимізація веб-проєкту — 3 способи прискорити завантаження веб-сторінок
Оптимізація веб-проєкту — 3 способи прискорити завантаження веб-сторінок
Ви розробили дизайн сайту, підібрали відповідні теми картинки та замовили дорогий текст для лендінгу, але кількість відвідувань мінімальна.
Реалізація підключення API Payoneer
Підключення до платіжної системи API Payoneer. Основні середовища підключення. Проходження користувачем реєстрації/авторизації за наданим посиланням, список користувачів
Зв'яжіться з нами
#0013
Готові Розпочати? Повідомте нас про це!
Телефон:
Адреса:

Україна, Житомир
вул. Вітрука 9в

Пн-Пт 9.00 - 19.00

Зв'яжіться з нами
#0000
Залишилися запитання?
Опишіть свою проблему, заповніть форму нижче та наші спеціалісти допоможуть Вам!
Обов'язкове поле
Обов'язкове поле
Обов'язкове поле
Обов'язкове поле