Бази даних і Doctrine (Частина 2)

01/03/2016 0 symfony, розробка, налаштування

 

Добування об’єктів з бази даних і внесення їх назад до бази даних

Добування об’єктів з бази даних і внесення їх назад до бази даних - ще легший процес. Наприклад, ви налаштували маршрутизатор так, щоб він показував певний об’єкт Product, базуючись на значенні id.

 public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    // ... do something, like pass the $product object into a template
}

Ви можете досягти того ж результату, використовуючи @ParamConvertershortcut. Детальніше: FrameworkExtraBundle Documentation.

Коли ви робите запит до певного типу об’єкту, ви зазвичай користуєтеся так званим  репозиторієм ("repository"). Це може бути репозиторій типу PHP класу, чиє основне завдання добувати і вносити класи-сутності певного класу. Можна мати доступ до об’єкту репозиторія для класу-сутності через наступну команду:

 $repository = $this->getDoctrine()
    ->getRepository('AppBundle:Product');

Рядок AppBundle:Product є скороченням, яке можна використовувати будь-де у Doctrinе, замість повного імені класу-сутності (AppBundle\Entity\Product). Це працюватиме, поки об’єкт “проживатиме” під іменем Entity у вашому пакеті.

Якщо у вас є такий репозиторій, то у вас є доступ до всіх видів потрібних методів:

// query by the primary key (usually "id")
$product = $repository->find($id);

// dynamic method names to find based on a column value
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');

// find *all* products
$products = $repository->findAll();

// find a group of products based on an arbitrary column value
$products = $repository->findByPrice(19.99);

Звичайно, ви можете давати команди на виконання складніших запитів, про які ви дізнаєтеся у цьому ж розділі.

Також можна вигідно використати методи findBy та findOneBy для добування і внесення класів-сутностей на основі багатьох умов.

// query for one product matching by name and price
$product = $repository->findOneBy(
    array('name' => 'foo', 'price' => 19.99)
);

// query for all products matching the name, ordered by price
$products = $repository->findBy(
    array('name' => 'foo'),
    array('price' => 'ASC')
);

Оновлюємо об’єкт

Як тільки ви добудете об’єкт з бібліотеки Doctrine, оновити його буде дуже просто. Уявіть, що ви маєте маршрут, який зіставляє id продукту з оновленими діями у контролері:

public function updateAction($id)
{
    $em = $this->getDoctrine()->getManager();
    $product = $em->getRepository('AppBundle:Product')->find($id);

    if (!$product) {
        throw $this->createNotFoundException(
            'No product found for id '.$id
        );
    }

    $product->setName('New product name!');
    $em->flush();

    return $this->redirectToRoute('homepage');
}

Оновлення об’єкту включає три кроки:

1. Добування об’єкта з бібліотеки Doctrine;

2. Зміна об’єкта;

3. Виконання методу flush() із entity manager;

Зауважте, що виконання $em->persist($product) не обов’язкове. Нагадаємо, що цей метод повідомляє Doctrine про те, що бібліотеці потрібно керувати та слідкувати за об’єктом $product. У такому випадку, коли ви вже добули об’єкт $product з бібліотеки  Doctrine, він уже перебуває під її керуванням.

Видаляємо об’єкт

Видалення об’єкта є подібним процесом до попереднього, але у цьому випадку нам потрібен метод remove() із entity manager.

$em->remove($product);
$em->flush();

Ви напевно вже здогадались, що метод  remove() повідомляє Doctrine, про те, що ви плануєте видалити даний об’єкт з бази даних. Фактично, запит DELETE не виконується, допоки не ввійде в дію метод flush().

Запити до об’єктів

Ви вже побачили, як об’єкти зі сховища дозволяють запускати базові запити без зайвої роботи:  

$repository->find($id);
$repository->findOneByName('Foo');

