Test Setup Failed
Pull Request — master (#36)
by Alex
11:38
created

LaravelQuery::createUpdateDeleteCore()   C

Complexity

Conditions 16
Paths 202

Size

Total Lines 71
Code Lines 46

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 18.4426

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 71
ccs 26
cts 33
cp 0.7879
rs 5.0723
cc 16
eloc 46
nc 202
nop 4
crap 18.4426

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use POData\Providers\Metadata\ResourceProperty;
6
use POData\Providers\Metadata\ResourceSet;
7
use POData\UriProcessor\QueryProcessor\Expression\Parser\IExpressionProvider;
8
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
9
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
10
use POData\Providers\Query\IQueryProvider;
11
use POData\Providers\Expression\MySQLExpressionProvider;
12
use POData\Providers\Query\QueryType;
13
use POData\Providers\Query\QueryResult;
14
use POData\Providers\Expression\PHPExpressionProvider;
15
use \POData\Common\ODataException;
16
use AlgoWeb\PODataLaravel\Interfaces\AuthInterface;
17
use AlgoWeb\PODataLaravel\Auth\NullAuthProvider;
18
use Illuminate\Support\Facades\App;
19
use Illuminate\Database\Eloquent\Model;
20
use Symfony\Component\Process\Exception\InvalidArgumentException;
21
22
class LaravelQuery implements IQueryProvider
23
{
24
    protected $expression;
25
    protected $auth;
26 5
    public $queryProviderClassName;
27
28
    public function __construct(AuthInterface $auth = null)
29 5
    {
30 5
        /* MySQLExpressionProvider();*/
31 5
        $this->expression = new LaravelExpressionProvider(); //PHPExpressionProvider('expression');
32 5
        $this->queryProviderClassName = get_class($this);
33
        $this->auth = isset($auth) ? $auth : new NullAuthProvider();
34
    }
35
36
    /**
37
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
38
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
39
     * perform the ordering and paging
40
     *
41
     * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
42
     */
43
    public function handlesOrderedPaging()
44
    {
45
        return true;
46
    }
47
48
    /**
49
     * Gets the expression provider used by to compile OData expressions into expression used by this query provider.
50
     *
51
     * @return \POData\Providers\Expression\IExpressionProvider
52
     */
53
    public function getExpressionProvider()
54
    {
55
        return $this->expression;
56
    }
57
58
    /**
59
     * Gets collection of entities belongs to an entity set
60
     * IE: http://host/EntitySet
61
     *  http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value
62
     *
63
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
64
     * @param ResourceSet $resourceSet The entity set containing the entities to fetch
65
     * @param FilterInfo $filterInfo represents the $filter parameter of the OData query.  NULL if no $filter specified
66
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
67
     * @param int $top number of records which  need to be skip
68
     * @param String $skipToken value indicating what records to skip
69
     *
70 3
     * @return QueryResult
71
     */
72
    public function getResourceSet(
73
        QueryType $queryType,
74
        ResourceSet $resourceSet,
75
        $filterInfo = null,
76
        $orderBy = null,
77
        $top = null,
78
        $skipToken = null,
79 3
        Model $sourceEntityInstance = null
80
    ) {
81
        if (null != $filterInfo && !($filterInfo instanceof FilterInfo)) {
82 3
            throw new InvalidArgumentException('Filter info must be either null or instance of FilterInfo.');
83 1
        }
84 1
85
        if (null == $sourceEntityInstance) {
86 3
            $sourceEntityInstance = $this->getSourceEntityInstance($resourceSet);
87 3
        }
88 3
89
        $result          = new QueryResult();
90 3
        $result->results = null;
91
        $result->count   = null;
92
93
        if (null != $orderBy) {
94
            foreach ($orderBy->getOrderByInfo()->getOrderByPathSegments() as $order) {
95
                foreach ($order->getSubPathSegments() as $subOrder) {
96
                    $sourceEntityInstance = $sourceEntityInstance->orderBy(
97 1
                        $subOrder->getName(),
98
                        $order->isAscending() ? 'asc' : 'desc'
99
                    );
100 3
                }
101 1
            }
102 1
        }
103 3
104 1
        $resultSet = $sourceEntityInstance->get();
105 1
106
        if (isset($filterInfo)) {
107 3
            $method = "return ".$filterInfo->getExpressionAsString().";";
108
            $clln = "$".$resourceSet->getResourceType()->getName();
109 3
            $isvalid = create_function($clln, $method);
0 ignored issues
show
Security Best Practice introduced by
The use of create_function is highly discouraged, better use a closure.

create_function can pose a great security vulnerability as it is similar to eval, and could be used for arbitrary code execution. We highly recommend to use a closure instead.

// Instead of
$function = create_function('$a, $b', 'return $a + $b');

// Better use
$function = function($a, $b) { return $a + $b; }
Loading history...
110
            $resultSet = $resultSet->filter($isvalid);
111
        }
112
113
        $resultCount = $resultSet->count();
114
115
        if (isset($skipToken)) {
116
            $resultSet = $resultSet->slice($skipToken);
117 3
        }
118 1
        if (isset($top)) {
119 1
            $resultSet = $resultSet->take($top);
120 1
        }
121 1
122 1
123 3
        if (QueryType::ENTITIES() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
124 3
            $result->results = array();
125
            foreach ($resultSet as $res) {
126
                $result->results[] = $res;
127 3
            }
128 3
        }
129 3
        if (QueryType::COUNT() == $queryType || QueryType::ENTITIES_WITH_COUNT() == $queryType) {
130
            $result->count = $resultCount;
131
        }
132
        return $result;
133
    }
134
    /**
135
     * Gets an entity instance from an entity set identified by a key
136
     * IE: http://host/EntitySet(1L)
137
     * http://host/EntitySet(KeyA=2L,KeyB='someValue')
138
     *
139
     * @param ResourceSet $resourceSet The entity set containing the entity to fetch
140
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
141
     *
142
     * @return object|null Returns entity instance if found else null
143
     */
144
    public function getResourceFromResourceSet(
145
        ResourceSet $resourceSet,
146
        KeyDescriptor $keyDescriptor = null
147
    ) {
148
        return $this->getResource($resourceSet, $keyDescriptor);
149
    }
150
151
    /**
152
     * Common method for getResourceFromRelatedResourceSet() and getResourceFromResourceSet()
153
     * @param ResourceSet|null $resourceSet
154
     * @param null|KeyDescriptor $keyDescriptor
155
     */
156
    protected function getResource(
157
        ResourceSet $resourceSet = null,
158
        KeyDescriptor $keyDescriptor = null,
159
        array $whereCondition = [],
160
        Model $sourceEntityInstance = null
161
    ) {
162
        if (null == $resourceSet && null == $sourceEntityInstance) {
163
            throw new \Exception('Must supply at least one of a resource set and source entity');
164
        }
165
166
        if (null == $sourceEntityInstance) {
167
            $entityClassName = $resourceSet->getResourceType()->getInstanceType()->name;
0 ignored issues
show
Bug introduced by
It seems like $resourceSet is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
168
            $sourceEntityInstance = App::make($entityClassName);
169
        }
170
171
        if ($keyDescriptor) {
172
            foreach ($keyDescriptor->getValidatedNamedValues() as $key => $value) {
173
                $trimValue = trim($value[0], "\"'");
174
                $sourceEntityInstance = $sourceEntityInstance->where($key, $trimValue);
175
            }
176
        }
177
        foreach ($whereCondition as $fieldName => $fieldValue) {
178
            $sourceEntityInstance = $sourceEntityInstance->where($fieldName, $fieldValue);
179
        }
180
        $sourceEntityInstance = $sourceEntityInstance->get();
181
        return (0 == $sourceEntityInstance->count()) ? null : $sourceEntityInstance->first();
182
    }
183
184
    /**
185
     * Get related resource set for a resource
186
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection
187
     * http://host/EntitySet?$expand=NavigationPropertyToCollection
188
     *
189
     * @param QueryType $queryType indicates if this is a query for a count, entities, or entities with a count
190
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
191
     * @param object $sourceEntityInstance The source entity instance.
192
     * @param ResourceSet $targetResourceSet The resource set of containing the target of the navigation property
193
     * @param ResourceProperty $targetProperty The navigation property to retrieve
194
     * @param FilterInfo $filter represents the $filter parameter of the OData query.  NULL if no $filter specified
195
     * @param mixed $orderBy sorted order if we want to get the data in some specific order
196
     * @param int $top number of records which  need to be skip
197
     * @param String $skip value indicating what records to skip
198
     *
199 3
     * @return QueryResult
200
     *
201
     */
202
    public function getRelatedResourceSet(
203
        QueryType $queryType,
204
        ResourceSet $sourceResourceSet,
205
        $sourceEntityInstance,
206
        ResourceSet $targetResourceSet,
207
        ResourceProperty $targetProperty,
208
        $filter = null,
209
        $orderBy = null,
210 3
        $top = null,
211 3
        $skip = null
212
    ) {
213 3
        if (!($sourceEntityInstance instanceof Model)) {
214 3
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
215 3
        }
216 3
217 3
        $propertyName = $targetProperty->getName();
218 3
        $results = $sourceEntityInstance->$propertyName();
219 3
        $relatedClass = $results->getRelated();
220
        $sourceEntityInstance = new $relatedClass();
221 3
222
        return $this->getResourceSet(
223
            $queryType,
224
            $sourceResourceSet,
225
            $filter,
226
            $orderBy,
227
            $top,
228
            $skip,
229
            $sourceEntityInstance
230
        );
231
    }
232
233
    /**
234
     * Gets a related entity instance from an entity set identified by a key
235
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33)
236
     *
237
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
238
     * @param object $sourceEntityInstance The source entity instance.
239
     * @param ResourceSet $targetResourceSet The entity set containing the entity to fetch
240
     * @param ResourceProperty $targetProperty The metadata of the target property.
241
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to fetch
242
     *
243
     * @return object|null Returns entity instance if found else null
244
     */
245
    public function getResourceFromRelatedResourceSet(
246
        ResourceSet $sourceResourceSet,
247
        $sourceEntityInstance,
248
        ResourceSet $targetResourceSet,
249
        ResourceProperty $targetProperty,
250
        KeyDescriptor $keyDescriptor
251
    ) {
252
        if (!($sourceEntityInstance instanceof Model)) {
253
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
254
        }
255
        $propertyName = $targetProperty->getName();
256
        return $this->getResource(null, $keyDescriptor, [], $sourceEntityInstance->$propertyName);
257
    }
258
259
    /**
260
     * Get related resource for a resource
261
     * IE: http://host/EntitySet(1L)/NavigationPropertyToSingleEntity
262
     * http://host/EntitySet?$expand=NavigationPropertyToSingleEntity
263
     *
264
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
265
     * @param object $sourceEntityInstance The source entity instance.
266
     * @param ResourceSet $targetResourceSet The entity set containing the entity pointed to by the navigation property
267
     * @param ResourceProperty $targetProperty The navigation property to fetch
268
     *
269
     * @return object|null The related resource if found else null
270
     */
271
    public function getRelatedResourceReference(
272
        ResourceSet $sourceResourceSet,
273
        $sourceEntityInstance,
274
        ResourceSet $targetResourceSet,
275
        ResourceProperty $targetProperty
276
    ) {
277
        if (!($sourceEntityInstance instanceof Model)) {
278
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
279
        }
280
        $propertyName = $targetProperty->getName();
281
        return $sourceEntityInstance->$propertyName;
282
    }
283
284
    /**
285
     * @param ResourceSet $resourceSet
286
     * @return mixed
287
     */
288
    protected function getSourceEntityInstance(ResourceSet $resourceSet)
289
    {
290
        $entityClassName = $resourceSet->getResourceType()->getInstanceType()->name;
291
        $sourceEntityInstance = new $entityClassName();
292 1
        return $sourceEntityInstance = $sourceEntityInstance->newQuery();
0 ignored issues
show
Unused Code introduced by
$sourceEntityInstance is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
293
    }
294
295
    /**
296
     * Updates a resource
297
     *
298
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
299 1
     * @param object           $sourceEntityInstance The source entity instance
300 1
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
301
     * @param object           $data                 The New data for the entity instance.
302 1
     * @param bool             $shouldUpdate        Should undefined values be updated or reset to default
303
     *
304 1
     * @return object|null The new resource value if it is assignable or throw exception for null.
305
     */
306 1 View Code Duplication
    public function updateResource(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
307
        ResourceSet $sourceResourceSet,
308
        $sourceEntityInstance,
309 1
        KeyDescriptor $keyDescriptor,
310
        $data,
311
        $shouldUpdate = false
312
    ) {
313
        if (!(null == $sourceEntityInstance || $sourceEntityInstance instanceof Model)) {
314
            throw new InvalidArgumentException('Source entity must either be null or an Eloquent model.');
315
        }
316
        $verb = 'update';
317
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->name;
318 1
319
        $data = $this->createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb);
320
321
        $success = isset($data['id']);
322 1
323 1
        if ($success) {
324 1
            return $class::findOrFail($data['id']);
325 1
        }
326 1
        throw new ODataException('Target model not successfully updated', 422);
327
    }
328 1
    /**
329
     * Delete resource from a resource set.
330 1
     * @param ResourceSet|null $sourceResourceSet
331 1
     * @param object           $sourceEntityInstance
332
     *
333
     * return bool true if resources sucessfully deteled, otherwise false.
334 1
     */
335
    public function deleteResource(
336
        ResourceSet $sourceResourceSet,
337
        $sourceEntityInstance
338
    ) {
339
        if (!($sourceEntityInstance instanceof Model)) {
340
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
341
        }
342
        $verb = 'delete';
343 1
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->name;
344
        $id = $sourceEntityInstance->getKey();
345
        $name = $sourceEntityInstance->getKeyName();
346
        $data = [$name => $id];
347
348 1
        $data = $this->createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb);
349 1
350
        $success = isset($data['id']);
351 1
        if ($success) {
352
            return true;
353 1
        }
354
        throw new ODataException('Target model not successfully deleted', 422);
355 1
    }
