Test Failed
Pull Request — master (#73)
by Alex
03:30
created

LaravelQuery::getRelatedResourceSet()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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