Звичайно, за допомогою Doctrine можна писати більш складні запити використовуючи Doctrine Query Language (DQL). Мова DQL подібна до SQL, проте користуючись DQL вам потрібно уявити, що ви посилаєте запит до одного, чи більше класів-сутностей (наприклад, класу Product), замість посилати запит до рядків у таблиці (наприклад, таблиці product).

Коли ми посилаємо запити до бібліотеки Doctrine, у нас є дві опції: писати чисті Doctrine запити або користуватися Doctrine's Query Builder.

Робимо запити до об’єктів через DQL 

Уявіть, що ви хочете зробити запит на певні продукти, але отримати їх у порядку від найдешевшого до найдорожчого, і лише ті, які коштують більш ніж 19.99. Щоб виконати цей запит використаємо подібну до SQL мову Doctrine, яку називають DQL:

$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
    'SELECT p
    FROM AppBundle:Product p
    WHERE p.price > :price
    ORDER BY p.price ASC'
)->setParameter('price', '19.99');

$products = $query->getResult();
// to get just one result:
// $product = $query->setMaxResults(1)->getOneOrNullResult();

Якщо ви дуже добре знаєте SQL, то користування DQL не буде проблемою. Найвагомішою відмінністю є те, що замість роботи з рядками у базі даних ви будете працювати з об’єктами. Через це, слід зробити вибірку з об’єкта AppBundle:Product (скорочення від AppBundle\Entity\Product) і потім записати це під назвою p.

Також запам’ятайте метод setParameter(). Працюючи з Doctrine, було б добре встановити певні зовнішні значення, такі наприклад як “заповнювачі” або, як їх ще називають "placeholders" (:price, згадувалося у попередньому прикладі). Вони запобігатимуть атакам SQL-ін'єкцій.

Метод getResult() повертає масив результатів. Для того, щоб отримати лише один результат, використовується метод getOneOrNullResult():

 $product = $query->setMaxResults(1)->getOneOrNullResult();

Надзвичайно потужний синтаксис DQL дозволяє легко об’єднувати класи-сутності (поняття відношення ми розглянемо пізніше). Щоб дізнатися деталі, читайте офіційну документацію: Doctrine Query Language.

Робимо запити до об’єктів за допомогою Doctrine's Query Builder

Замість того, щоб писати DQL рядок, використаємо об’єкт QueryBuilder. Це зручно, коли реальний запит залежить від динамічних умов. Користуючись DQL, ваш код скоро стане важким для читання, адже ви будете пов’язувати рядки між собою:

$repository = $this->getDoctrine()
    ->getRepository('AppBundle:Product');

// createQueryBuilder automatically selects FROM AppBundle:Product
// and aliases it to "p"
$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();

$products = $query->getResult();
// to get just one result:
// $product = $query->setMaxResults(1)->getOneOrNullResult();
 

Об’єкт QueryBuilder містить усі методи, які будуть потрібні для того, щоб побудувати запит. Виконавши getQuery() метод, “конструктор запитів” повертає звичайний об’єкт, який може використовуватися для отримання результатів запиту.

Більше інформації про Doctrine's Query Builder ви можете почитати у документації Query Builder.

Custom Repository Classes

У попередніх підрозділах, ми вже почали створювати та використовувати більш складні запити з середини контролера. Для того щоб ізолювати, протестувати, та повторно використати ці запити, можна попрактикуватися створювати “сustom repository classes” для вашого класу-сутності та додати методи з запитми туди ж.

Щоб це зробити, додайте ім’я репозиторного класу до відображення:  

Анотації:

// src/AppBundle/Entity/Product.php
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="AppBundle\Entity\ProductRepository")
 */
class Product
{
    //...
}

YAML

# src/AppBundle/Resources/config/doctrine/Product.orm.yml
AppBundle\Entity\Product:
   type: entity
   repositoryClass: AppBundle\Entity\ProductRepository
   # ...

XML

<!-- src/AppBundle/Resources/config/doctrine/Product.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="AppBundle\Entity\Product"
        repository-class="AppBundle\Entity\ProductRepository">

        <!-- ... -->
    </entity>
