Zend Frameworkdoctrine2 в zendFramework

если кому нужно:
www.doctrine-project.org/projects/orm/2.0/docs/en — ссылочка на английскую доку.

Теперь все по-другому(чем в Doctrine предыдущих версий).
Doctrine 2 использует namespaces — т.е. Вам нужен php минимум версии 5.3.0

Приступим:
Создание сущщностей и генерирование методов для них, генерация таблиц.
Для того, чтоб нужные мне сущщности лежали там, где нужно мне — в doctrine.ini пишу:

resources.doctrine2.entity.namespace = «Entities»
resources.doctrine2.metadata.entitiesPaths.Entities = APPLICATION_PATH «/data/Entities/»

(папка с сущщностями: ./application/data/Entities)

В Doctrine2 мы в начале пишем php-файл с аннотациями (комментариями) для Entity, после — генерим для нее методы. Все описания полей — в комментариях — свойства @Column.

Свойства для @Column:
Необходимое — тип столбца. «type»
Принимает значения: (string, smallint, integer, decimal, datetime, ...)
Например:
* @Column(type=«string»)

Заметьте, при использовании типа DateTime — нужно вносить/считывать значение как объект. (Например: $this->setCreatedAt(new \DateTime());

Опционально:
«name» — имя — по умолчанию имя свойства для названия столбца базы данных, кроме того, позволяет определить имя столбца.
«length» — Используется типа «string» — определение его максимальной длины в базе данных.
«precision», «scale»- точность — применяется только для десятеричных типов столбцов.
«unique» — логическое значение, указывает на то, что значение столбца должно быть уникальным
«nullable» — определяет, готово ли значение в базе быть равно «null»
«columnDefinition» — позволяет использовать расширенные функции RMDBS.

Свойства для @ GeneratedValue (определяет, какие методы используются для идентификатора экземпляра переменной, которая аннотриуется с помощью @Id и используется в сочетании с ним). Может принимать значения: AUTO, SEQUENCE, TABLE, IDENTITY, NONE.

Например, для того, чтобы получить autoIncremet следует использовать:
* @GeneratedValue(strategy=«IDENTITY»)
Если не указано ничего — по умолчанию испльзуется NONE

@HasLifecycleCallbacks:
Позволяет использовать функции call-back:
@PostLoad — выполняется после загрузки данных.
@PrePersist — выполняется перед созданием сущщности.
@PostPersist — выполняется после создания сущщности.
@PreRemove — выполняется перед удалением сущщности.
@PostRemove — выполняется после удаления сущщности.
@PreUpdate — выполняется перед обновлением сущщности.
@PostUpdate — выполняется после обновления сущщности.

Например, хочу записать в колонку createdAt дату и время создания данной записи:
/** @PrePersist */
public function prePersist() {

$this->setCreatedAt(new \DateTime());
}
этим самым получая дату и время в нужной мне колонке для текущей записи.

Простой пример нескольких Entity:

<?php
namespace Entities\MyModule;
/**
* Notes
*
* @Table(name=«notes»)
* @Entity @HasLifecycleCallbacks
*/
class Notes {
/**
* @Id
* @GeneratedValue(strategy=«AUTO»)
* @Column(type=«integer»)
*/
private $noteId;

/**
* @OneToOne(targetEntity=«Entities\MyModule\Clients»)
* @JoinColumn(name=«userId», referencedColumnName=«clientID»)
*/
private $Clients;
}

namespace Entities\MyModule;
/**
* Clients
* @Table(name=«clients»)
* @Entity
*/
class Clients {
/**
* @Column(type=«integer»)
* @Id
* @GeneratedValue(strategy=«IDENTITY»)
*
*/
private $clientId;

/**
* @Column(type=«string»)
*/
private $email;
}

Пояснения:
Поле $Clients — связь «один к одному»с сущщностью «Clients», таким образом можно будет достучаться ко всем свойствам сущщности, что будет рассмотрено ниже[*].

Связи:
Хочу отметить, что существует три типа связей: Undirectional — однонаправленные, Bidirectional — двунаправленные и Self-referencing
Что это значит?
Undirectional используется тогда, когда связь из таблицы А в талицу Б нужна, а наоборот — нет. Экономим ровно половину ресурсов.
Bidirectional — когда нужна связь в обоих направлениях.

— в сущщности «Notes»
@OneToOne(targetEntity=«Clients», mappedBy=«clientId»)

— в сущщности «Clients»
* @OneToOne(targetEntity=«Notes»)
* @JoinColumn(name=«userId», referencedColumnName=«noteId»)
private $customer;

Self-referencing — связь, которая не нуждается в ссылках.

Генерирование:
заходим в папочку data и:
./doctrine orm:generate-entities.
точка — генерим «сюда» (папочка ./application/data)

Такая комманда сгененрирует для нас методы get для всех свойств и методы set для доступных, стоит заметить, что при внесении руками изменений в методы генератор методов обойдет изменения и сгенерит то, что нужно сгенерить, не задев того, что мы там навносили (методы, которые изменены либо созданы нами).

Консоль
Вообще консоль, выполнив из /application/data комманду ./doctrine позволяет нам проделать следующее:

dbal
:import импортировать sql напрямую в базу данных
:run-sql выполнить произвольный sql запрос из консоли
orm
:convert-d1-schema конвертировать базу из 1.х формата в формат 2.х
:convert-mapping конвертировать информацию о mapping
:ensure-production-settings проверка правильности настроек doctrine для конкретной среды
:generate-entities сгенерировать классы и методы
:generate-proxies сгенерировать прокси
:generate-repositories сгенерировать хранилище классов
:run-dql выполнить произвольный dql запрос из консоли
:validate-schema проверить правильность исходных данных для генерации
orm:clear-cache
:metadata очистить всю кешированную информацию
:query очистить кеш запросов
:result очистить кеш результатов
orm:schema-tool
:create сгенрировать таблицы из php файлов в базу данных
:update обновить таблицы в базе данных

Итак, пример сгенеренного кода для «Notes»:
class Notes {

/**
* Get noteId
*
* @return integer $noteId
*/
public function getNoteId()
{
return $this->noteId;
}

/**
* Set Clients
* @param Entities\Passport\Clients $clients
*/
public function setClients(\Entities\MyModule\Clients $clients)
{
$this->Clients = $clients;
}

/**
* Get Clients
* @return Entities\MyModule\Clients $clients
*/
public function getClients()
{
return $this->Clients;
}

}
и для «Clients»:
class Clients {

/**
* Get clientID
*
* @return integer $clientId
*/
public function getClientId()
{
return $this->clientId;
}

/**
* Set email
*
* @param string $email
*/
public function setEmail($email)
{
$this->email = $email;
}

/**
* Get email
*
* @return string $email
*/
public function getEmail()
{
return $this->email;
}

}
Таким образом, все поля сущщности приватны и изменения либо изъятие значения осуществляется посредством методов.

Отмечу, что для свойства для полей с аннотациями по типу:
* @Id
* @GeneratedValue(strategy=«AUTO»)
или
* @Id
* @GeneratedValue(strategy=«IDENTITY»)
не генерируются, так как являются ключевыми,

Теперь можно и таблчики сгенерить/обновить

./doctrine orm:schema-tool:generate //генерим, если таковых нету
./doctrine orm:schema-tool:update //обновляем, если таковые имеются

Конечно же для этого в doctrine.ini должны иметься записи:
resources.doctrine2.connection.host = «[IP_ХОСТА]»
resources.doctrine2.connection.dbname = «[ИМЯ_БАЗЫ_ДАННЫХ]»
resources.doctrine2.connection.user = «[ИМЯ_ПОЛЬЗОВАТЕЛЯ]»
resources.doctrine2.connection.password = «[ПАРОЛЬ_ПОЛЬЗОВАТЕЛЯ]»

Генерация запросов
Надеюсь, совместными усилиями мы получили необходимые поля, связи, методы, таблицы. Попробуем использовать методы Doctrine2 для изъятия/изменений данных.

Обусловлюсь, что тут я привожу лишь примеры, так как выкладывать полный текст кода для сущщностей, а также контроллеров, вьювов и менеджеров не вижу логичным, надеюсь, Вы разберетесь, исходя их этих самых примеров, с этими моментами для своего частного случая.

Итак, самое простое извлечение данных:
GetRepository(...)+Find(...)
Для нахождения записи используется только один критерий, причем ключевой.

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

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

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

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

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

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

Плавно подбираемся к 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».
Сортировочка — как видно, по дате создания.
Что за последняя строка? — получение результатов. В последующих примерах я буду использовать именно такой метод. О других методах гидрации (формата полученных данных) напишу чуть ниже.

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

$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:

$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('псевдоним.поле по которому сортируем', 'тип сортировки');

или для update

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

Транзакции

Для безопасности вносимых данных советую использовать транзакции.
Транза?кция (англ.Transaction) — в информатике, группа последовательных операций, которая представляет собой логическую единицу работы с данными. Транзакция может быть выполнена либо целиком и успешно, соблюдая целостность данных и независимо от параллельно идущих других транзакций, либо не выполнена вообще и тогда она не должна произвести никакого эффекта. Транзакции обрабатываются транзакционными системами, в процессе работы которых создаётся история транзакций.

Как это реализуется?

$this->em->getConnection()->beginTransaction();
try{
$message = new [СУЩЩНОСТЬ];

$message->setParentId($parentId);
$message->setIsRead($recipientId);
$message->setSubject(strip_tags($subject));
$message->setMessage(strip_tags($messageText));

$this->em->persist($message); //добавить в очередь
$this->em->flush(); //выполнить очередь

$this->em->getConnection()->commit(); //выполнить транзакцию
return true;
}catch(Exception $e){
$this->em->getConnection()->rollback(); //откатить изменения
}
return false;

Гидрирование результатов

после getQuery() можно использовать:

getResult(): Возвращает коллекцию объектов. Результат: либо простой
набор объектов (чистый) либо массив объектов.
getSingleResult (): Возвращает один объект. Если результат содержит больше, чем один объект — исключение.
getArrayResult (): Возвращает массив, граф (вложенный массив) результатов;
getResultList(): только для чтения могут быть и массивы и объекты.
getScalarResult(): возвращает набор скалярных значений, которые могут содержать повторяющиеся данные.
getSingleScalarResult(): Возвращает одно скалярное значение, возвращаемое СУБД.
getSQL(): Возвращает sql код запроса

есть и еще, но рассматривать тут я их не буду.
ну, если кого заинтересует — еще всяких штук попишу.
Всем спасибо за внимание
  • +1
  • aimodify
  • 28 октября 2010, 18:57

Комментарии (4)

  • avatar
  • Guf
  • 28 октября 2010, 19:13
  • #
  • 0
каша, а не пост…
Кашу-то на вкус попробовали? Или с оберткой в урну?
За критику спасибо, поправил, буду стараться.
Для первого поста, нормально.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.