Test Setup Failed
Pull Request — master (#53)
by Alex
12:38
created

LaravelQuery::getRelatedResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 0
cts 10
cp 0
rs 9.0856
c 0
b 0
f 0
cc 1
eloc 20
nc 1
nop 9
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use AlgoWeb\PODataLaravel\Enums\ActionVerb;
6
use Illuminate\Database\Eloquent\Relations\Relation;
7
use POData\Providers\Metadata\ResourceProperty;
8
use POData\Providers\Metadata\ResourceSet;
9
use POData\UriProcessor\QueryProcessor\Expression\Parser\IExpressionProvider;
10
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
11
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
12
use POData\Providers\Query\IQueryProvider;
13
use POData\Providers\Expression\MySQLExpressionProvider;
14
use POData\Providers\Query\QueryType;
15
use POData\Providers\Query\QueryResult;
16
use POData\Providers\Expression\PHPExpressionProvider;
17
use \POData\Common\ODataException;
18
use AlgoWeb\PODataLaravel\Interfaces\AuthInterface;
19
use AlgoWeb\PODataLaravel\Auth\NullAuthProvider;
20
use Illuminate\Support\Facades\App;
21
use Illuminate\Database\Eloquent\Model;
22
use Symfony\Component\Process\Exception\InvalidArgumentException;
23
24
class LaravelQuery implements IQueryProvider
25
{
26 5
    protected $expression;
27
    protected $auth;
28
    protected $reader;
29 5
    public $queryProviderClassName;
30 5
    private $verbMap = [];
31 5
32 5
    public function __construct(AuthInterface $auth = null)
33
    {
34
        /* MySQLExpressionProvider();*/
35
        $this->expression = new LaravelExpressionProvider(); //PHPExpressionProvider('expression');
36
        $this->queryProviderClassName = get_class($this);
37
        $this->auth = isset($auth) ? $auth : new NullAuthProvider();
38
        $this->reader = new LaravelReadQuery($this->auth);
39
        $this->verbMap['create'] = ActionVerb::CREATE();
40
        $this->verbMap['update'] = ActionVerb::UPDATE();
41
        $this->verbMap['delete'] = ActionVerb::DELETE();
42
    }
43
44
    /**
45
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
46
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
47
     * perform the ordering and paging
48
     *
49
     * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
50
     */
51
    public function handlesOrderedPaging()
52
    {
53
        return true;
54
    }
55
56
    /**
57
     * Gets the expression provider used by to compile OData expressions into expression used by this query provider.
58
     *
59
     * @return \POData\Providers\Expression\IExpressionProvider
60
     */
61
    public function getExpressionProvider()
62
    {
63
        return $this->expression;
64
    }
65
66
    /**
67
     * Gets the LaravelReadQuery instance used to handle read queries (repetitious, nyet?)
68
     *
69
     * @return LaravelReadQuery
70 3
     */
71
    public function getReader()
72
    {
73
        return $this->reader;
74
    }
75
76
    /**
77
     * Gets collection of entities belongs to an entity set
78
     * IE: http://host/EntitySet
79 3
     *  http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value
80
     *
81
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
82 3
     * @param ResourceSet $resourceSet The entity set containing the entities to fetch
83 1
     * @param FilterInfo $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
84 1
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
85
     * @param int $top number of records which  need to be skip
86 3
     * @param String $skipToken value indicating what records to skip
87 3
     * @param Model|Relation|null $sourceEntityInstance Starting point of query
88 3
     *
89
     * @return QueryResult
90 3
     */
91
    public function getResourceSet(
92
        QueryType $queryType,
93
        ResourceSet $resourceSet,
94
        $filterInfo = null,
95
        $orderBy = null,
96
        $top = null,
97 1
        $skipToken = null,
98
        $sourceEntityInstance = null
99
    ) {
100 3
        return $this->getReader()->getResourceSet(
101 1
            $queryType,
102 1
            $resourceSet,
103 3
            $filterInfo,
104 1
            $orderBy,
105 1
            $top,
106
            $skipToken,
107 3
            $sourceEntityInstance
108
        );
109 3
    }
110
    /**
111
     * Gets an entity instance from an entity set identified by a key
112
     * IE: http://host/EntitySet(1L)
113
     * http://host/EntitySet(KeyA=2L,KeyB='someValue')
114
     *
115
     * @param ResourceSet $resourceSet The entity set containing the entity to fetch
116
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
117 3
     *
118 1
     * @return object|null Returns entity instance if found else null
119 1
     */
120 1
    public function getResourceFromResourceSet(
121 1
        ResourceSet $resourceSet,
122 1
        KeyDescriptor $keyDescriptor = null
123 3
    ) {
124 3
        return $this->getReader()->getResourceFromResourceSet($resourceSet, $keyDescriptor);
125
    }
126
127 3
    /**
128 3
     * Get related resource set for a resource
129 3
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection
130
     * http://host/EntitySet?$expand=NavigationPropertyToCollection
131
     *
132
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
133
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
134
     * @param object $sourceEntityInstance The source entity instance.
135
     * @param ResourceSet $targetResourceSet The resource set of containing the target of the navigation property
136
     * @param ResourceProperty $targetProperty The navigation property to retrieve
137
     * @param FilterInfo $filter represents the $filter parameter of the OData query.  NULL if no $filter specified
138
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
139
     * @param int $top number of records which  need to be skip
140
     * @param String $skip value indicating what records to skip
141
     *
142
     * @return QueryResult
143
     *
144
     */
145
    public function getRelatedResourceSet(
146
        QueryType $queryType,
147
        ResourceSet $sourceResourceSet,
148
        $sourceEntityInstance,
149
        ResourceSet $targetResourceSet,
150
        ResourceProperty $targetProperty,
151
        $filter = null,
152
        $orderBy = null,
153
        $top = null,
154
        $skip = null
155
    ) {
156
        return $this->getReader()->getRelatedResourceSet(
157
            $queryType,
158
            $sourceResourceSet,
159
            $sourceEntityInstance,
160
            $targetResourceSet,
161
            $targetProperty,
162
            $filter,
163
            $orderBy,
164
            $top,
165
            $skip
166
        );
167
    }
168
169
    /**
170
     * Gets a related entity instance from an entity set identified by a key
171
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33)
172
     *
173
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
174
     * @param object $sourceEntityInstance The source entity instance.
175
     * @param ResourceSet $targetResourceSet The entity set containing the entity to fetch
176
     * @param ResourceProperty $targetProperty The metadata of the target property.
177
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
178
     *
179
     * @return object|null Returns entity instance if found else null
180
     */
181
    public function getResourceFromRelatedResourceSet(
182
        ResourceSet $sourceResourceSet,
183
        $sourceEntityInstance,
184
        ResourceSet $targetResourceSet,
185
        ResourceProperty $targetProperty,
186
        KeyDescriptor $keyDescriptor
187
    ) {
188
        return $this->getReader()->getResourceFromRelatedResourceSet(
189
            $sourceResourceSet,
190
            $sourceEntityInstance,
191
            $targetResourceSet,
192
            $targetProperty,
193
            $keyDescriptor
194
        );
195
    }
196
197
    /**
198
     * Get related resource for a resource
199 3
     * IE: http://host/EntitySet(1L)/NavigationPropertyToSingleEntity
200
     * http://host/EntitySet?$expand=NavigationPropertyToSingleEntity
201
     *
202
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
203
     * @param object $sourceEntityInstance The source entity instance.
204
     * @param ResourceSet $targetResourceSet The entity set containing the entity pointed to by the navigation property
205
     * @param ResourceProperty $targetProperty The navigation property to fetch
206
     *
207
     * @return object|null The related resource if found else null
208
     */
209
    public function getRelatedResourceReference(
210 3
        ResourceSet $sourceResourceSet,
211 3
        $sourceEntityInstance,
212
        ResourceSet $targetResourceSet,
213 3
        ResourceProperty $targetProperty
214 3
    ) {
215 3
        return $this->getReader()->getRelatedResourceReference(
216 3
            $sourceResourceSet,
217 3
            $sourceEntityInstance,
218 3
            $targetResourceSet,
219 3
            $targetProperty
220
        );
221 3
    }
222
223
    /**
224
     * Updates a resource
225
     *
226
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
227
     * @param object           $sourceEntityInstance The source entity instance
228
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
229
     * @param object           $data                 The New data for the entity instance.
230
     * @param bool             $shouldUpdate        Should undefined values be updated or reset to default
231
     *
232
     * @return object|null The new resource value if it is assignable or throw exception for null.
233
     */
234
    public function updateResource(
235
        ResourceSet $sourceResourceSet,
236
        $sourceEntityInstance,
237
        KeyDescriptor $keyDescriptor,
238
        $data,
239
        $shouldUpdate = false
240
    ) {
241
        $verb = 'update';
242
        return $this->createUpdateCoreWrapper($sourceResourceSet, $sourceEntityInstance, $data, $verb);
243
    }
244
    /**
245
     * Delete resource from a resource set.
246
     * @param ResourceSet|null $sourceResourceSet
247
     * @param object           $sourceEntityInstance
248
     *
249
     * return bool true if resources sucessfully deteled, otherwise false.
250
     */
251
    public function deleteResource(
252
        ResourceSet $sourceResourceSet,
253
        $sourceEntityInstance
254
    ) {
255
        $verb = 'delete';
256
        if (!($sourceEntityInstance instanceof Model)) {
257
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
258
        }
259
260
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
261
        $id = $sourceEntityInstance->getKey();
262
        $name = $sourceEntityInstance->getKeyName();
263
        $data = [$name => $id];
264
265
        $data = $this->createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb);
266
267
        $success = isset($data['id']);
268
        if ($success) {
269
            return true;
270
        }
271
        throw new ODataException('Target model not successfully deleted', 422);
272
    }
273
    /**
274
     * @param ResourceSet      $resourceSet   The entity set containing the entity to fetch
275
     * @param object           $sourceEntityInstance The source entity instance
276
     * @param object           $data                 The New data for the entity instance.
277
     *
278
     * returns object|null returns the newly created model if sucessful or null if model creation failed.
279
     */
280
    public function createResourceforResourceSet(
281
        ResourceSet $resourceSet,
282
        $sourceEntityInstance,
283
        $data
284
    ) {
285
        $verb = 'create';
286
        return $this->createUpdateCoreWrapper($resourceSet, $sourceEntityInstance, $data, $verb);
287
    }
288
289
    /**
290
     * @param $sourceEntityInstance
291
     * @param $data
292 1
     * @param $class
293
     * @param string $verb
294
     * @return array|mixed
295
     * @throws ODataException
296
     * @throws \POData\Common\InvalidOperationException
297
     */
298
    private function createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb)