</doctrine-mapping>

Doctrine спроможна згенерувати репозиторні класи, запустивши команду, яка вже раніше використовувалася для пропущених геттерів і сеттерів:

$ php app/console doctrine:generate:entities AppBundle

Тепер, додаємо метод findAllOrderedByName(), до щойно створеного репозиторного класу. Цей метод посилатиме запит до усіх класів Product, які розташовані в алфавітному порядку.

// src/AppBundle/Entity/ProductRepository.php
namespace AppBundle\Entity;

use Doctrine\ORM\EntityRepository;

class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery(
                'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
            )
            ->getResult();
    }
}

Доступ до entity manager можна отримати через метод $this->getEntityManager() з середини сховища.

Можете використати цей новий метод, як і раніше доступні пошукові методи за замовчуванням репозиторія (сховища).   

$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('AppBundle:Product')
    ->findAllOrderedByName();

При використанні custom repository class, у вас все ще залишиться доступ до методів find() та findAll().

Відношення між класами-сутностями

Уявімо, що усі продукти у вашому додатку належать до однієї категорії. У цьому випадку вам необхідний об’єкт Category і шлях, яким ви поєднаєте об’єкти Product і  Category. Розпочнемо зі створення класу Category. Так як ви вже знаєте, що врешті решт вам потрібно буде зберегти клас використовуючи Doctrine, дозвольте бібліотеці Doctrine створити його для вас.

$ php app/console doctrine:generate:entity --no-interaction \
    --entity="AppBundle:Category" \
    --fields="name:string(255)"

Це завдання створить для вас клас Category, з полями id та name, і відповідними функціями геттерів і сеттерів.

Метадані відображення зв’язків

Для того, щоб поєднати класи Category і Product, розпочнемо зі створення властивості  products у класі Category:

Анотації:

// src/AppBundle/Entity/Category.php

// ...
use Doctrine\Common\Collections\ArrayCollection;

class Category
{
    // ...

    /**
     * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
     */
    protected $products;

    public function __construct()
    {
        $this->products = new ArrayCollection();
    }
}

YAML

# src/AppBundle/Resources/config/doctrine/Category.orm.yml
AppBundle\Entity\Category:
    type: entity
    # ...
    oneToMany:
        products:
            targetEntity: Product
            mappedBy: category
    # don't forget to init the collection in the __construct() method
    # of the entity

XML

<!-- src/AppBundle/Resources/config/doctrine/Category.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="AppBundle\Entity\Category">
        <!-- ... -->
        <one-to-many
            field="products"
            target-entity="Product"
            mapped-by="category" />

        <!--
            don't forget to init the collection in
            the __construct() method of the entity
        -->
    </entity>
</doctrine-mapping>

Так як об’єкт Category буде поєднаним з багатьма об’єктами Product, додаємо властивість масиву products, щоб підтримувати об’єкти Product. Нагадаємо знову, ми не робимо це тому, що Doctrine потребує цього, але через те, що це має вагу у додатку для кожного об’єкту Category, який підтримує масив об’єктів Product.

Код у методі  __construct() є дуже важливим, тому що для Doctrine необхідно, щоб властивість  $products стала об’єктом ArrayCollection. Цей об’єкт виглядає і діє так само як і масив, але має більш гнучкі властивості. Не турбуйтесь, якщо вам не зручно, або незвично  з ними працювати. Просто уявіть, що це масив, і вам сподобається.

Значення targetEntity, у вище використаному декораторі, може посилатися на будь-який клас-сутність з дійсним заповнювачем, а не клас-сутність, який просто знаходиться у тому ж заповнювачі. Для того, щоб під’єднатися до класу-сутності, визначеному в іншому класі або пакеті, помістіть увесь заповнювач як значення targetEntity.

Ккожен клас Product може мати відношення до лише одного об’єкту Category, який ви додаватимете, як властивість $category, до класу Product:

Анотації:

// src/AppBundle/Entity/Product.php