356
    /**
357
     * @param ResourceSet      $resourceSet   The entity set containing the entity to fetch
358 1
     * @param object           $sourceEntityInstance The source entity instance
359
     * @param object           $data                 The New data for the entity instance.
360
     *
361
     * returns object|null returns the newly created model if sucessful or null if model creation failed.
362
     */
363 View Code Duplication
    public function createResourceforResourceSet(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
364
        ResourceSet $resourceSet,
365
        $sourceEntityInstance,
366
        $data
367
    ) {
368
        if (!(null == $sourceEntityInstance || $sourceEntityInstance instanceof Model)) {
369
            throw new InvalidArgumentException('Source entity must either be null or an Eloquent model.');
370 3
        }
371
        $verb = 'create';
372 3
        $class = $resourceSet->getResourceType()->getInstanceType()->name;
373 3
374
        $data = $this->createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb);
375 3
376
        $success = isset($data['id']);
377
378 3
        if ($success) {
379 3
            return $class::findOrFail($data['id']);
380
        }
381
        throw new ODataException('Target model not successfully created', 422);
382
    }
383
384
    /**
385 3
     * @param $sourceEntityInstance
386
     * @param $data
387
     * @param $class
388 3
     * @param string $verb
389 2
     * @return array|mixed
390 2
     * @throws ODataException
391 3
     * @throws \POData\Common\InvalidOperationException
392
     */
