Заметки WEB-разработчика

Полезные материалы для web-разработки

Doctrine 2 в Zend Framework 2. Примеры использования запросов.

В этом посте я покажу самые частые примеры использования запросов доктрины (doctrine 2) в Zend Framework 2. Пост будет постепенно дополняться. Сам я только начал изучать Доктрину, так что возможно вы заметите некие неточрности. О сих пишите в коментах

Doctrine 2 в Zend Framework 2. Примеры использования запросов.

Методы которые мы будем использовать

  • $em = $this->container->getDoctrine_ORM_EntityManagerService(); таким образом используя DI Container мы получаем доступ к EntityManager
  • $em->getRepository() — так мы получаем хранилище объектов определенного типа, то есть некий аналог *Table из Doctrine 1.x.
  • $em->persist($link); — так сохраняем объект
  • $em->flush() так завершаем транзакцию, то есть реально отправляем данные в БД.
  • $em->remove($link) — ну а так удаляем объект.

Выборка [ SELECT ]

GetRepository(...) + find(...)

public function find($id, $lockMode = null, $lockVersion = null)

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

$em = $this->getEntityManager(); //Получение связи с менеджером Сущщностей.
$message = $em->getRepository('\Entities\Notes')->find($noteId);
// или
$message = $em->find(''\Entities\Notes'', $noteId);

Что я с помощью этого нехитрого кода хотел получить и что я получил? Нужна была запись из таблицы «\Entities\Notes» c noteId, равному $noteId. Получено: в переменную $message один объект (в случае, если поле noteId — уникально).

Как это использовать? Получаю текст сообщения: $text = $message->getText(); Конечно же при наличии данных в таблице и присутствии поля «text» у этой сущщности.

GetRepository(...) + findBy(...)

public function findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)

Для нахождения записи используется один и более критериев.

$em = $this->getEntityManager(); //Получение связи с менеджером Сущщностей.
$message = $em->getRepository('\Entities\Clients')->findBy(array('name'=>'Ivan', 'surname'=>'Иванов'));

Что нам дает использование нескольких критериев (т.е. FindBy(...))? Правильно — поиск данных, удовлетворяющих нескольки критериям. Только теперь я могу получить более одного результата — массив объектов.

GetRepository(...) + findOneBy(...)

public function findOneBy(array $criteria, array $orderBy = null)

Действует аналогично предыдущему примеру, но вернет в любом случае одну запись (один объект).

GetRepository(...) + findByPropertyName(...)

// remoteGuid - название свойства в модели
$currency = $em->getRepository('Ext1C\Model\VocabCurrency')->findByRemoteGuid('ru');

Выборка по свойству, где 'ru' - его значение

GetRepository(...) + findAll(...)

$users = $em->getRepository('User')->findAll();

QueryBuilder. Пример А. Простая выборка.

$em = $this->getEntityManager();
$notes = $em->createQueryBuilder()
->select('n')
->from('Entities\Notes', 'n')
->where('n.deletedAt IS NULL')
->orderBy('n.createdAt', 'DESC')
->getQuery()->getResult();

Сгенерированный SQL:

SELECT n0_.noteId AS noteId0, n0_.typeId AS typeId1, n0_.objectId AS objectId2, n0_.objectTypeId AS objectTypeId3, n0_.title AS title4, n0_.text AS text5, n0_.cuttedText AS cuttedText6, n0_.keywords AS keywords7, n0_.commentsCount AS commentsCount8, n0_.viewsCount AS viewsCount9, n0_.raiting AS raiting10, n0_.isActive AS isActive11, n0_.createdAt AS createdAt12, n0_.updatedAt AS updatedAt13, n0_.deletedAt AS deletedAt14, n0_.expiredAt AS expiredAt15, n0_.userId AS userId16, n0_.noteId AS noteId17, n0_.noteId AS noteId18 FROM notes n0_ WHERE n0_.deletedAt IS NULL ORDER BY n0_.createdAt DESC

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

