Completed
Pull Request — master (#1269)
by Wesley
11:25
created

Indexable::getExpressionLanguage()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

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