Использование Propel 2
В статье собраны примеры составления не тривиальных запросов в 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 класс;