Completed
Push — develop ( ae7e37...6d0422 )
by Nate
01:47
created

Scope::validParams()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 32
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

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