393
    private function createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb)
394
    {
395
        $raw = App::make('metadataControllers');
396
        $map = $raw->getMetadata();
397 3
398 3
        if (!array_key_exists($class, $map)) {
399 3
            throw new \POData\Common\InvalidOperationException('Controller mapping missing for class '.$class.'.');
400 3
        }
401 3
        $goal = $raw->getMapping($class, $verb);
402
        if (null == $goal) {
403 3
            throw new \POData\Common\InvalidOperationException(
404 3
                'Controller mapping missing for '.$verb.' verb on class '.$class.'.'
405 3
            );
406 3
        }
407 2
408 2
        if (null == $data) {
409 2
            $data = [];
410 2
        }
411 2
        if (is_object($data)) {
412
            $data = (array) $data;
413
        }
414
        if (!is_array($data)) {
415 2
            throw \POData\Common\ODataException::createPreConditionFailedError(
416
                'Data not resolvable to key-value array.'
417
            );
418 3
        }
419 3
420
        $controlClass = $goal['controller'];
421 3
        $method = $goal['method'];
422
        $paramList = $goal['parameters'];
423 3
        $controller = App::make($controlClass);
424
        $parms = [];
425
426 3
        foreach ($paramList as $spec) {
427 3
            $varType = isset($spec['type']) ? $spec['type'] : null;
428 3
            $varName = $spec['name'];
429 3
            if ($spec['isRequest']) {
430
                $var = new $varType();
431 3
                $var->setMethod('POST');
432
                $var->request = new \Symfony\Component\HttpFoundation\ParameterBag($data);
433
            } else {
434 3
                if (null != $varType) {
435
                    // TODO: Give this smarts and actively pick up instantiation details
436
                    $var = new $varType();
437
                } else {
438
                    $var = $sourceEntityInstance->$varName;
439 3
                }
440
            }
441
            $parms[] = $var;
442
        }
443
444
        $result = call_user_func_array(array($controller, $method), $parms);
445
446
        if (!($result instanceof \Illuminate\Http\JsonResponse)) {
447
            throw ODataException::createInternalServerError('Controller response not well-formed json.');
448
        }
449
        $data = $result->getData();
450
        if (is_object($data)) {
451
            $data = (array) $data;
452
        }
453
454
        if (!is_array($data)) {
455
            throw ODataException::createInternalServerError('Controller response does not have an array.');
456
        }
457
        if (!(key_exists('id', $data) && key_exists('status', $data) && key_exists('errors', $data))) {
458
            throw ODataException::createInternalServerError(
459
                'Controller response array missing at least one of id, status and/or errors fields.'
460
            );
461
        }
462
        return $data;
463
    }
464
465
    /**
466
     * Puts an entity instance to entity set identified by a key.
467
     *
468
     * @param ResourceSet $resourceSet The entity set containing the entity to update
469
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
470
     * @param $data
471
     *
472
     * @return bool|null Returns result of executiong query
473
     */
474
    public function putResource(
475
        ResourceSet $resourceSet,
476
        KeyDescriptor $keyDescriptor,
477
        $data
478
    ) {
479
        // TODO: Implement putResource() method.
480
    }
481
}
482