Merge pull request #394 from pierredup/master

Add Symfony ExpressionLanguage support for indexable callback
This commit is contained in:
Tim Nagel 2013-11-04 01:59:40 -08:00
commit 8a14fbd73b
4 changed files with 68 additions and 5 deletions

View file

@ -6,6 +6,9 @@ use Doctrine\Common\EventSubscriber;
use Doctrine\Common\Persistence\ObjectManager;
use FOS\ElasticaBundle\Persister\ObjectPersisterInterface;
use FOS\ElasticaBundle\Persister\ObjectPersister;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\SyntaxError;
abstract class AbstractListener implements EventSubscriber
{
@ -51,6 +54,13 @@ abstract class AbstractListener implements EventSubscriber
*/
private $scheduledForRemoval = array();
/**
* An instance of ExpressionLanguage
*
* @var ExpressionLanguage
*/
protected $expressionLanguage;
/**
* Constructor.
*
@ -83,14 +93,23 @@ abstract class AbstractListener implements EventSubscriber
* should expect the object for consideration as its only argument and
* return a boolean.
*
* @param callback $callback
* @param callback $callback
* @throws \RuntimeException if the callback is not callable
*/
public function setIsIndexableCallback($callback)
{
if (is_string($callback)) {
if (!is_callable(array($this->objectClass, $callback))) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $this->objectClass, $callback));
if (false !== ($expression = $this->getExpressionLanguage())) {
$callback = new Expression($callback);
try {
$expression->compile($callback, array($this->getExpressionVar()));
} catch (SyntaxError $e) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable or a valid expression.', $this->objectClass, $callback), 0, $e);
}
} else {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $this->objectClass, $callback));
}
}
} elseif (!is_callable($callback)) {
if (is_array($callback)) {
@ -98,6 +117,7 @@ abstract class AbstractListener implements EventSubscriber
if (is_object($class)) {
$class = get_class($class);
}
if ($class && $method) {
throw new \RuntimeException(sprintf('Indexable callback %s::%s() is not callable.', $class, $method));
}
@ -111,7 +131,7 @@ abstract class AbstractListener implements EventSubscriber
/**
* Return whether the object is indexable with respect to the callback.
*
* @param object $object
* @param object $object
* @return boolean
*/
protected function isObjectIndexable($object)
@ -120,6 +140,10 @@ abstract class AbstractListener implements EventSubscriber
return true;
}
if ($this->isIndexableCallback instanceof Expression) {
return $this->getExpressionLanguage()->evaluate($this->isIndexableCallback, array($this->getExpressionVar($object) => $object));
}
return is_string($this->isIndexableCallback)
? call_user_func(array($object, $this->isIndexableCallback))
: call_user_func($this->isIndexableCallback, $object);
@ -155,4 +179,32 @@ abstract class AbstractListener implements EventSubscriber
unset($this->scheduledForRemoval[$objectHash]);
}
}
/**
* @param mixed $object
* @return string
*/
private function getExpressionVar($object = null)
{
$class = $object ?: $this->objectClass;
$ref = new \ReflectionClass($class);
return strtolower($ref->getShortName());
}
/**
* @return bool|ExpressionLanguage
*/
private function getExpressionLanguage()
{
if (null === $this->expressionLanguage) {
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
return false;
}
$this->expressionLanguage = new ExpressionLanguage();
}
return $this->expressionLanguage;
}
}

View file

@ -630,6 +630,13 @@ In this case, the callback_class will be the `isIndexable()` method on the speci
service and the object being considered for indexing will be passed as the only
argument. This allows you to do more complex validation (e.g. ACL checks).
If you have the [Symfony ExpressionLanguage](https://github.com/symfony/expression-language) component installed, you can use expressions
to evaluate the callback:
persistence:
listener:
is_indexable_callback: "user.isActive() && user.hasRole('ROLE_USER')"
As you might expect, new entities will only be indexed if the callback_class returns
`true`. Additionally, modified entities will be updated or removed from the
index depending on whether the callback_class returns `true` or `false`, respectively.

View file

@ -164,6 +164,7 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
array('nonexistentEntityMethod'),
array(array(new Listener\IndexableDecider(), 'internalMethod')),
array(42),
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
);
}
@ -173,6 +174,7 @@ abstract class AbstractListenerTest extends \PHPUnit_Framework_TestCase
array('getIsIndexable'),
array(array(new Listener\IndexableDecider(), 'isIndexable')),
array(function(Listener\Entity $entity) { return $entity->getIsIndexable(); }),
array('entity.getIsIndexable()')
);
}

View file

@ -24,14 +24,16 @@
"doctrine/mongodb-odm": "1.0.*@dev",
"propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev",
"knplabs/knp-components": "1.2.*"
"knplabs/knp-components": "1.2.*",
"symfony/expression-language" : "2.4.*@dev"
},
"suggest": {
"doctrine/orm": ">=2.2,<2.5-dev",
"doctrine/mongodb-odm": "1.0.*@dev",
"propel/propel1": "1.6.*",
"pagerfanta/pagerfanta": "1.0.*@dev",
"knplabs/knp-components": "1.2.*"
"knplabs/knp-components": "1.2.*",
"symfony/expression-language" : "2.4.*@dev"
},
"autoload": {
"psr-0": { "FOS\\ElasticaBundle": "" }