Тут мы берем просто берем все записи, кроме тех, которые были «мягко-удалены». Поясню: SoftDelete — метод, когда вместо физического удаления данных мы используем признак того, что запись была удалена, в нашем случае — этот признак хранится в поле «deletedAt». Сортировочка — как видно, по дате создания. Что за последняя строка? — получение результатов. В последующих примерах я буду использовать именно такой метод. О других методах гидрации (формата полученных данных) напишу чуть ниже.

QueryBuilder. Пример Б. Выборка посложне, но тоже простая, но с явным заданием параметров.

$notes = $em->createQueryBuilder()
->select('n')
->from('Entities\Notes', 'n')
->where('n.deletedAt IS NULL')
->andWhere('n.noteId =:noteId')
->setParameter('noteId', $noteId)
->getQuery()->getResult();

Сгенерированный SQL:

SELECT n0_.noteId AS noteId0, n0_.typeId AS typeId1, n0_.objectId AS objectId2, n0_.objectTypeId AS objectTypeId3, n0_.title AS title4, n0_.text AS text5, n0_.cuttedText AS cuttedText6, n0_.keywords AS keywords7, n0_.commentsCount AS commentsCount8, n0_.viewsCount AS viewsCount9, n0_.raiting AS raiting10, n0_.isActive AS isActive11, n0_.createdAt AS createdAt12, n0_.updatedAt AS updatedAt13, n0_.deletedAt AS deletedAt14, n0_.expiredAt AS expiredAt15, n0_.name AS name16, n0_.userId AS userId17, n0_.noteId AS noteId18, n0_.noteId AS noteId19 FROM notes n0_ WHERE (n0_.deletedAt IS NULL) AND (n0_.noteId = ?)

Что изменилось? Добавился метод andWhere c параметрами вида: a.field =:p, где a — alias(псевдоним таблицы), field — поле в таблице a, p — параметр. Таким образом строятся и более сложные запросы, например

для select:

В предыдущих примерах выполнялось по два запроса - один для исходного объекта (например, Category) и один для связанного (например, объекты Product).
Совет
Конечно, если заранее известно что будет необходим доступ к обоим объектам, то можно избежать второго запроса, используя join в исходном запросе. Добавьте следующий метод к классу ProductRepository:

$qb = $em->createquerybuilder()
->select('псевдоним, псевдоним2')
->from('[сущщность]', 'псевдоним')
->leftjoin('псевдоним.поле_по_которому_джойним', 'псевдоним2', 'with', 'псевдоним.поле = псевдоним2.поле and псевдоним2.поле = '. какой-то параметр)
->where('псевдоним.поле1 =: параметр1')
->andwhere('псевдоним.поле2 =: параметр2')
->andwhere('псевдоним.поле3 is null')
->setparameters(array('параметр1' => значение параметра1, 'параметр2' => значение параметра2))
->orderby('псевдоним.поле по которому сортируем', 'тип сортировки');

Объединение со связанными записями

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

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

Criteria

ex. 1

$criteria = new \Doctrine\Common\Collections\Criteria();
// >
$criteria->where($criteria->expr()->gt('price', 200));
$result = $entityRepository->matching($criteria);

ex. 2

$criteria = new \Doctrine\Common\Collections\Criteria();
// ==
$criteria->where($criteria::expr()->eq('lot', $lotsModel));
$criteria->andWhere($criteria::expr()->in('customer', $customers['ids']));
$lotCustomersModel = $em->getRepository('Ext1C\Model\LotCustomers')->matching($criteria);
// количество итемов
$count = $lotCustomersModel->count();

Нативный запрос

$em = $this->getDoctrine()->getEntityManager();
$stmt = $em->getConnection()->prepare('SELECT id FROM procedure_steps WHERE lot_id = :lot_id AND procedure_id = :procedure_id);
        $stmt->bindValue('lot_id', $lot_id);
        $stmt->bindValue('procedure_id', $procedure_id);
        $stmt->execute();
        $result = $stmt->fetchAll();

Изменение [ UPDATE / EDIT ]

Типичный пример

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

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

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

    return $this->redirect($this->generateUrl('homepage'));
}

