Посему за сеансом «просветления в Дао» я обратился лично у вышеупомянутому Джэйсону, и получил ответы на некоторые вопросы. (Хотя один из у меня разрешить так и не удалось, но об этом чуть позже).
Данная статья покрывает такие функции xPDO:
→ $xpdo->newQuery();
→ new xPDOCriteria();
→ $xpdo->getOne;
→ $xpdo->getMany;
→ $xpdo->getObjectGraph;
→ $xpdo->getCollectionGraph;
→ $xpdo->getCount;
→ незадокументированные методы создания и отладки запросов.
→ а также не упомянутые ранее вопросы «облегченного» удаления записей в БД.
Итак, в первом топике мы на «УРА» отработали простейшие запросы. Да вот незадача — они покрывают лишь частные случаи и зачастую неэффективны в плане расхода ресурсов.
В обычном SQL мы привыкли делать ограничения по выборке и т.д. Здесь на сцену выходит функция $xpdo->newQuery
// Простой пример newQuery <?php $query= $xpdo->newQuery('Actor', array('actor_id:>' => 2)); $query->limit(10); $actors = $xpdo->getCollection('Actor',$query); foreach ($actors as $actor) { echo $actor->first_name." "; } ?>
Объясняю, что здесь произошло. Мы выбрали 10 записей актеров с ИД больше 2.
Функция newQuery (офф. документация по функции) это по сути конструктор запроса в объектно ориентированной форме. Первым параметром обязательно принимает имя объекта модели (имя таблицы БД — если проще), а вторым некий [mixed $criteria].
КСТАТИ данный объект под общим именованием $criteria принимают в себя почти все функции xPDO. Очень важным для понимания я считаю тот факт, что $criteria это обширный круг понятий и сущностей.
Это как минимум может быть:
— прямое указание на значение первичного ключа (например getObject('Actor', 1);
)
— пары значений/ключей (например array('actor_id:>' => 2))
— объект newQuery (который сам может содержать вхождение критерии)
— объект xPDOCriteria (который сам может входить в newQuery)
— ассоциативный массив привязок (bindings)
— … и ещё… сам Ковард знает что (вобщем полная свобода действий)
Так вот $xpdo->newQuery('Actor', array('actor_id:>' => 2));
это укороченная запись равносильно эту строку можно записать двумя строками:
<?php $query= $xpdo->newQuery('Actor'); $query->where(array( 'actor_id:>' => 2) ); ?>
Не томя вам душу выкладываю, что у xPDO::newQuery есть следующие функции конструкторы запроса:
→ where
→ select
→ limit
→ sortby
→ groupby
→ innerJoin
→ leftJoin
→ rightJoin
→ andCondition
→ orCondition
→ setClassAlias
Так давайте же используем всё это на полную катушку… в диком, беспощадном и бессмысленном запросе :-)
<?php // сложный пример $query= $xpdo->newQuery('Actor'); $query->select('Actor.actor_id, Actor.last_name'); $query->where(array('actor_id:<=' => 20)); $query->andCondition(array('actor_id:!=' => 8)); $query->orCondition(array('actor_id:!=' => 7)); $query->sortby('actor_id','DESC'); $query->groupby('first_name'); $query->limit(10); $actors = $xpdo->getCollection('Actor',$query); foreach ($actors as $actor) { print_r($actor); } ?>
!NB Здесь хочу отметить тот, неразрешенный мною до сих пор глюк, у меня почему то отказывается работать getCollection при использовании $query->select, при $query->innerJoin (left и right тоже), и попытке засунуть в него сырой запрос с xPDOCriteria. Почему-то обрывается соединение с сервером по нехватке памяти. Консультации по вопросу мне не помогли, пробовал на двух локальных серверах. Есть подозрения, что это какой-то глюк моей тестовой Sakila DB. На форуме меня заверили, что это должно работать, я склонен этому верить :) на днях дойдёт дело до сложных запросов в одном проекте, который я начал делать на xPDO — попробую на нём и отпишусь ещё по этой теме.
Кстати для любопытных — тот мега конструктор создает такой запрос:
... WHERE ( `Actor`.`actor_id` <= ? AND `Actor`.`actor_id` != ? OR `Actor`.`actor_id` != ? )
А сейчас, господа, придержите ваши шляпы… дабы не произошел вынос мозга :) пишем вот такую вот конструкцию (чисто в тренировочных целях, не ищите смысл :) )
<?php $query= $xpdo->newQuery('Actor'); $query->where(array( array( 'actor_id:<=' => '20', array( 'OR:last_name:LIKE' => '%a%', 'AND:actor_id:>' => '5' ) ), 'actor_id:<=' => '30', 'OR:actor_id:>=' => '6', )); $query->limit(10); ?>
тоесть все Condition`ы можно запихнуть прямо в where() и получаем такой запрос на выходе
... WHERE ( ( `Actor`.`actor_id` <= ? OR ( `Actor`.`last_name` LIKE ? AND `Actor`.`actor_id` > ? ) ) AND `Actor`.`actor_id` <= ? OR `Actor`.`actor_id` >= ? )
Из этого следует, что все то что находится в первом входном массиве where() по умолчанию собирается вместе с AND (последний OR:actor_id: показывает, что «не по умолчанию» xPDO ставит то что вы укажите), вложенность запроса создается доп. вложением массива, двоеточия это разделители между именем столбца и оператором запроса. Ну думаю это интуитивно понятно.
Однако остановлюсь на логических операторах запросов.
Вложенность массивов здесь регулирует вложенность условий в запросе.
Вот такая запись 'actor_id:<=' => '30' обозначает все актёры с ИД меньше либо равно 30. И по аналогии элементарно. Пишем логический знак после двоеточия. По умолчанию, если ничего не указать считается знак "=".
Однако заметьте !
Если вы пишите логический указатель перед именем колонки типа 'OR:disabled:!=' => true, то нужно вручную указывать "=" обязательно.
Например 'AND:gender:=' => 'M'.
Как же узнать, какой запрос сгенерирован?
Вариант 1 — Вставить после формирования newQuery и перед самим запросом, например, getCollection вызов дебаггера.
$xpdo->setDebug(true);и в куче инфы, что вывалится на экран найти запрос (он там есть… и ошибки там отображаются).
Вариант 2 — Который мне нравится больше. Но он куда менее явный.
В том же месте, вместо дебага пишем эти строки
$query->prepare(); echo "RAW SQL : ".$query->toSQL();И получаем чистый SQL запрос, без кучи лишней информации.
Как задать произвольный «сырой» SQL запрос?
При помощи xPDOCriteria
$actorTable = $xpdo->getTableName('Actor'); $query= new xPDOCriteria($xpdo,' SELECT first_name, last_name FROM '.$actorTable.' WHERE actor_id = :index LIMIT 1 ',array( ':index' => 2, )); $actors = $xpdo->getObject('Actor',$query); print_r($actors);
Замечу сразу, что SELECT * не проходит. Нужно указывать поля явно! Здесь полный полёт фантазии запрос может быть абсолютно любой и соответсвенно вместо getObject может стоять getCollection.
Но здесь вы берёт всю ответственность за запрос и результаты на себя.
РАБОТА СО СВЯЗАННЫМИ ТАБЛИЦАМИ
Для того, чтобы делать запросы с JOIN`ами через объектные конструкторы (а не сырые запросы с помощью xPDOCriteria) вам нужно провести подготовительную работу со сгенерированной моделью базы данных. А именно указать связи. (документация по теме svn.modxcms.com/docs/display/xPDO20/Defining+Relationships).
Есть два типа зависимости Композиционная (Composite) и Агрегирующая (Aggregate). Подробно о них читайте в офф. доках. По идее agregate просто устанавливает «ни к чему не обязывающую связь», а composite — подразумевает автоматическое удаление связанных с ним строк, в случае удаления самого композициионной строки. Вобщем я в этих вопросах не специалист и делал всё по аналогии с мануалом — вроде работает :)
В нашем примере это будет так. Есть три таблицы Actor (с актёрами), Film (с фильмами), FilmActor (связующая таблица Актер_ИД = Фильм_ИД).
Откройте ваш файл со сгенерированной схемой БД (у меня это models/MyDBModel.mysql.schema.xml). И укажем связи. У меня это вышло так.
<?xml version="1.0" encoding="UTF-8"?> <model package="MyDBModel" baseClass="xPDOObject" platform="mysql" defaultEngine="MyISAM"> <object class="Actor" table="actor" extends="xPDOObject"><param name="wmode" value="opaque"></param> <field key="actor_id" dbtype="smallint" precision="5" attributes="unsigned" phptype="integer" null="false" index="pk" generated="native" /> <field key="first_name" dbtype="varchar" precision="45" phptype="string" null="false" /> <field key="last_name" dbtype="varchar" precision="45" phptype="string" null="false" index="index" /> <field key="last_update" dbtype="timestamp" phptype="timestamp" null="false" default="CURRENT_TIMESTAMP" extra="on update current_timestamp" /> <composite alias="FilmActor" class="FilmActor" local="actor_id" foreign="actor_id" cardinality="many" owner="local" /> </object> <object class="Film" table="film" extends="xPDOObject"><param name="wmode" value="opaque"></param> <field key="film_id" dbtype="smallint" precision="5" attributes="unsigned" phptype="integer" null="false" index="pk" generated="native" /> <field key="title" dbtype="varchar" precision="255" phptype="string" null="false" index="index" /> <field key="description" dbtype="text" phptype="string" null="true" /> <field key="release_year" dbtype="year" precision="4" phptype="string" null="true" /> <field key="language_id" dbtype="tinyint" precision="3" attributes="unsigned" phptype="integer" null="false" index="index" /> <field key="original_language_id" dbtype="tinyint" precision="3" attributes="unsigned" phptype="integer" null="true" index="index" /> <field key="rental_duration" dbtype="tinyint" precision="3" attributes="unsigned" phptype="integer" null="false" default="3" /> <field key="rental_rate" dbtype="decimal" precision="4,2" phptype="float" null="false" default="4.99" /> <field key="length" dbtype="smallint" precision="5" attributes="unsigned" phptype="integer" null="true" /> <field key="replacement_cost" dbtype="decimal" precision="5,2" phptype="float" null="false" default="19.99" /> <field key="rating" dbtype="enum" precision="'G','PG','PG-13','R','NC-17'" phptype="string" null="true" default="G" /> <field key="special_features" dbtype="set" precision="'Trailers','Commentaries','Deleted Scenes','Behind the Scenes'" phptype="string" null="true" /> <field key="last_update" dbtype="timestamp" phptype="timestamp" null="false" default="CURRENT_TIMESTAMP" extra="on update current_timestamp" /> <composite alias="FilmActor" class="FilmActor" local="film_id" foreign="film_id" cardinality="many" owner="local" /> </object> <object class="FilmActor" table="film_actor" extends="xPDOObject"><param name="wmode" value="opaque"></param> <field key="actor_id" dbtype="smallint" precision="5" attributes="unsigned" phptype="integer" null="false" index="pk" /> <field key="film_id" dbtype="smallint" precision="5" attributes="unsigned" phptype="integer" null="false" index="pk" /> <field key="last_update" dbtype="timestamp" phptype="timestamp" null="false" default="CURRENT_TIMESTAMP" extra="on update current_timestamp" /> <aggregate alias="Actor" class="Actor" local="actor_id" foreign="actor_id" cardinality="one" owner="foreign" /> <aggregate alias="Film" class="Film" local="film_id" foreign="film_id" cardinality="one" owner="foreign" /> </object> </model>
Теперь нужно запустить парсер схемы в модель. Если делать по моей прошлой стате, то это файл dbParser.php
Всё после того как вы пересобрали модель БД с нужными связями можно идти дальше.
Как сделать запрос со связанными таблицами (тобишь JOIN`ами)?
1-ый вариант не самый оптимальный
$actor = $xpdo->getObject('Actor',1); echo "Actor - ".$actor->first_name." ".$actor->last_name." "; $actors = $actor->getMany('FilmActor'); foreach ($actors as $actor) { echo "Film ".$actor->film_id; echo " / ".$actor->getOne('Film')->title." "; }
Делаем выборку актёра с ИД = 1, потом выбираем «много» строк из таблицы-связки, по сути это выбор всех ИДшников фильмов с актером.
А потом уже пройдемся по всем Фильмам и получим инфу по каждому фильму.
Думаю вы поняли, почему это не оптимально в данном случае — куча запросов, по одному на каждый getOne.
Получение всех связанных объектов сразу
Вот здесь выходит на сцену «жадный загрузчик» $xpdo->getCollectionGraph
$items = $xpdo->getCollectionGraph('FilmActor', '{"Film":{},"Actor":{}}', array('FilmActor.actor_id' => 1,)); //print_r($items); foreach($items as $item) { echo "FilmActor Actro_ID = ".$item->actor_id." "; echo "Film Title = ".$item->Film->title." ";
За один запрос мы получаем всю инфу об актере, и фильмах.
Кстати, чтобы сделать более расширенный запрос можно использовать getCollectionGraph с newQuery, например.
Вот так
<?php $c = $xpdo->newQuery('FilmActor'); $c->bindGraph('{"Film":{},"Actor":{}}'); $c->where(array( 'FilmActor.actor_id' => 1, )); $items = $xpdo->getCollectionGraph('FilmActor', '{"Film":{},"Actor":{}}', $c); ?>Новое здесь только bindGraph('{«Film»:{},«Actor»:{}}');
я честно говоря не понял как точно это работает, но такая запись в формате JSON нужна чтобы автоматически составить запрос со всеми связями. По аналогии сделать это легко для любых таблиц. Ну и понятное дело, что getObjectGraph() будет получать тоже самое, но лишь один экземпляр, то есть одну строку.
Подобные запросы можно сделать и без графов
$c = $xpdo->newQuery('FilmActor'); $c->innerJoin('Film','Film','Film.film_id = FilmActor.film_id'); $c->innerJoin('Actor','Actor','Actor.actor_id = FilmActor.actor_id'); $с->select('Film.film_id, Film.title, FilmActor.film_id, FilmActor.actor_id'); $c->where(array( 'FilmActor.actor_id' => 1, )); $items = $xpdo->getCollection('FilmActor',$c);
вот только если getCollectionGraph сам получает все связанные поля сразу и не реагирует на select(), то использование Join с newQuery требует использовать select() — иначе не будет выведено вобще ниодного поля.
Ну… и как маленькое дополнение и расслаление, после непонятных вещей :)
1) Функция считающая вхождения строк подходящих под данный критерий.
$total = $xpdo->getCount('Box',array( 'width' => 20, )); }
2) Фишка, позволяющая полученный объект перевести в формат массива
$actor->toArray()это антипод функции fromArray() (которая используется при создании объектов).
Кстати есть также функции toJSON() / fromJSON().
3) Также для удаления записей не обязательно их загружать и потом делать $item->remove();
Поидее можно делать $xpdo->removeObject('Table', $criteria) и $xpdo->removeCollection('Table', $criteria)
4) И напоследок конструкция, скорее памятка мне. Как можно обойтись без getCollection(), которая у меня глючит. За сие личный поклон гуру-отцу-основателю xPDO, которого я уже упоминал в этом посте :).
<?php $actorTable = $xpdo->getTableName('Actor'); $query= new xPDOCriteria($xpdo, "SELECT * FROM {$actorTable} WHERE `actor_id` < :index LIMIT 10", array( ':index' => 2, )); if ($query->prepare() && $query->stmt->execute()) { while ($actor = $query->stmt->fetch(PDO::FETCH_ASSOC)) { print_r($actor); } } ?>
Кто дочитал сей пост, писавшийся три дня, — тому пирожок :)
Ссылки:
→ Знакомство с xPDO ч.1
→ xPDO O/R Bridge 2.0 Documentation (очень советую прочитать, если вас заинтересовал xPDO, там не очень много, но помогает лучше понять).
Источник: community.modx-cms.ru/blog/documentation/448.html
web.archive.org/web/20160727034748/http://community.modx-cms.ru/blog/documentation/401.html
это реализуется через связи, нужно вернуться к 4 разделу (связи объектов) и ещё раз всё обдумать :)