299 1
    {
300 1
        $raw = App::make('metadataControllers');
301
        $map = $raw->getMetadata();
302 1
303
        if (!array_key_exists($class, $map)) {
304 1
            throw new \POData\Common\InvalidOperationException('Controller mapping missing for class '.$class.'.');
305
        }
306 1
        $goal = $raw->getMapping($class, $verb);
307
        if (null == $goal) {
308
            throw new \POData\Common\InvalidOperationException(
309 1
                'Controller mapping missing for '.$verb.' verb on class '.$class.'.'
310
            );
311
        }
312
313
        assert($data != null, "Data must not be null");
314
        if (is_object($data)) {
315
            $data = (array) $data;
316
        }
317
        if (!is_array($data)) {
318 1
            throw \POData\Common\ODataException::createPreConditionFailedError(
319
                'Data not resolvable to key-value array.'
320
            );
321
        }
322 1
323 1
        $controlClass = $goal['controller'];
324 1
        $method = $goal['method'];
325 1
        $paramList = $goal['parameters'];
326 1
        $controller = App::make($controlClass);
327
        $parms = $this->createUpdateDeleteProcessInput($sourceEntityInstance, $data, $paramList);
328 1
        unset($data);
329
330 1
        $result = call_user_func_array(array($controller, $method), $parms);
331 1
332
        return $this->createUpdateDeleteProcessOutput($result);
333
    }
