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

LaravelQuery::updateResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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