Completed
Push — master ( 67f4d5...2cf8cf )
by Alex
03:20
created

LaravelQuery::deleteResource()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2.003

Importance

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

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
453
        KeyDescriptor $keyDescriptor,
0 ignored issues
show
Unused Code introduced by
The parameter $keyDescriptor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
454
        $data
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
455
    ) {
456
        // TODO: Implement putResource() method.
457
    }
458
}
459