334 1
335
    /**
336
     * Puts an entity instance to entity set identified by a key.
337
     *
338
     * @param ResourceSet $resourceSet The entity set containing the entity to update
339
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
340
     * @param $data
341
     *
342
     * @return bool|null Returns result of executing query
343 1
     */
344
    public function putResource(
345
        ResourceSet $resourceSet,
346
        KeyDescriptor $keyDescriptor,
347
        $data
348 1
    ) {
349 1
        // TODO: Implement putResource() method.
350
        return true;
351 1
    }
352
353 1
    /**
354
     * @param ResourceSet $sourceResourceSet
355 1
     * @param $sourceEntityInstance
356
     * @param $data
357
     * @param $verb
358 1
     * @return mixed
359
     * @throws ODataException
360
     * @throws \POData\Common\InvalidOperationException
361
     */
362
    private function createUpdateCoreWrapper(ResourceSet $sourceResourceSet, $sourceEntityInstance, $data, $verb)
363
    {
364
        $lastWord = 'update' == $verb ? 'updated' : 'created';
365
        if (!(null == $sourceEntityInstance || $sourceEntityInstance instanceof Model)) {
366
            throw new InvalidArgumentException('Source entity must either be null or an Eloquent model.');
367
        }
368
369
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
370 3
        if (!$this->auth->canAuth($this->verbMap[$verb], $class, $sourceEntityInstance)) {
371
            throw new ODataException("Access denied", 403);
372 3
        }
373 3
374
        $data = $this->createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb);
