Completed
Push — master ( 7610d9...d72fda )
by Nate
03:58 queued 02:30
created

Scope::validParams()   D

Complexity

Conditions 9
Paths 14

Size

Total Lines 45
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
dl 0
loc 45
ccs 0
cts 41
cp 0
rs 4.909
c 0
b 0
f 0
cc 9
eloc 31
nc 14
nop 2
crap 90
1
<?php
2
3
/**
4
 * @author    Flipbox Factory
5
 * @copyright Copyright (c) 2017, Flipbox Digital
6
 * @link      https://github.com/flipbox/transform/releases/latest
7
 * @license   https://github.com/flipbox/transform/blob/master/LICENSE
8
 */
9
10
namespace Flipbox\Transform;
11
12
use Flipbox\Transform\Helpers\TransformerHelper;
13
use Flipbox\Transform\Transformers\TransformerInterface;
14
use InvalidArgumentException;
15
16
/**
17
 * @author Flipbox Factory <[email protected]>
18
 * @since 1.0.0
19
 */
20
class Scope
21
{
22
    /**
23
     * @var string
24
     */
25
    protected $scopeIdentifier;
26
27
    /**
28
     * @var Transform
29
     */
30
    protected $transform;
31
32
    /**
33
     * @var array
34
     */
35
    protected $parentScopes = [];
36
37
    /**
38
     * Scope constructor.
39
     * @param Transform $transform
40
     * @param string|null $scopeIdentifier
41
     * @param array $parentScopes
42
     */
43
    public function __construct(
44
        Transform $transform,
45
        string $scopeIdentifier = null,
46
        array $parentScopes = []
47
    ) {
48
        $this->transform = $transform;
49
        $this->scopeIdentifier = $scopeIdentifier;
50
        $this->parentScopes = $parentScopes;
51
    }
52
53
    /**
54
     * @return Transform
55
     */
56
    public function getTransform(): Transform
57
    {
58
        return $this->transform;
59
    }
60
61
    /**
62
     * @param $key
63
     * @return ParamBag
64
     */
65
    public function getParams(string $key = null): ParamBag
66
    {
67
        return $this->getTransform()->getParams(
68
            $this->getIdentifier($key)
69
        );
70
    }
71
72
    /**
73
     * Get the current identifier.
74
     *
75
     * @return string|null
76
     */
77
    public function getScopeIdentifier()
78
    {
79
        return $this->scopeIdentifier;
80
    }
81
82
    /**
83
     * Get the unique identifier for this scope.
84
     *
85
     * @param string $appendIdentifier
86
     *
87
     * @return string
88
     */
89
    public function getIdentifier(string $appendIdentifier = null): string
90
    {
91
        return implode(
92
            '.',
93
            array_filter(array_merge(
94
                $this->parentScopes,
95
                [
96
                    $this->scopeIdentifier,
97
                    $appendIdentifier
98
                ]
99
            ))
100
        );
101
    }
102
103
    /**
104
     * Getter for parentScopes.
105
     *
106
     * @return array
107
     */
108
    public function getParentScopes(): array
109
    {
110
        return $this->parentScopes;
111
    }
112
113
    /**
114
     * Is Requested.
115
     *
116
     * Check if - in relation to the current scope - this specific segment is allowed.
117
     * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current
118
     * scope is a then c is not allowed, even if it is there and potentially transformable.
119
     *
120
     * @internal
121
     *
122
     * @param string $checkScopeSegment
123
     *
124
     * @return bool Returns the new number of elements in the array.
125
     */
126
    public function isRequested($checkScopeSegment): bool
127
    {
128
        return in_array(
129
            $this->scopeString($checkScopeSegment),
130
            $this->transform->getIncludes()
131
        );
132
    }
133
134
    /**
135
     * Is Excluded.
136
     *
137
     * Check if - in relation to the current scope - this specific segment should
138
     * be excluded. That means, if a.b.c is excluded and the current scope is a.b,
139
     * then c will not be allowed in the transformation whether it appears in
140
     * the list of default or available, requested includes.
141
     *
142
     * @internal
143
     *
144
     * @param string $checkScopeSegment
145
     *
146
     * @return bool
147
     */
148
    public function isExcluded($checkScopeSegment): bool
149
    {
150
        return in_array(
151
            $this->scopeString($checkScopeSegment),
152
            $this->transform->getExcludes()
153
        );
154
    }
155
156
    /**
157
     * @param TransformerInterface|callable $transformer
158
     * @param mixed $data
159
     * @param array $extra
160
     * @return mixed
161
     */
162
    public function transform(callable $transformer, $data, array $extra = [])
163
    {
164
        return $this->parseValue($transformer, $data, null, $extra);
165
    }
166
167
    /**
168
     * @param callable $transformer
169
     * @param string $key
170
     * @return bool
171
     */
172
    public function includeValue(callable $transformer, string $key): bool
173
    {
174
        // Ignore optional (that have not been explicitly requested)
175
        if ($transformer instanceof TransformerInterface &&
176
            in_array($key, $transformer->getIncludes(), true) &&
177
            !$this->isRequested($key)
178
        ) {
179
            return false;
180
        }
181
182
        // Ignore excludes
183
        if ($this->isExcluded($key)) {
184
            return false;
185
        }
186
187
        return true;
188
    }
189
190
    /**
191
     * @param $val
192
     * @param $data
193
     * @param string|null $key
194
     * @param array $extra
195
     * @return mixed
196
     */
197
    public function parseValue($val, $data, string $key = null, array $extra = [])
198
    {
199
        if (TransformerHelper::isTransformer($val)) {
200
            $args = [$data, $this, $key];
201
202
            if (!empty($extra)) {
203
                $args = array_merge(
204
                    $args,
205
                    $this->validParams($val, $extra)
206
                );
207
            }
208
209
            return call_user_func_array($val, $args);
210
        }
211
212
        return $val;
213
    }
214
215
    /**
216
     * @param $transformer
217
     * @param array $params
218
     * @return array
219
     */
220
    private function validParams($transformer, array $params): array
221
    {
222
        if (!is_object($transformer)) {
223
            return $params;
224
        }
225
226
        $method = new \ReflectionMethod($transformer, '__invoke');
227
228
        $ignore = ['data', 'scope', 'identifier'];
229
        $args = [];
230
        $missing = [];
231
        $actionParams = [];
232
        foreach ($method->getParameters() as $param) {
233
            $name = $param->name;
234
            if (true === in_array($name, $ignore, true)) {
235
                continue;
236
            }
237
            if (array_key_exists($name, $params)) {
238
                if ($param->isArray()) {
239
                    $args[] = $actionParams[$name] = (array) $params[$name];
240
                } elseif (!is_array($params[$name])) {
241
                    $args[] = $actionParams[$name] = $params[$name];
242
                } else {
243
                    throw new InvalidArgumentException(sprintf(
244
                        'Invalid data received for parameter "%s".',
245
                        $name
246
                    ));
247
                }
248
                unset($params[$name]);
249
            } elseif ($param->isDefaultValueAvailable()) {
250
                $args[] = $actionParams[$name] = $param->getDefaultValue();
251
            } else {
252
                $missing[] = $name;
253
            }
254
        }
255
256
        if (!empty($missing)) {
257
            throw new InvalidArgumentException(sprintf(
258
                'Missing required parameters "%s".',
259
                implode(', ', $missing)
260
            ));
261
        }
262
263
        return $args;
264
    }
265
266
    /**
267
     * @param string $identifier
268
     * @return Scope
269
     */
270
    public function childScope(string $identifier): Scope
271
    {
272
        $parentScopes = $this->getParentScopes();
273
        $parentScopes[] = $this->getScopeIdentifier();
274
275
        return new static(
276
            $this->getTransform(),
277
            $identifier,
278
            $parentScopes
279
        );
280
    }
281
282
    /**
283
     * Check, if this is the root scope.
284
     *
285
     * @return bool
286
     */
287
    protected function isRootScope(): bool
288
    {
289
        return empty($this->parentScopes);
290
    }
291
292
    /**
293
     * Filter the provided data with the requested filter fields for
294
     * the scope resource
295
     *
296
     * @param array $data
297
     *
298
     * @return array
299
     */
300
    public function filterFields(array $data): array
301
    {
302
        $fields = $this->getFilterFields();
303
304
        if ($fields === null) {
305
            return $data;
306
        }
307
308
        return array_intersect_key(
309
            $data,
310
            array_flip(
311
                iterator_to_array($fields)
312
            )
313
        );
314
    }
315
316
    /**
317
     * Return the requested filter fields for the scope resource
318
     *
319
     * @internal
320
     *
321
     * @return ParamBag|null
322
     */
323
    protected function getFilterFields()
324
    {
325
        return $this->transform->getField(
326
            $this->getScopeIdentifier()
327
        );
328
    }
329
330
    /**
331
     * @param string $checkScopeSegment
332
     * @return string
333
     */
334
    private function scopeString(string $checkScopeSegment): string
335
    {
336
        if (!empty($this->parentScopes)) {
337
            $scopeArray = array_slice($this->parentScopes, 1);
338
            array_push($scopeArray, $this->scopeIdentifier, $checkScopeSegment);
339
        } else {
340
            $scopeArray = [$checkScopeSegment];
341
        }
342
343
        return implode('.', (array)$scopeArray);
344
    }
345
}
346