Add: support for param converters

This commit is contained in:
Kévin Gomez 2013-11-03 22:52:53 +00:00
parent 41ebcdbaa7
commit b1217c0e5d
4 changed files with 333 additions and 1 deletions

View file

@ -48,7 +48,7 @@ class PropelExtension extends Extension
if (!$container->hasDefinition('propel')) {
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('propel.xml');
//$loader->load('converters.xml');
$loader->load('converters.xml');
}
// build properties

View file

@ -0,0 +1,281 @@
<?php
namespace Propel\PropelBundle\Request\ParamConverter;
use Propel\PropelBundle\Util\PropelInflector;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter\ParamConverterInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
/**
* PropelParamConverter
*
* This convert action parameter to a Propel Object
* there is two option for this converter:
*
* mapping : take an array of routeParam => column
* exclude : take an array of routeParam to exclude from the conversion process
*
*
* @author Jérémie Augustin <jeremie.augustin@pixel-cookers.com>
*/
class PropelParamConverter implements ParamConverterInterface
{
/**
* the pk column (e.g. id)
* @var string
*/
protected $pk;
/**
* list of column/value to use with filterBy
* @var array
*/
protected $filters = array();
/**
* list of route parameters to exclude from the conversion process
* @var array
*/
protected $exclude = array();
/**
* list of with option use to hydrate related object
* @var array
*/
protected $withs;
/**
* @var bool
*/
protected $hasWith = false;
/**
* @var RouterInterface
*/
protected $router;
public function setRouter(RouterInterface $router = null)
{
$this->router = $router;
}
/**
* @param Request $request
* @param ConfigurationInterface $configuration
*
* @return bool
*
* @throws \LogicException
* @throws NotFoundHttpException
* @throws \Exception
*/
public function apply(Request $request, ConfigurationInterface $configuration)
{
$class = $configuration->getClass();
$classQuery = $class . 'Query';
$classTableMap = $class::TABLE_MAP;
$this->filters = array();
$this->exclude = array();
if (!class_exists($classQuery)) {
throw new \Exception(sprintf('The %s Query class does not exist', $classQuery));
}
$tableMap = new $classTableMap();
$pkColumns = $tableMap->getPrimaryKeys();
if (count($pkColumns) === 1) {
$pk = array_pop($pkColumns);
$this->pk = strtolower($pk->getName());
}
$options = $configuration->getOptions();
// Check route options for converter options, if there are non provided.
if (empty($options) && $request->attributes->has('_route') && $this->router && $configuration instanceof ParamConverter) {
$converterOption = $this->router->getRouteCollection()->get($request->attributes->get('_route'))->getOption('propel_converter');
if (!empty($converterOption[$configuration->getName()])) {
$options = $converterOption[$configuration->getName()];
}
}
if (isset($options['mapping'])) {
// We use the mapping for calling findPk or filterBy
foreach ($options['mapping'] as $routeParam => $column) {
if ($request->attributes->has($routeParam)) {
if ($this->pk === $column) {
$this->pk = $routeParam;
} else {
$this->filters[$column] = $request->attributes->get($routeParam);
}
}
}
} else {
$this->exclude = isset($options['exclude'])? $options['exclude'] : array();
$this->filters = $request->attributes->all();
}
$this->withs = isset($options['with'])? is_array($options['with'])? $options['with'] : array($options['with']) : array();
// find by Pk
if (false === $object = $this->findPk($classQuery, $request)) {
// find by criteria
if (false === $object = $this->findOneBy($classQuery, $request)) {
if ($configuration->isOptional()) {
//we find nothing but the object is optional
$object = null;
} else {
throw new \LogicException('Unable to guess how to get a Propel object from the request information.');
}
}
}
if (null === $object && false === $configuration->isOptional()) {
throw new NotFoundHttpException(sprintf('%s object not found.', $configuration->getClass()));
}
$request->attributes->set($configuration->getName(), $object);
return true;
}
/**
* @param ConfigurationInterface $configuration
*
* @return bool
*/
public function supports(ConfigurationInterface $configuration)
{
if (null === ($classname = $configuration->getClass())) {
return false;
}
if (!class_exists($classname)) {
return false;
}
// Propel Class?
$class = new \ReflectionClass($configuration->getClass());
if ($class->implementsInterface('\Propel\Runtime\ActiveRecord\ActiveRecordInterface')) {
return true;
}
return false;
}
/**
* Try to find the object with the id
*
* @param string $classQuery the query class
* @param Request $request
*
* @return mixed
*/
protected function findPk($classQuery, Request $request)
{
if (in_array($this->pk, $this->exclude) || !$request->attributes->has($this->pk)) {
return false;
}
$query = $this->getQuery($classQuery);
if (!$this->hasWith) {
return $query->findPk($request->attributes->get($this->pk));
} else {
return $query->filterByPrimaryKey($request->attributes->get($this->pk))->find()->getFirst();
}
}
/**
* Try to find the object with all params from the $request
*
* @param string $classQuery the query class
* @param Request $request
*
* @return mixed
*/
protected function findOneBy($classQuery, Request $request)
{
$query = $this->getQuery($classQuery);
$hasCriteria = false;
foreach ($this->filters as $column => $value) {
if (!in_array($column, $this->exclude)) {
try {
$query->{'filterBy' . PropelInflector::camelize($column)}($value);
$hasCriteria = true;
} catch (\PropelException $e) { }
}
}
if (!$hasCriteria) {
return false;
}
if (!$this->hasWith) {
return $query->findOne();
} else {
return $query->find()->getFirst();
}
}
/**
* Init the query class with optional joinWith
*
* @param string $classQuery
*
* @return \ModelCriteria
*
* @throws \Exception
*/
protected function getQuery($classQuery)
{
$query = $classQuery::create();
foreach ($this->withs as $with) {
if (is_array($with)) {
if (2 == count($with)) {
$query->joinWith($with[0], $this->getValidJoin($with));
$this->hasWith = true;
} else {
throw new \Exception(sprintf('ParamConverter : "with" parameter "%s" is invalid,
only string relation name (e.g. "Book") or an array with two keys (e.g. {"Book", "LEFT_JOIN"}) are allowed',
var_export($with, true)));
}
} else {
$query->joinWith($with);
$this->hasWith = true;
}
}
return $query;
}
/**
* Return the valid join Criteria base on the with parameter
*
* @param array $with
*
* @return string
*
* @throws \Exception
*/
protected function getValidJoin($with)
{
switch (trim(str_replace(array('_', 'JOIN'), '', strtoupper($with[1])))) {
case 'LEFT':
return \Criteria::LEFT_JOIN;
case 'RIGHT':
return \Criteria::RIGHT_JOIN;
case 'INNER':
return \Criteria::INNER_JOIN;
}
throw new \Exception(sprintf('ParamConverter : "with" parameter "%s" is invalid,
only "left", "right" or "inner" are allowed for join option',
var_export($with, true)));
}
}

View file

@ -0,0 +1,20 @@
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<parameters>
<parameter key="propel.converter.propel.class">Propel\PropelBundle\Request\ParamConverter\PropelParamConverter</parameter>
</parameters>
<services>
<service id="propel.converter.propel.orm" class="%propel.converter.propel.class%">
<tag name="request.param_converter" converter="propel" priority="1" />
<call method="setRouter">
<argument type="service" id="router" on-invalid="null" />
</call>
</service>
</services>
</container>

31
Util/PropelInflector.php Normal file
View file

@ -0,0 +1,31 @@
<?php
/**
* This file is part of the PropelBundle package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* @license MIT License
*/
namespace Propel\PropelBundle\Util;
/**
* The Propel inflector class provides methods for inflecting text.
*
* @author William Durand <william.durand1@gmail.com>
*/
class PropelInflector
{
/**
* Camelize a word.
* Inspirated by https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Util/Inflector.php
*
* @param string $word The word to camelize.
* @return string
*/
public static function camelize($word)
{
return lcfirst(str_replace(" ", "", ucwords(strtr($word, "_-", " "))));
}
}