// ...
class Product
{
    // ...

    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;
}

YAML

# src/AppBundle/Resources/config/doctrine/Product.orm.yml
AppBundle\Entity\Product:
    type: entity
    # ...
    manyToOne:
        category:
            targetEntity: Category
            inversedBy: products
            joinColumn:
                name: category_id
                referencedColumnName: id

XML

<!-- src/AppBundle/Resources/config/doctrine/Product.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="AppBundle\Entity\Product">
        <!-- ... -->
        <many-to-one
            field="category"
            target-entity="Category"
            inversed-by="products"
            join-column="category">

            <join-column name="category_id" referenced-column-name="id" />
        </many-to-one>
    </entity>
</doctrine-mapping>

Тепер, ми вже додали нову властивість до класів Category та Product, дайте команду Doctrine згенерувати геттери і сеттери:

$ php app/console doctrine:generate:entities AppBundle

А тепер, деякий час, не звертаймо уваги на метадані Doctrine. У вас уже є два класи - Category та Product, кожен з відношенням “один до всіх”. Клас Category містить масив об’єктів Product і об’єкт Product, який може містити один об’єкт Categorу. Іншими словами - ви збудували потрібні вам класи, саме так, належить. Той факт, що дані потрібно зберігати у базі даних - другорядний.

Тепер давайте подивимось на метадані, розміщені у рядку вище над властивістю $category, класу Product. Ця інформація дає команду Doctrine, що поєднаним класом є Category, і що цей клас повинен містити id категоріїї, записані у полі category_id, яке розміщене у таблиці product. Іншими словами, поєднаний об’єкт Category буде зберігатися у властивості $category, хоча, за лаштунками, Doctrine буде зберігати ці відношення, зберігаючи значення категорії id у колонці category_id таблиці product.

Метадані, розміщені над властивістю $products об’єкта Category, є менш важливими, вони просто дають Doctrine команду прочитати властивість Product.category для того, щоб визначити, як саме відображені відношення.

Перш ніж рухатися далі, перевірте чи ви дали команду Doctrine добавити нову таблицю з іменем category, колонку product.category_id, і новий зовнішній ключ:

$ php app/console doctrine:schema:update --force

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

Зберігаємо поєднані класи-сутності

Тепер ви можете побачити новий код у дії! Уявіть що ви знаходитесь прямо у контролері:

// ...

use AppBundle\Entity\Category;
use AppBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;

class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category->setName('Main Products');

        $product = new Product();
        $product->setName('Foo');
        $product->setPrice(19.99);
        $product->setDescription('Lorem ipsum dolor');
        // relate this product to the category
        $product->setCategory($category);

        $em = $this->getDoctrine()->getManager();
        $em->persist($category);
        $em->persist($product);
        $em->flush();

        return new Response(
            'Created product id: '.$product->getId()
            .' and category id: '.$category->getId()
        );
    }
}

Тепер, ми додали один рядок до таблиць category і product. Колонка product.category_id, створена для нового продукту, підходить для будь-якого id з нової категорії. Doctrine справляється із збереженням відношень, саме для вас.

Отримання поєднаних об’єктів

Коли вам потрібно отримати відповідні об’єкти, робочий процес виглядатиме точнісінько як і раніше. Спочатку отримайте об’єкт $product, а потім і доступ до об’єкту Category:

 public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->find($id);

    $categoryName = $product->getCategory()->getName();

    // ...
}

У цьому прикладі, спочатку даємо запит на об’єкт Product, що базується на id продукту. Це видасть запит лише для даних продукту і добавить ці дані в об’єкт $product. Пізніше, коли ви дасте команду на виконання $product->getCategory()->getName(), Doctrine непомітно дасть наступний запит, для того щоб знайти об’єкт Category, який пов’язаний з об’єктом Product. Це підготує об’єкт $category і поверне його вам.

 

Що важливо, ми маємо легкий доступ до категорій пов’язаних з продуктами, проте дані категорії фактично не добуваються, допоки ви не запитаєте про них.

