Skip to content

Commit 72a9dfa

Browse files
committed
Indexable service improvements
1 parent 9cf0117 commit 72a9dfa

File tree

5 files changed

+92
-31
lines changed

5 files changed

+92
-31
lines changed

CHANGELOG-3.1.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ https://github.com/FriendsOfSymfony/FOSElasticaBundle/compare/v3.0.4...v3.1.0
4444
* New events `PRE_INDEX_RESET`, `POST_INDEX_RESET`, `PRE_TYPE_RESET` and
4545
`POST_TYPE_RESET` are run before and after operations that will reset an
4646
index. #744
47+
* Added indexable callback support for the __invoke method of a service. #823

Index/AliasProcessor.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ private function buildAliasUpdateRequest($aliasedIndex, $aliasName, $newIndexNam
110110
* Cleans up an index when we encounter a failure to rename the alias.
111111
*
112112
* @param Client $client
113-
* @param $indexName
113+
* @param string $indexName
114114
* @param \Exception $renameAliasException
115115
*/
116116
private function cleanupRenameFailure(Client $client, $indexName, \Exception $renameAliasException)

Provider/Indexable.php

Lines changed: 77 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Indexable implements IndexableInterface
5555

5656
/**
5757
* @param array $callbacks
58+
* @param ContainerInterface $container
5859
*/
5960
public function __construct(array $callbacks, ContainerInterface $container)
6061
{
@@ -112,37 +113,46 @@ private function buildCallback($type, $object)
112113
return $callback;
113114
}
114115

115-
if (is_array($callback)) {
116-
list($class, $method) = $callback + array(null, null);
117-
118-
if (is_object($class)) {
119-
$class = get_class($class);
120-
}
116+
if (is_array($callback) && !is_object($callback[0])) {
117+
return $this->processArrayToCallback($type, $callback);
118+
}
121119

122-
if (strpos($class, '@') === 0) {
123-
$service = $this->container->get(substr($class, 1));
120+
if (is_string($callback)) {
121+
return $this->buildExpressionCallback($type, $object, $callback);
122+
}
124123

125-
return array($service, $method);
126-
}
124+
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
125+
}
127126

128-
if ($class && $method) {
129-
throw new \InvalidArgumentException(sprintf('Callback for type "%s", "%s::%s()", is not callable.', $type, $class, $method));
130-
}
127+
/**
128+
* Processes a string expression into an Expression.
129+
*
130+
* @param string $type
131+
* @param mixed $object
132+
* @param string $callback
133+
*
134+
* @return Expression
135+
*/
136+
private function buildExpressionCallback($type, $object, $callback)
137+
{
138+
$expression = $this->getExpressionLanguage();
139+
if (!$expression) {
140+
throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
131141
}
132142

133-
if (is_string($callback) && $expression = $this->getExpressionLanguage()) {
143+
try {
134144
$callback = new Expression($callback);
145+
$expression->compile($callback, array(
146+
'object', $this->getExpressionVar($object)
147+
));
135148

136-
try {
137-
$expression->compile($callback, array('object', $this->getExpressionVar($object)));
138-
139-
return $callback;
140-
} catch (SyntaxError $e) {
141-
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is an invalid expression', $type), $e->getCode(), $e);
142-
}
149+
return $callback;
150+
} catch (SyntaxError $e) {
151+
throw new \InvalidArgumentException(sprintf(
152+
'Callback for type "%s" is an invalid expression',
153+
$type
154+
), $e->getCode(), $e);
143155
}
144-
145-
throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
146156
}
147157

148158
/**
@@ -163,30 +173,68 @@ private function getCallback($type, $object)
163173
}
164174

165175
/**
166-
* @return bool|ExpressionLanguage
176+
* Returns the ExpressionLanguage class if it is available.
177+
*
178+
* @return ExpressionLanguage|null
167179
*/
168180
private function getExpressionLanguage()
169181
{
170-
if (null === $this->expressionLanguage) {
171-
if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
172-
return false;
173-
}
174-
182+
if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
175183
$this->expressionLanguage = new ExpressionLanguage();
176184
}
177185

178186
return $this->expressionLanguage;
179187
}
180188

181189
/**
190+
* Returns the variable name to be used to access the object when using the ExpressionLanguage
191+
* component to parse and evaluate an expression.
192+
*
182193
* @param mixed $object
183194
*
184195
* @return string
185196
*/
186197
private function getExpressionVar($object = null)
187198
{
199+
if (!is_object($object)) {
200+
return 'object';
201+
}
202+
188203
$ref = new \ReflectionClass($object);
189204

190205
return strtolower($ref->getShortName());
191206
}
207+
208+
/**
209+
* Processes an array into a callback. Replaces the first element with a service if
210+
* it begins with an @.
211+
*
212+
* @param string $type
213+
* @param array $callback
214+
* @return array
215+
*/
216+
private function processArrayToCallback($type, array $callback)
217+
{
218+
list($class, $method) = $callback + array(null, '__invoke');
219+
220+
if (strpos($class, '@') === 0) {
221+
$service = $this->container->get(substr($class, 1));
222+
$callback = array($service, $method);
223+
224+
if (!is_callable($callback)) {
225+
throw new \InvalidArgumentException(sprintf(
226+
'Method "%s" on service "%s" is not callable.',
227+
$method,
228+
substr($class, 1)
229+
));
230+
}
231+
232+
return $callback;
233+
}
234+
235+
throw new \InvalidArgumentException(sprintf(
236+
'Unable to parse callback array for type "%s"',
237+
$type
238+
));
239+
}
192240
}

