Manager::getRequestedExcludes()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
crap 1
1
<?php
2
3
/*
4
 * This file is part of the League\Fractal package.
5
 *
6
 * (c) Phil Sturgeon <[email protected]>
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 League\Fractal;
13
14
use League\Fractal\Resource\ResourceInterface;
15
use League\Fractal\Serializer\DataArraySerializer;
16
use League\Fractal\Serializer\SerializerAbstract;
17
18
/**
19
 * Manager
20
 *
21
 * Not a wildly creative name, but the manager is what a Fractal user will interact
22
 * with the most. The manager has various configurable options, and allows users
23
 * to create the "root scope" easily.
24
 */
25
class Manager
26
{
27
    /**
28
     * Array of scope identifiers for resources to include.
29
     *
30
     * @var array
31
     */
32
    protected $requestedIncludes = [];
33
34
    /**
35
     * Array of scope identifiers for resources to exclude.
36
     *
37
     * @var array
38
     */
39
    protected $requestedExcludes = [];
40
41
    /**
42
     * Array of requested fieldsets.
43
     *
44
     * @var array
45
     */
46
    protected $requestedFieldsets = [];
47
48
    /**
49
     * Array containing modifiers as keys and an array value of params.
50
     *
51
     * @var array
52
     */
53
    protected $includeParams = [];
54
55
    /**
56
     * The character used to separate modifier parameters.
57
     *
58
     * @var string
59
     */
60
    protected $paramDelimiter = '|';
61
62
    /**
63
     * Upper limit to how many levels of included data are allowed.
64
     *
65
     * @var int
66
     */
67
    protected $recursionLimit = 10;
68
69
    /**
70
     * Serializer.
71
     *
72
     * @var SerializerAbstract
73
     */
74
    protected $serializer;
75
76
    /**
77
     * Factory used to create new configured scopes.
78
     *
79
     * @var ScopeFactoryInterface
80
     */
81
    private $scopeFactory;
82
83 86
    public function __construct(ScopeFactoryInterface $scopeFactory = null)
84
    {
85 86
        $this->scopeFactory = $scopeFactory ?: new ScopeFactory();
86 86
    }
87
88
    /**
89
     * Create Data.
90
     *
91
     * Main method to kick this all off. Make a resource then pass it over, and use toArray()
92
     *
93
     * @param ResourceInterface $resource
94
     * @param string            $scopeIdentifier
95
     * @param Scope             $parentScopeInstance
96
     *
97
     * @return Scope
98
     */
99 42
    public function createData(ResourceInterface $resource, $scopeIdentifier = null, Scope $parentScopeInstance = null)
100
    {
101 42
        if ($parentScopeInstance !== null) {
102 36
            return $this->scopeFactory->createChildScopeFor($this, $parentScopeInstance, $resource, $scopeIdentifier);
103
        }
104
105 6
        return $this->scopeFactory->createScopeFor($this, $resource, $scopeIdentifier);
106
    }
107
108
    /**
109
     * Get Include Params.
110
     *
111
     * @param string $include
112
     *
113
     * @return \League\Fractal\ParamBag
114
     */
115 32
    public function getIncludeParams($include)
116
    {
117 32
        $params = isset($this->includeParams[$include]) ? $this->includeParams[$include] : [];
118
119 32
        return new ParamBag($params);
120
    }
121
122
    /**
123
     * Get Requested Includes.
124
     *
125
     * @return array
126
     */
127 46
    public function getRequestedIncludes()
128
    {
129 46
        return $this->requestedIncludes;
130
    }
131
132
    /**
133
     * Get Requested Excludes.
134
     *
135
     * @return array
136
     */
137 33
    public function getRequestedExcludes()
138
    {
139 33
        return $this->requestedExcludes;
140
    }
141
142
    /**
143
     * Get Serializer.
144
     *
145
     * @return SerializerAbstract
146
     */
147 60
    public function getSerializer()
148
    {
149 60
        if (! $this->serializer) {
150 10
            $this->setSerializer(new DataArraySerializer());
151 10
        }
152
153 60
        return $this->serializer;
154
    }
155
156
    /**
157
     * Parse Include String.
158
     *
159
     * @param array|string $includes Array or csv string of resources to include
160
     *
161
     * @return $this
162
     */
163 44
    public function parseIncludes($includes)
164
    {
165
        // Wipe these before we go again
166 44
        $this->requestedIncludes = $this->includeParams = [];
167 44
        $subRelations = '';
168
169 44
        if (is_string($includes)) {
170 34
            $includes = explode(',', $includes);
171 34
        }
172
173 44
        if (! is_array($includes)) {
174 2
            throw new \InvalidArgumentException(
175 2
                'The parseIncludes() method expects a string or an array. '.gettype($includes).' given'
176 2
            );
177
        }
178
179 42
        foreach ($includes as $include) {
180 42
            list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null);
181 42
            list($allModifiersStr, $subRelations) = array_pad(explode('.', $allModifiersStr, 2), 2, null);
182
183
            // Trim it down to a cool level of recursion
184 42
            $includeName = $this->trimToAcceptableRecursionLevel($includeName);
185
186 42
            if (in_array($includeName, $this->requestedIncludes)) {
187 1
                continue;
188
            }
189 42
            $this->requestedIncludes[] = $includeName;
190
191
            // No Params? Bored
192 42
            if ($allModifiersStr === null) {
193
                continue;
194
            }
195
196
            // Matches multiple instances of 'something(foo|bar|baz)' in the string
197
            // I guess it ignores : so you could use anything, but probably don't do that
198 42
            preg_match_all('/([\w]+)(\(([^\)]+)\))?/', $allModifiersStr, $allModifiersArr);
199
200
            // [0] is full matched strings...
201 42
            $modifierCount = count($allModifiersArr[0]);
202
203 42
            $modifierArr = [];
204
205 42
            for ($modifierIt = 0; $modifierIt < $modifierCount; $modifierIt++) {
206
                // [1] is the modifier
207 3
                $modifierName = $allModifiersArr[1][$modifierIt];
208
209
                // and [3] is delimited params
210 3
                $modifierParamStr = $allModifiersArr[3][$modifierIt];
211
212
                // Make modifier array key with an array of params as the value
213 3
                $modifierArr[$modifierName] = explode($this->paramDelimiter, $modifierParamStr);
214 3
            }
215
216 42
            $this->includeParams[$includeName] = $modifierArr;
217
218 42
            if ($subRelations) {
219 2
                $this->requestedIncludes[] = $this->trimToAcceptableRecursionLevel($includeName . '.' . $subRelations);
220 2
            }
221 42
        }
222
223
        // This should be optional and public someday, but without it includes would never show up
224 42
        $this->autoIncludeParents();
225
226 42
        return $this;
227
    }