Обновление объекта включает три шага:

-получение объкта из Doctrine;
-изменение объекта;
-вызов flush() из entity manager

Заметьте, что в вызове $em->persist($product) нет необходимости. Вспомните, что этот метод лишь сообщает Doctrine что нужно управлять или “наблюдать” за объектом $product. В данной же ситуации, т. к. объект $product получен из Doctrine, он уже является управляемым

Update через QueryBuilder

$this->getEntityManager()->createQueryBuilder()
->update(СУЩЩНОСТЬ, 'псевдоним')
->set('псевдоним.поле1, значение1)
->set('псевдоним.поле2', 'null')
->where('псевдоним.поле3 =: параметр3')
->setParameters(array('параметр3' => значение3))
->getQuery()->execute();

Нативный запрос

$sql = "UPDATE lots SET current_step = $current_step_id WHERE id = $lot_id";
        $stmt = $em->getConnection()->prepare($sql);
        $stmt->execute();

Сохранение [ SAVE ]

Простой пример

$product = new Product();
$product->setName('A Foo Bar');
$product->setPrice('19.99');
$product->setDescription('Lorem ipsum dolor');

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

Метод persist() сообщает Doctrine команду на “управление” объектом $product. Она не вызывает создание запроса к базе данных (пока). Когда вызывается метод flush(), Doctrine просматривает все объекты, которыми она управляет, чтобы узнать, надо ли сохранить их в базу данных. В этом примере объект $product ещё не был сохранён, поэтому entity manager выполнит запрос INSERT и будет создана строка в таблице product. Фактически, т. к. Doctrine знает обо всех управляемых сущностях, когда вызывается метод flush(), она прощитывает общий набор изменений и выполняет наиболее эффективный и возможный запрос или запросы. Например, если сохраняется 100 объектов Product и впоследствии вызывается flush(), то Doctrine создаст единственное подготовленное выражение и повторно использует его для каждой вставки. Этот паттерн называется Unit of Work и используется потомучто быстр и эффективен.

Сохранение связанных сущностей

Пример 1

$contragentsModel = new \Ext1C\Model\Contragents();
$contragentsModel->setStatus(3);
$supplierProfilesModel = $this->_entityManager->find('\Ext1C\Model\SupplierProfiles', 1);

$contragentsModel->setSupplierProfile($supplierProfilesModel);
$this->_entityManager->persist($contragentsModel);
$this->_entityManager->flush();

Пример 2

$category = new Category();
$category->setName('Main Products');

$product = new Product();
$product->setName('Foo');
$product->setPrice(19.99);
// Связывает этот продукт с категорией
$product->setCategory($category);

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

Итак, одна строка добавлена в таблицы category и product. В столбец product.category_id для нового продукта установлен тот id, который соотвествует новой категории. Doctrine осуществляет сохранение этой связи для вас.

Удаление [ DELETE ]

$em = $this->getDoctrine()->getEntityManager();
$product = $em->getRepository('AcmeStoreBundle:Product')->find($id);
$em->remove($product);
$em->flush();

Остальоне

Получение id последней вставленной записи

$widgetEntity = new WidgetEntity();
$entityManager->persist($widgetEntity);
$entityManager->flush();
$widgetEntity->getId();

Просмотр запроса

use Doctrine\DBAL\Logging\EchoSQLLogger
// ...
$this->entityManager->getConnection()->getConfiguration()->setSQLLogger(new EchoSQLLogger());

Join-Columns with non-primary keys

Невозможна. Любая сущность должна иметь primary key и связь таблиц невозможна по ключу не являющимся primary key у привязываемой таблицы

Комментарии

Комментарии через Вконтакте