Також можете дати запит у іншому напрямку:

 public function showProductsAction($id)
{
    $category = $this->getDoctrine()
        ->getRepository('AppBundle:Category')
        ->find($id);

    $products = $category->getProducts();

    // ...
}

У цьому випадку, спочатку ви даєте запит для одного об’єкта Category, і тоді Doctrine робить наступний запит для того щоб добути відповідні об’єкти Product, але лише тоді, коли ви також дасте запит на них (коли ви даєте запит на ->getProducts()). Змінна $products є масивом усіх об’єктів Product, які відносяться до об’єкта Category через їхнє значення category_id.

Відношення і проксі-класи

Таке так зване “ліниве завантаження” є можливим, оскільки Doctrine при необхідності повертає “проксі” об’єкт на місце істинного об’єкта. Давайте знову подивимось на попередній приклад.

$product = $this->getDoctrine()
    ->getRepository('AppBundle:Product')
    ->find($id);

$category = $product->getCategory();

// prints "Proxies\AppBundleEntityCategoryProxy"
dump(get_class($category));
die();

Цей проксі об’єкт розширює істинний об’єкт Category, і виглядає точнісінько як він. Різниця полягає у тому, що користуючись проксі-об’єктом, Doctrine може не відразу давати запит на справжні дані об’єкта Category, допоки вони не будуть потрібні додатку (наприклад, поки ви не дасте команду $category->getName()).

Проксі-класи генеруються Doctrine, і зберігаються у каталозі кеша. І хоча, ви можливо ніколи не звертали увагу що ваш об'єкт $category є фактично проксі-об’єкт, настав час це запам’ятати.

У наступному підрозділі, коли ви вже добудете одразу усі дані про продукт і категорію, Doctrine поверне істинний об’єкт Category, тому що нам не потрібно, щоби будь-які елементи “ліниво завантажувались”.

Об’єднання з поєднаними записами

У прикладах, згаданих вище, зроблені два запити - один для оригінального об’єкта (такого як Category) і один для об’єднаних об‘єктів (таких як Product objects).

Пам’ятайте, що ви можете побачити усі запити до БД, зроблені під час веб-запиту, за допомогою панелі налагодження.

Звичайно, якщо ви відразу знаєте, що вам потрібно для того щоб отримати доступ до обох об’єктів, ви можете уникнути наступного запиту створивши об’єднання в вихідному запиті. Давайте додамо наступний метод до класу ProductRepository:

// src/AppBundle/Entity/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
    $query = $this->getEntityManager()
        ->createQuery(
            'SELECT p, c FROM AppBundle:Product p
            JOIN p.category c
            WHERE p.id = :id'
        )->setParameter('id', $id);

    try {
        return $query->getSingleResult();
    } catch (\Doctrine\ORM\NoResultException $e) {
        return null;
    }
}

Тепер, ви можете побачити цей метод у вашому контролері, дати запит на об’єкт Product, а також і на об’єднаний об’єкт Category лише одним запитом:

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AppBundle:Product')
        ->findOneByIdJoinedToCategory($id);

    $category = $product->getCategory();

    // ...
}

Більше інформації стосовно об’єднань

Цей підрозділ був вступом до звичайного типу відношень між класами-сутностями, а саме відношення один-до-багатьох. Якщо ви бажаєте прочитати про це детальніше, і знайти більше прикладів як використовувати інші типи відношень (наприклад, один-до-одного, багато-до-багатьох), перейдіть за посиланням.

Якщо ви користуєтеся анотаціями, тоді вам слід написати ORM\  перед усіма анотаціями, наприклад, ORM\OneToMany, який не висвітлений у документації Doctrine.

Також потрібно включити команду use Doctrine\ORM\Mapping as ORM;, яка імпортує анотаційний префікс ORM.

Налаштування

Doctrine досить легко налаштовується, тому вам не потрібно турбуватися про більшу частину опцій. Для того, щоб дізнатися більше про налаштування, читайте підрозділ Config reference.

