Меню

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

logo
Расширяйте проект на прочной основе с использованием встроенного функционала Doctrine2, попробуйте добавить свои классы и таблицы. Характеристика базы продуктов
Реализация структуры база продуктов с сервиса sql-ex.ru с помощью Doctrine2

Особенности использования Doctrine2 для реализации наследования

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

База эта интересна тем, что служит хорошим примером наследования. В её основе лежит сущность "продукт", у которой есть поля Производитель, Модель и Тип. В оригинальной базе данных в качестве внешнего ключа использовалось поле Модель, но в Doctrine дублирование этого поля в таблицах будет излишним, кроме того имеет смысл создать отдельное поле Id для некоторой систематизированности и расширяемости. Для описанного выше создадим Entity Product.

<?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;
    }
}

и

<?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 будет происходить быстрее и легче, и вы можете попробовать добавить свои классы и таблицы.

Подключение API EasyPost 07.10.2019 Подключение API EasyPost
Особенности подключения API EasyPost

Особенности подключения Iiko API 24.12.2019 Особенности подключения Iiko API
Iiko API это JSON API. Каждый запрос нужно подписывать специальным временным токеном доступа. Получить временный токен можно используя имя и пароль Вашего Iiko аккаунта, предоставленного Вам Iiko.

Возврат к списку