Збільшення продуктивності проекту в Symfony2 за допомогою Doctrine2 ORM

Я намагався написати цей посібник по Doctrine 2 ORM протягом тривалого часу, але ніяк не міг взятися за це. Нарешті, я зібрався з думками і зробив це. Тож я ділюся кількома техніками роботи з Doctrine2 ORM, які допоможуть покращити продуктивність сайту на Symfony2(точніше, будь-якого сайту, що використовує Doctrine2 ORM). Я створив проект і виклав його на GitHub як візуальний посібник, щоб кожен міг перевірити мої слова на ділі.

1. Завантаження всіх необхідних зв'язків

Я вважаю, що цей метод є найефективнішим. Ось чому він займає перше місце.

Припустимо, у нас є 5 авторів і їх 100 постів, і нам потрібно відобразити всі пости з іменами авторів на цій сторінці.

Шаблон:

    {% for post in posts %}
    <article><h2>{{ post.title }}</h2><p>Автор: {{ post.author.firstName }} {{ post.author.lastName }} створено {{ post.createdAt | date('d-m-Y H:i') }}</p><p>{{ post.text }}</p></article>
    {% endfor %}

Що може бути простіше, скажете ви, і зробите щось на кшталт цього:

$posts=$this-&gt;getDoctrine()-&gt;getRepository('AcmeDemoBundle:Post')-&gt;findAll();

Але якщо ви подивитеся на профайлер (Symfony Profiler Toolbar), ви побачите цю тривожну інформацію:

doctrine 2 query

Звідки стільки запитів до бази даних, якщо ми зробили лише один запит?

Справа в тому, що Doctrine не завантажує за замовчуванням зв'язки сутностей, і коли ми звертаємося до них, видається новий запит за цими даними. Ось чому у нас є ще 5 запитів для авторів постів.

Щоб оптимізувати запит Doctrine 2 ORM, нам потрібно використовувати JOIN і додати необхідні сутності до вибірки. Ось оптимізований код методу репозиторію для отримання списку постів.

/**
* Знайти всі пости з авторами
*
* @return array | Post[]
*/publicfunction findAllPostsAndAuthors(){$qb=$this-&gt;createQueryBuilder('p');$qb-&gt;addSelect('a')-&gt;innerJoin('p.author','a');
 
    return$qb-&gt;getQuery()-&gt;getResult();}

Використовуючи цей метод для отримання списку постів, ми отримаємо таку картину в профайлері:

Збільшення продуктивності проекту в Symfony2 за допомогою Doctrine2 ORM

Тепер у нас залишився лише один запит. Час виконання його зменшився в порівнянні з попередньою версією.

2. Оновлення кількох сутностей за запитом

Припустимо, тепер нам потрібно оновити дату створення всіх наших постів. Часто це робиться шляхом отримання всіх постів з бази даних (згідно з документацією Doctrine 2) і потім оновлення кожної сутності Doctrine 2 окремо в циклі:

$newCreatedAt=new \DateTime();$posts=$this->getDoctrine()->getRepository('AcmeDemoBundle:Post')->findAll();
 
/** @var Post $post */foreach($postsas$post){$post->setCreatedAt($newCreatedAt);}$this->getDoctrine()->getManager()->flush();

В результаті, ми маємо наступні значення в профайлері після оновлення 100 постів:

doctrine 2 queries

Як ми можемо побачити, до бази даних було направлено аж 103 запити. Навіть профайлер виділив їх жовтим кольором. Це вже має вас насторожити і змусити задуматися про те, як підвищити продуктивність сайту Symfony2.

Чому так багато запитів? Оскільки ми оновлювали сутності в циклі, ми отримали велику кількість запитів UPDATE до бази даних.

Для оптимізації нам потрібно написати лише один запит для оновлення всіх записів у базі даних.

/**
* Оновити дату створення для всіх постів
*
* @param \DateTime $newCreatedAt
*
* @return int
*/publicfunction updateCreatedAtForAllPosts(\DateTime $newCreatedAt){$qb=$this->createQueryBuilder('p');$qb->update()->set('p.createdAt',':newCreatedAt')->setParameter('newCreatedAt',$newCreatedAt);
 
    return$qb->getQuery()->execute();}

Після цього ми отримаємо лише один запит до бази даних і також зменшимо час виконання.

Increasing project productivity in Symfony2 from Doctrine2 ORM

3. Відмова від гідратації Doctrine 2

Що таке гідратація? Гідратація — це перетворення масиву в об'єкт і назад.

Гідратація є найвитратнішою за часом і пам'яттю ORM.

Саме тому, коли ми отримуємо велику кількість даних з бази даних лише для відображення, наприклад, при отриманні списку, гідратація всього в об'єкти сутностей буде дуже затратною. Краще отримати дані у вигляді асоціативного масиву та зекономити ресурси.

$posts=$this->createQueryBuilder('p')->getQuery()->getArrayResult();

4. Використання проксі-ссилок

Припустимо, нам потрібно додати зв'язок з автором до поста, і в нас є ID автора. В більшості випадків це робиться шляхом отримання сутності автора з бази даних за його ID, а потім простим встановленням зв'язку з постом автора:

$author=$this->getDoctrine()->getRepository('AcmeDemoBundle:Author')->find($autorId);
 
$post=new Post();$post->setAuthor($author);

Як виявилось, отримання автора з бази даних є зайвим запитом, і його дуже легко відкинути. Для цього є проксі-ссилки Doctrine2. Ось як це працює:

$em=$this->getDoctrine()->getManager();
 
$post=new Post();$post->setAuthor($em->getReference('Acme\DemoBundle\Entity\Author',$authorId));

Таким чином, ми позбулися зайвого запиту і встановили зв'язок лише з ID автора.

5. Використання панелі профайлера Symfony

Хоча це останній пункт, цей рада не є менш важливою. Постійний контроль за тим, що відбувається в профайлері, безумовно, допоможе вам розробити ефективні проекти на Symfony2. Завдяки профайлеру ви завжди будете відстежувати свої запити до бази даних і зможете оптимізувати їх вчасно для підвищення продуктивності сайту в Symfony 2.

Дізнатися більше про архітектуру Doctrine 2 ви можете тут.

Stfalcon.com запрошує вас до співпраці!