Lifecycle Callbacks

Деколи вам потрібно виконати дію безпосередньо перед тим, або після того як ви вставили, оновили, або видалили клас-сутність. Ці типи дій відомі як lifecycle callbacks, оскільки вони “викликають” методи, які потрібно вам добути під час стадій циклу класу-сутності (наприклад, вставити, оновити, видалити клас).

Якщо ви користуєтеся анотаціями для ваших метаданих, розпочніть використовувати цикли зворотнього виклику. Це не є необхідністю, якщо для відображення ви користуєтеся YAML або XML.

/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...
}

Тепер, ви можете дати команду Doctrine виконати метод на будь-яку доступну подію циклу. Наприклад, ви хочете встановити колонку з датами createdAt до поточної дати, після того, як клас-сутність вже збережений, тобто встановлений.

Анотації:

// src/AppBundle/Entity/Product.php

/**
 * @ORM\PrePersist
 */
public function setCreatedAtValue()
{
    $this->createdAt = new \DateTime();
}

YAML:

# src/AppBundle/Resources/config/doctrine/Product.orm.yml
AppBundle\Entity\Product:
    type: entity
    # ...
    lifecycleCallbacks:
        prePersist: [setCreatedAtValue]

XML:

<!-- src/AppBundle/Resources/config/doctrine/Product.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="AppBundle\Entity\Product">
        <!-- ... -->
        <lifecycle-callbacks>
            <lifecycle-callback type="prePersist" method="setCreatedAtValue" />
        </lifecycle-callbacks>
    </entity>
</doctrine-mapping>

У наведеному вище прикладі передбачається, що ви створили, і відобразили властивість createdAt, хоча це і не показується.  

Тепер, перед тим як зберегти клас-сутність, Doctrine автоматично дасть команду на виконання методу, який і налаштує поточну дату поля createdAt.  

Існує декілька інших циклів, які ви можете підключити. Для того, щоб дізнатися більше інформації стосовно lifecycle callbacks, або просто загальну інформацію читайте розділ Lifecycle Events Documentation.

Lifecycle Callbacks та Event Listeners

Зверніть увагу, що метод setCreatedAtValue() не отримує ніяких аргументів. Цей випадок існує для циклів (lifecycle callbacks) і він є “навмисним”: lifecycle callbacks повинні бути простими методами, які повинні внутрішньо змінити дані у класі-сутності (наприклад, встановити створене чи оновлене поле, згенерувати значення slug.

Якщо вам потрібно виконати складніші дії, наприклад логування або надсилання електронного листа, вам слід зареєструвати зовнішній клас, як  event listeners або subscriber і надати доступ до ресурсів, які вам потрібні. Щоб дізнатися більш детальну інформацію, читайте у довідник: How to Register Event Listeners and Subscribers.

Типи полів у Doctrine

Doctrine містить велику кількість доступних полів. Кожне з яких збігається з типом даних PHP і відповідним типом колонки в будь-якій базі даних, яку ви використовуватимете. Для кожного типу поля, ви можете нлаштувати Column, змінивши довжину, ім’я або інші параметри. Список усіх доступних типів полів, ви знайдете у розділі Mapping Types Documentation.

Підсумок

Разом з Doctrine, ви можете звернути увагу на об’єкти і на те, як вони використовуються у додатку і лише секунду потурбуватися про збереження бази даних. Це все тому, що  Doctrine дозволяє вам користуватися будь-якими PHP об’єктами, для підтримання даних і покладається на відображення інформації метаданих, для відображення даних об'єкта у конкретній таблиці бази даних.

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

Дізнавайтесь більше

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

How to use Doctrine Extensions: Timestampable, Sluggable, Translatable, etc.

Console Commands

DoctrineFixturesBundle

DoctrineMongoDBBundle

 

 

 

 

 

 
 

 
 
 
 
 
 
 

 

 

Поділитися