Completed
Pull Request — master (#1145)
by
unknown
06:24
created

Indexable::buildCallback()   C

Complexity

Conditions 7
Paths 5

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 7

Importance

Changes 0
Metric Value
dl 0
loc 22
c 0
b 0
f 0
ccs 11
cts 11
cp 1
rs 6.9811
cc 7
eloc 11
nc 5
nop 2
crap 7
1
<?php
2
3
/**
4
 * This file is part of the FOSElasticaBundle project.
5
 *
6
 * (c) FriendsOfSymfony <https://github.com/FriendsOfSymfony/FOSElasticaBundle/graphs/contributors>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace FOS\ElasticaBundle\Provider;
13
14
use Symfony\Component\DependencyInjection\ContainerInterface;
15
use Symfony\Component\ExpressionLanguage\Expression;
16
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
17
use Symfony\Component\ExpressionLanguage\SyntaxError;
18
use Symfony\Component\PropertyAccess\PropertyAccess;
19
use Symfony\Component\PropertyAccess\PropertyAccessorInterface;
20
21
class Indexable implements IndexableInterface
22
{
23
    /**
24
     * An array of raw configured callbacks for all types.
25
     *
26
     * @var array
27
     */
28
    private $callbacks = array();
29
30
    /**
31
     * @var \Symfony\Component\DependencyInjection\ContainerInterface
32
     */
33
    private $container;
34
35
    /**
36
     * An instance of ExpressionLanguage.
37
     *
38
     * @var ExpressionLanguage
39
     */
40
    private $expressionLanguage;
41
42
    /**
43
     * An array of initialised callbacks.
44
     *
45
     * @var array
46
     */
47
    private $initialisedCallbacks = array();
48
49
    /**
50
     * PropertyAccessor instance.
51
     *
52
     * @var PropertyAccessorInterface
53
     */
54
    private $propertyAccessor;
55
56
    /**
57
     * @var bool
58
     */
59
    private $indexingEnabled = true;
60
61
    /**
62
     * @param array $callbacks
63
     * @param ContainerInterface $container
64
     */
65 25
    public function __construct(array $callbacks, ContainerInterface $container)
66
    {
67 25
        $this->callbacks = $callbacks;
68 25
        $this->container = $container;
69 25
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
70 25
    }
71
72
    /**
73
     * Return whether the object is indexable with respect to the callback.
74
     *
75
     * @param string $indexName
76
     * @param string $typeName
77
     * @param mixed  $object
78
     *
79
     * @return bool
80
     */
81 18
    public function isObjectIndexable($indexName, $typeName, $object)
82
    {
83 18
        if(!$this->indexingEnabled) {
84 1
            return false;
85
        }
86
87 17
        $type = sprintf('%s/%s', $indexName, $typeName);
88 17
        $callback = $this->getCallback($type, $object);
89 12
        if (!$callback) {
90 1
            return true;
91
        }
92
93 11
        if ($callback instanceof Expression) {
94 6
            return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
95 6
                'object' => $object,
96 6
                $this->getExpressionVar($object) => $object,
97
            ));
98
        }
99
100 6
        return is_string($callback)
101 2
            ? call_user_func(array($object, $callback))
102 6
            : call_user_func($callback, $object);
103
    }
104
105
    /**
106
     * Returns true if global indexing is enabled, false otherwise.
107
     * 
108
     * @return bool
109
     */
110
    public function isIndexingEnabled()
111
    {
112
        return $this->indexingEnabled;
113
    }
114
115
    /**
116
     * Sets global indexing.
117
     * 
118
     * @param bool $indexingEnabled
119
     */
120 1
    public function setIndexingEnabled($indexingEnabled)
121
    {
122 1
        $this->indexingEnabled = $indexingEnabled;
123 1
    }
124
    
125
    /**
126
     * Builds and initialises a callback.
127
     *
128
     * @param string $type
129
     * @param object $object
130
     *
131
     * @return mixed
132
     */
