Использование Propel 2

Категория: Propel

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

Классы

use \Propel\Runtime\Propel;
use \Propel\Runtime\ActiveQuery\Criteria;
use \Propel\Runtime\Map\TableMap;

Отладка

Получить последний SQL-запрос:

/** @var \Propel\Runtime\Connection\DebugPDO $con */
$con = \Propel\Runtime\Propel::getConnection();
$sql = $con->getLastExecutedQuery();
die($sql);

Получить шаблон SQL-запроса и отдельно параметры:

$query->toString();

Выборка

Выбрать определенные поля из таблицы:

$attrs = ModelQuery::create()
  ->select(['Id' => 'id', 'Name' => 'my_name'])
  ->findByPresetId(3);

Фильтрация

Пример условия:

Table1Query::create()
  ->leftJoinTable2()
  ->leftJoinTable3()
  ->useTable2Query()
    ->leftJoinTable4()
  ->endUse()
  ->condition('cond1', Table4::FIELD_4 . ' = ' . Table2::FIELD_2)
  ->condition('cond2', Table4::FIELD_4 . ' = ' . Table3::FIELD_1)
  ->combine(array('cond1', 'cond2'), Criteria::LOGICAL_OR, 'onClause')
  ->setJoinCondition('Table4', 'onClause')
  ->find();

Общие методы фильтрации:

$items = ItemQuery::create()
  ->filterByUpdatedAt(['min' => '2013-01-01', 'max' => '2013-04-04'])
  ->filterByName('%' . $name . '%') // LIKE
  ->filterByHits(5, '>')            // > 5 (больше)
  -filterByCategoryId([1,22,333])   // WHERE IN
  ->orderByName()                   // ORDER BY
  ->find();

Использование вложенного объекта фильтрации:

$criteria = ItemQuery::create()->filterByIsActive(false)->orderByName();
$items = $object->getItems($criteria);

Составные методы выборки:

$item  = ModelQuery::create()->findOneByEmailAndIsActive('e@mail', true);
$items = ModelQuery::create()->findBy('FieldName', 'value')
Внимание!

В методе filterBy* нельзя использовать составной метод! Составные методы доступны только для find* методов.

Получить одну запись:

$user = UserQuery::create()->findOneByEmail('user@gail.com'); // object || NULL

Виртуальные поля


ModelQuery::create()->withColumn('UPPER(Field.name)', 'fname')->find();
$products = ProductQuery::create('p')
  ->join('p.Attr a')
  ->with('a')
  ->addAsColumn('field_id1', 'a.field_id') // Оба метода
  ->withColumn('a.FieldId', 'field_id2')   // аналогичны
  ->find();
  
# OR
$products = ProductQuery::create('p')
  ->useAttrQuery('a')
    ->filterById(3, '>')
  ->endUse()
  ->addAsColumn('field_id1', 'a.field_id') // Оба метода
  ->withColumn('a.FieldId', 'field_id2')   // аналогичны
  ->find();


Получить массив значений ID:

$items_ids = $items->getPrimaryKeys(false);
$items_ids = $items->toKeyValue('PrimaryKey', 'Id');
//$items_ids = $items->getArrayCopy('Id');

Использование условий (condition) при составлении запроса:

->_if($user)
  ->filterByUserId(is_object($user) ? $user->getId() : $user)
->_endIf()

Объект Criteria

$criteria = new \Criteria();
$criteria->addAscendingOrderByColumn('table_name.sortable_rank');
$criteria->addDescendingOrderByColumn('table_name.id');
$criteria->setLimit(2);

$this->getSomeEntity()->getRelationEntity($criteria); 

Связанные записи

JOIN'ы

Общие методы:

->joinWith('Field')
->join('Field alias')->with('alias')
->innerJoin('Field alias')->with('alias')
->leftJoin%ModelName%('alias')->with('alias')

->addAlias('ProductParams', 'product_params_3')
->addJoin('product.Id', 'ProductParams.product_id', Criteria::INNER_JOIN)

Получить все связанные 1:M записи как вложенный массив в свойство FieldOption:

$fields = FieldQuery::create()
  ->joinWith('FieldOption', \Criteria::LEFT_JOIN)
  ->find();

При LEFT JOIN для каждого поля (Field) будут присоединены все опции (FieldOption), т.е. для каждой опции фактически будет приджойнено поле:

$fields = FieldQuery::create()->leftJoinFieldOption()->find();

Lazy Load / Populate

Подгрузить связанные записи после получения записей:

$model  = ModelQuery::create()->findById(1);
$fields = $model->populateRelation('Field');

Обновление

->update(array('created_at' => ['raw' => 'NOW()']));

Удаление


Удалить запись:

$item = UserQuery::create()->findOneByEmail('e@mail')->delete();

Formatters

Установка желаемого форматтера:

->setFormatter(ModelCriteria::FORMAT_ARRAY)
->setFormatter('CustomPropelObjectFormatter')

Выполнение нативного SQL кода

Пример получения PDO объекта и выполнение SQL запроса с использованием prepared statements:

/** @var \Propel\Runtime\Connection\DebugPDO $con */
$con = Propel::getReadConnection(\Models\Map\ModelTableMap::DATABASE_NAME);

$sql  = 'SELECT id FROM table WHERE col1 > ? AND col2 = ?';
$stmt = $con->prepare($sql);
$success = $stmt->execute([10, true]);
$result  = $stmt->fetch(\PDO::FETCH_ASSOC);

Разберем этапы выполнения нативного SQL подробнее.

1. Получаем PDO объект:

$dbname = \Models\Map\ModelTableMap::DATABASE_NAME;
$con = Propel::getReadConnection($dbname);  // Чтение
$con = Propel::getWriteConnection($dbname); // Запись

2. Пишем SQL запрос с использованием prepared statements:

$sql = 'SELECT id, name FROM table WHERE is_active = ?';

3. Подготовить запрос (prepared statements):

/** @var \Propel\Runtime\Connection\StatementWrapper $stmt */
$stmt = $con->prepare($sql);

4. Установить значения заполнителей (плейсхолдеров, placeholders):

$stmt->bindValue(':id', 33, \PDO::PARAM_INT);
$stmt->bindValue(1, 'value', \PDO::PARAM_STR); // первый параметр
# Привязать переменную к параметру (по ссылке)
$stmt->bindParam(':color', $color, \PDO::PARAM_STR);

5. Выполнить запрос и извлечь данные:

$success = $stmt->execute([':param' => 'value']); // для именованных `:param`
$success = $stmt->execute(['value1', 'value2']);  // последовательно, для `?`
$result  = $stmt->fetch(\PDO::FETCH_ASSOC);

Пожелания

Что хотелось бы доработать:

  • Методы with, joinWith - возвращают ModelCriteria, а для code completion нужно вернуть базовый Query класс;


категория: Propel