375 3
376
        $success = isset($data['id']);
377
378 3
        if ($success) {
379 3
            try {
380
                return $class::findOrFail($data['id']);
381
            } catch (\Exception $e) {
382
                throw new ODataException($e->getMessage(), 500);
383
            }
384
        }
385 3
        throw new ODataException('Target model not successfully '.$lastWord, 422);
386
    }
387
388 3
    /**
389 2
     * @param $sourceEntityInstance
390 2
     * @param $data
391 3
     * @param $paramList
392
     * @return array
393
     */
394
    private function createUpdateDeleteProcessInput($sourceEntityInstance, $data, $paramList)
395
    {
396
        $parms = [];
397 3
398 3
        foreach ($paramList as $spec) {
399 3
            $varType = isset($spec['type']) ? $spec['type'] : null;
400 3
            $varName = $spec['name'];
401 3
            if (null == $varType) {
402
                $parms[] = $sourceEntityInstance->$varName;
403 3
                continue;
404 3
            }
405 3
            // TODO: Give this smarts and actively pick up instantiation details
406 3
            $var = new $varType();
407 2
            if ($spec['isRequest']) {
408 2
                $var->setMethod('POST');
409 2
                $var->request = new \Symfony\Component\HttpFoundation\ParameterBag($data);
410 2
            }
411 2
            $parms[] = $var;
412
        }
413
        return $parms;
414
    }
415 2
416
    /**
417
     * @param $result
418 3
     * @return array|mixed
419 3
     * @throws ODataException
420
     */
421 3
    private function createUpdateDeleteProcessOutput($result)
422
    {
423 3
        if (!($result instanceof \Illuminate\Http\JsonResponse)) {
424
            throw ODataException::createInternalServerError('Controller response not well-formed json.');
425
        }
426 3
        $outData = $result->getData();
427 3
        if (is_object($outData)) {
428 3
            $outData = (array)$outData;
429 3
        }
430
431 3
        if (!is_array($outData)) {
432
            throw ODataException::createInternalServerError('Controller response does not have an array.');
433
        }
434 3
        if (!(key_exists('id', $outData) && key_exists('status', $outData) && key_exists('errors', $outData))) {
435
            throw ODataException::createInternalServerError(
436
                'Controller response array missing at least one of id, status and/or errors fields.'
437
            );
438
        }
439 3
        return $outData;
440
    }
441
}
442