133 17
    private function buildCallback($type, $object)
134
    {
135 17
        if (!array_key_exists($type, $this->callbacks)) {
136 1
            return;
137
        }
138
139 16
        $callback = $this->callbacks[$type];
140
141 16
        if (is_callable($callback) or is_callable(array($object, $callback))) {
142 4
            return $callback;
143
        }
144
145 13
        if (is_array($callback) && !is_object($callback[0])) {
146 4
            return $this->processArrayToCallback($type, $callback);
147
        }
148
149 10
        if (is_string($callback)) {
150 8
            return $this->buildExpressionCallback($type, $object, $callback);
151
        }
152
153 2
        throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
154
    }
155
156
    /**
157
     * Processes a string expression into an Expression.
158
     *
159
     * @param string $type
160
     * @param mixed $object
161
     * @param string $callback
162
     *
163
     * @return Expression
164
     */
165 8
    private function buildExpressionCallback($type, $object, $callback)
166
    {
167 8
        $expression = $this->getExpressionLanguage();
168 8
        if (!$expression) {
169
            throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
170
        }
171
172
        try {
173 8
            $callback = new Expression($callback);
174 8
            $expression->compile($callback, array(
175 8
                'object', $this->getExpressionVar($object)
176
            ));
177
178 6
            return $callback;
179 2
        } catch (SyntaxError $e) {
180 2
            throw new \InvalidArgumentException(sprintf(
181 2
                'Callback for type "%s" is an invalid expression',
182
                $type
183 2
            ), $e->getCode(), $e);
184
        }
185
    }
186
187
    /**
188
     * Retreives a cached callback, or creates a new callback if one is not found.
189
     *
190
     * @param string $type
191
     * @param object $object
192
     *
193
     * @return mixed
194
     */
195 17
    private function getCallback($type, $object)
196
    {
197 17
        if (!array_key_exists($type, $this->initialisedCallbacks)) {
198 17
            $this->initialisedCallbacks[$type] = $this->buildCallback($type, $object);
199
        }
200
201 12
        return $this->initialisedCallbacks[$type];
202
    }
203
204
    /**
205
     * Returns the ExpressionLanguage class if it is available.
206
     *
207
     * @return ExpressionLanguage|null
208
     */
209 8
    private function getExpressionLanguage()
210
    {
211 8
        if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
212 8
            $this->expressionLanguage = new ExpressionLanguage();
213
        }
214
215 8
        return $this->expressionLanguage;
216
    }
217
218
    /**
219
     * Returns the variable name to be used to access the object when using the ExpressionLanguage
220
     * component to parse and evaluate an expression.
221
     *
222
     * @param mixed $object
223
     *
224
     * @return string
225
     */
226 8
    private function getExpressionVar($object = null)
227
    {
228 8
        if (!is_object($object)) {
229
            return 'object';
230
        }
231
232 8
        $ref = new \ReflectionClass($object);
233
234 8
        return strtolower($ref->getShortName());
235
    }
236
237
    /**
238
     * Processes an array into a callback. Replaces the first element with a service if
239
     * it begins with an @.
240
     *
241
     * @param string $type
242
     * @param array $callback
243
     *
244
     * @return array
245
     */
246 4
    private function processArrayToCallback($type, array $callback)
247
    {
248 4
        list($class, $method) = $callback + array(null, '__invoke');
249
250 4
        if (strpos($class, '@') === 0) {
251 4
            $service = $this->container->get(substr($class, 1));
252 4
            $callback = array($service, $method);
253
254 4
            if (!is_callable($callback)) {
255 1
                throw new \InvalidArgumentException(sprintf(
256 1
                    'Method "%s" on service "%s" is not callable.',
257
                    $method,
258 1
                    substr($class, 1)
259
                ));
260
            }
261
262 3
            return $callback;
263
        }
264
265
        throw new \InvalidArgumentException(sprintf(
266
            'Unable to parse callback array for type "%s"',
267
            $type
268
        ));
269
    }
270
}
271