Completed
Push — master ( fef1f2...3eb154 )
by Tim
7s
created

Indexable::buildExpressionCallback()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 3.004
Metric Value
dl 0
loc 21
ccs 12
cts 13
cp 0.9231
rs 9.3142
cc 3
eloc 14
nc 4
nop 3
crap 3.004
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
     * @param array $callbacks
58
     * @param ContainerInterface $container
59
     */
60 21
    public function __construct(array $callbacks, ContainerInterface $container)
61
    {
62 21
        $this->callbacks = $callbacks;
63 21
        $this->container = $container;
64 21
        $this->propertyAccessor = PropertyAccess::createPropertyAccessor();
65 21
    }
66
67
    /**
68
     * Return whether the object is indexable with respect to the callback.
69
     *
70
     * @param string $indexName
71
     * @param string $typeName
72
     * @param mixed  $object
73
     *
74
     * @return bool
75
     */
76 17
    public function isObjectIndexable($indexName, $typeName, $object)
77
    {
78 17
        $type = sprintf('%s/%s', $indexName, $typeName);
79 17
        $callback = $this->getCallback($type, $object);
80 12
        if (!$callback) {
81 1
            return true;
82
        }
83
84 11
        if ($callback instanceof Expression) {
85 6
            return (bool) $this->getExpressionLanguage()->evaluate($callback, array(
86 6
                'object' => $object,
87 6
                $this->getExpressionVar($object) => $object,
88 6
            ));
89
        }
90
91 6
        return is_string($callback)
92 6
            ? call_user_func(array($object, $callback))
93 6
            : call_user_func($callback, $object);
94
    }
95
96
    /**
97
     * Builds and initialises a callback.
98
     *
99
     * @param string $type
100
     * @param object $object
101
     *
102
     * @return mixed
103
     */
104 17
    private function buildCallback($type, $object)
105
    {
106 17
        if (!array_key_exists($type, $this->callbacks)) {
107 1
            return;
108
        }
109
110 16
        $callback = $this->callbacks[$type];
111
112 16
        if (is_callable($callback) or is_callable(array($object, $callback))) {
113 4
            return $callback;
114
        }
115
116 13
        if (is_array($callback) && !is_object($callback[0])) {
117 4
            return $this->processArrayToCallback($type, $callback);
118
        }
119
120 10
        if (is_string($callback)) {
121 8
            return $this->buildExpressionCallback($type, $object, $callback);
122
        }
123
124 2
        throw new \InvalidArgumentException(sprintf('Callback for type "%s" is not a valid callback.', $type));
125
    }
126
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 8
    private function buildExpressionCallback($type, $object, $callback)
137
    {
138 8
        $expression = $this->getExpressionLanguage();
139 8
        if (!$expression) {
140
            throw new \RuntimeException('Unable to process an expression without the ExpressionLanguage component.');
141
        }
142
143
        try {
144 8
            $callback = new Expression($callback);
145 8
            $expression->compile($callback, array(
146 8
                'object', $this->getExpressionVar($object)
147 8
            ));
148
149 6
            return $callback;
150 2
        } catch (SyntaxError $e) {
151 2
            throw new \InvalidArgumentException(sprintf(
152 2
                'Callback for type "%s" is an invalid expression',
153
                $type
154 2
            ), $e->getCode(), $e);
155
        }
156
    }
157
158
    /**
159
     * Retreives a cached callback, or creates a new callback if one is not found.
160
     *
161
     * @param string $type
162
     * @param object $object
163
     *
164
     * @return mixed
165
     */
166 17
    private function getCallback($type, $object)
167
    {
168 17
        if (!array_key_exists($type, $this->initialisedCallbacks)) {
169 17
            $this->initialisedCallbacks[$type] = $this->buildCallback($type, $object);
170 12
        }
171
172 12
        return $this->initialisedCallbacks[$type];
173
    }
174
175
    /**
176
     * Returns the ExpressionLanguage class if it is available.
177
     *
178
     * @return ExpressionLanguage|null
179
     */
180 8
    private function getExpressionLanguage()
181
    {
182 8
        if (null === $this->expressionLanguage && class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) {
183 8
            $this->expressionLanguage = new ExpressionLanguage();
184 8
        }
185
186 8
        return $this->expressionLanguage;
187
    }
188
189
    /**
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
     *
193
     * @param mixed $object
194
     *
195
     * @return string
196
     */
197 8
    private function getExpressionVar($object = null)
198
    {
199 8
        if (!is_object($object)) {
200
            return 'object';
201
        }
202
203 8
        $ref = new \ReflectionClass($object);
204
205 8
        return strtolower($ref->getShortName());
206
    }
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 4
    private function processArrayToCallback($type, array $callback)
217
    {
218 4
        list($class, $method) = $callback + array(null, '__invoke');
219
220 4
        if (strpos($class, '@') === 0) {
221 4
            $service = $this->container->get(substr($class, 1));
222 4
            $callback = array($service, $method);
223
224 4
            if (!is_callable($callback)) {
225 1
                throw new \InvalidArgumentException(sprintf(
226 1
                    'Method "%s" on service "%s" is not callable.',
227 1
                    $method,
228 1
                    substr($class, 1)
229 1
                ));
230
            }
231
232 3
            return $callback;
233
        }
234
235
        throw new \InvalidArgumentException(sprintf(
236
            'Unable to parse callback array for type "%s"',
237
            $type
238
        ));
239
    }
240
}
241