Resources/doc/types.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,18 @@ index enabled users.
201201
The callback option supports multiple approaches:
202202

203203
* A method on the object itself provided as a string. `enabled` will call
204-
`Object->enabled()`
204+
`Object->enabled()`. Note that this does not support chaining methods with dot notation
205+
like property paths. To achieve something similar use the ExpressionLanguage option
206+
below.
205207
* An array of a service id and a method which will be called with the object as the first
206208
and only argument. `[ @my_custom_service, 'userIndexable' ]` will call the userIndexable
207209
method on a service defined as my_custom_service.
208210
* An array of a class and a static method to call on that class which will be called with
209211
the object as the only argument. `[ 'Acme\DemoBundle\IndexableChecker', 'isIndexable' ]`
210212
will call Acme\DemoBundle\IndexableChecker::isIndexable($object)
213+
* A single element array with a service id can be used if the service has an __invoke
214+
method. Such an invoke method must accept a single parameter for the object to be indexed.
215+
`[ @my_custom_invokable_service ]`
211216
* If you have the ExpressionLanguage component installed, A valid ExpressionLanguage
212217
expression provided as a string. The object being indexed will be supplied as `object`
213218
in the expression. `object.isEnabled() or object.shouldBeIndexedAnyway()`. For more

Tests/Provider/IndexableTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public function provideInvalidIsIndexableCallbacks()
5555
{
5656
return array(
5757
array('nonexistentEntityMethod'),
58+
array(array('@indexableService', 'internalMethod')),
5859
array(array(new IndexableDecider(), 'internalMethod')),
5960
array(42),
6061
array('entity.getIsIndexable() && nonexistentEntityFunction()'),
@@ -67,6 +68,7 @@ public function provideIsIndexableCallbacks()
6768
array('isIndexable', false),
6869
array(array(new IndexableDecider(), 'isIndexable'), true),
6970
array(array('@indexableService', 'isIndexable'), true),
71+
array(array('@indexableService'), true),
7072
array(function (Entity $entity) { return $entity->maybeIndex(); }, true),
7173
array('entity.maybeIndex()', true),
7274
array('!object.isIndexable() && entity.property == "abc"', true),
@@ -111,4 +113,9 @@ public function isIndexable(Entity $entity)
111113
protected function internalMethod()
112114
{
113115
}
116+
117+
public function __invoke($object)
118+
{
119+
return true;
120+
}
114121
}

0 commit comments

Comments
 (0)