228
229
    /**
230
     * Parse field parameter.
231
     *
232
     * @param array $fieldsets Array of fields to include. It must be an array whose keys
233
     *                         are resource types and values an array or a string
234
     *                         of the fields to return, separated by a comma
235
     *
236
     * @return $this
237
     */
238 13
    public function parseFieldsets(array $fieldsets)
239
    {
240 13
        $this->requestedFieldsets = [];
241 13
        foreach ($fieldsets as $type => $fields) {
242 13
            if (is_string($fields)) {
243 13
                $fields = explode(',', $fields);
244 13
            }
245
246
            //Remove empty and repeated fields
247 13
            $this->requestedFieldsets[$type] = array_unique(array_filter($fields));
248 13
        }
249 13
        return $this;
250
    }
251
252
    /**
253
     * Get requested fieldsets.
254
     *
255
     * @return array
256
     */
257 1
    public function getRequestedFieldsets()
258
    {
259 1
        return $this->requestedFieldsets;
260
    }
261
262
    /**
263
     * Get fieldset params for the specified type.
264
     *
265
     * @param string $type
266
     *
267
     * @return \League\Fractal\ParamBag|null
268
     */
269 59
    public function getFieldset($type)
270
    {
271 59
        return !isset($this->requestedFieldsets[$type]) ?
272 59
            null :
273 59
            new ParamBag($this->requestedFieldsets[$type]);
274
    }
275
276
    /**
277
     * Parse Exclude String.
278
     *
279
     * @param array|string $excludes Array or csv string of resources to exclude
280
     *
281
     * @return $this
282
     */
283 5
    public function parseExcludes($excludes)
284
    {
285 5
        $this->requestedExcludes = [];
286
287 5
        if (is_string($excludes)) {
288 2
            $excludes = explode(',', $excludes);
289 2
        }
290
291 5
        if (! is_array($excludes)) {
292 2
            throw new \InvalidArgumentException(
293 2
                'The parseExcludes() method expects a string or an array. '.gettype($excludes).' given'
294 2
            );
295
        }
296
297 3
        foreach ($excludes as $excludeName) {
298 3
            $excludeName = $this->trimToAcceptableRecursionLevel($excludeName);
299
300 3
            if (in_array($excludeName, $this->requestedExcludes)) {
301 1
                continue;
302
            }
303
304 3
            $this->requestedExcludes[] = $excludeName;
305 3
        }
306
307 3
        return $this;
308
    }
309
310
    /**
311
     * Set Recursion Limit.
312
     *
313
     * @param int $recursionLimit
314
     *
315
     * @return $this
316
     */
317 1
    public function setRecursionLimit($recursionLimit)
318
    {
319 1
        $this->recursionLimit = $recursionLimit;
320
321 1
        return $this;
322
    }
323
324
    /**
325
     * Set Serializer
326
     *
327
     * @param SerializerAbstract $serializer
328
     *
329
     * @return $this
330
     */
331 60
    public function setSerializer(SerializerAbstract $serializer)
332
    {
333 60
        $this->serializer = $serializer;
334
335 60
        return $this;
336
    }
337
338
    /**
339
     * Auto-include Parents
340
     *
341
     * Look at the requested includes and automatically include the parents if they
342
     * are not explicitly requested. E.g: [foo, bar.baz] becomes [foo, bar, bar.baz]
343
     *
344
     * @internal
345
     *
346
     * @return void
347
     */
348 42
    protected function autoIncludeParents()
349
    {
350 42
        $parsed = [];
351
352 42
        foreach ($this->requestedIncludes as $include) {
353 42
            $nested = explode('.', $include);
354
355 42
            $part = array_shift($nested);
356 42
            $parsed[] = $part;
357
358 42
            while (count($nested) > 0) {
359 11
                $part .= '.'.array_shift($nested);
360 11
                $parsed[] = $part;
361 11
            }
362 42
        }
363
364 42
        $this->requestedIncludes = array_values(array_unique($parsed));
365 42
    }
366
367
    /**
368
     * Trim to Acceptable Recursion Level
369
     *
370
     * Strip off any requested resources that are too many levels deep, to avoid DiCaprio being chased
371
     * by trains or whatever the hell that movie was about.
372
     *
373
     * @internal
374
     *
375
     * @param string $includeName
376
     *
377
     * @return string
378
     */
379 44
    protected function trimToAcceptableRecursionLevel($includeName)
380
    {
381 44
        return implode('.', array_slice(explode('.', $includeName), 0, $this->recursionLimit));
382
    }
383
}
384