Passed
Pull Request — master (#182)
by Alex
05:02
created

LaravelQuery   A

Complexity

Total Complexity 30

Size/Duplication

Total Lines 520
Duplicated Lines 0 %

Test Coverage

Coverage 60.69%

Importance

Changes 23
Bugs 1 Features 0
Metric Value
wmc 30
eloc 119
c 23
b 1
f 0
dl 0
loc 520
rs 10
ccs 105
cts 173
cp 0.6069

25 Methods

Rating   Name   Duplication   Size   Complexity  
A putResource() 0 7 1
A hookSingleModel() 0 13 1
A getResourceFromResourceSet() 0 6 1
A updateResource() 0 9 1
A unhookSingleModel() 0 13 1
A getResourceFromRelatedResourceSet() 0 14 1
A getModelHook() 0 3 1
A createUpdateMainWrapper() 0 10 2
A createResourceforResourceSet() 0 7 1
A getExpressionProvider() 0 3 1
A createBulkResourceforResourceSet() 0 5 1
A getRelatedResourceSet() 0 24 1
A rollBackTransaction() 0 5 1
A getReader() 0 3 1
A handlesOrderedPaging() 0 3 1
A updateBulkResource() 0 14 1
A queueModel() 0 8 2
A __construct() 0 13 1
A deleteResource() 0 23 3
A getRelatedResourceReference() 0 15 1
A getBulk() 0 3 1
A startTransaction() 0 5 1
A getResourceSet() 0 23 1
A getWriter() 0 3 1
A commitTransaction() 0 11 2
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use AlgoWeb\PODataLaravel\Auth\NullAuthProvider;
6
use AlgoWeb\PODataLaravel\Controllers\MetadataControllerContainer;
7
use AlgoWeb\PODataLaravel\Enums\ActionVerb;
8
use AlgoWeb\PODataLaravel\Interfaces\AuthInterface;
9
use AlgoWeb\PODataLaravel\Providers\MetadataProvider;
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Database\Eloquent\Relations\Relation;
12
use Illuminate\Support\Facades\App;
13
use Illuminate\Support\Facades\DB;
14
use POData\Common\InvalidOperationException;
15
use POData\Common\ODataException;
16
use POData\Providers\Metadata\ResourceProperty;
17
use POData\Providers\Metadata\ResourceSet;
18
use POData\Providers\Query\IQueryProvider;
19
use POData\Providers\Query\QueryResult;
20
use POData\Providers\Query\QueryType;
21
use POData\UriProcessor\QueryProcessor\ExpressionParser\FilterInfo;
22
use POData\UriProcessor\QueryProcessor\OrderByParser\InternalOrderByInfo;
23
use POData\UriProcessor\QueryProcessor\SkipTokenParser\SkipTokenInfo;
24
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
25
use Symfony\Component\Process\Exception\InvalidArgumentException;
26 5
27
class LaravelQuery extends LaravelBaseQuery implements IQueryProvider
28
{
29 5
    protected $expression;
30 5
    protected $reader;
31 5
    protected $modelHook;
32 5
    protected $bulk;
33
    protected $writer;
34
    public $queryProviderClassName;
35
    private static $touchList = [];
36
    private static $inBatch;
37
38
    public function __construct(AuthInterface $auth = null)
39
    {
40
        parent::__construct($auth);
41
        /* MySQLExpressionProvider();*/
42
        $this->expression = new LaravelExpressionProvider(); //PHPExpressionProvider('expression');
43
        $this->queryProviderClassName = get_class($this);
44
        $this->reader = new LaravelReadQuery($this->getAuth());
45
        $this->modelHook = new LaravelHookQuery($this->getAuth());
46
        $this->bulk = new LaravelBulkQuery($this, $this->getAuth());
47
        $this->writer = new LaravelWriteQuery($this->getAuth());
48
49
        self::$touchList = [];
50
        self::$inBatch = false;
51
    }
52
53
    /**
54
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
55
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
56
     * perform the ordering and paging.
57
     *
58
     * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
59
     */
60
    public function handlesOrderedPaging()
61
    {
62
        return true;
63
    }
64
65
    /**
66
     * Gets the expression provider used by to compile OData expressions into expression used by this query provider.
67
     *
68
     * @return \POData\Providers\Expression\IExpressionProvider
69
     */
70 3
    public function getExpressionProvider()
71
    {
72
        return $this->expression;
73
    }
74
75
    /**
76
     * Gets the LaravelReadQuery instance used to handle read queries (repetitious, nyet?).
77
     *
78
     * @return LaravelReadQuery
79 3
     */
80
    public function getReader()
81
    {
82 3
        return $this->reader;
83 1
    }
84 1
85
    /**
86 3
     * Gets the LaravelHookQuery instance used to handle hook/unhook queries (repetitious, nyet?).
87 3
     *
88 3
     * @return LaravelHookQuery
89
     */
90 3
    public function getModelHook()
91
    {
92
        return $this->modelHook;
93
    }
94
95
    /**
96
     * Gets the LaravelBulkQuery instance used to handle bulk queries (repetitious, nyet?).
97 1
     *
98
     * @return LaravelBulkQuery
99
     */
100 3
    public function getBulk()
101 1
    {
102 1
        return $this->bulk;
103 3
    }
104 1
105 1
    /**
106
     * Gets collection of entities belongs to an entity set
107 3
     * IE: http://host/EntitySet
108
     *  http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value.
109 3
     *
110
     * @param QueryType                $queryType            Is this is a query for a count, entities,
111
     *                                                       or entities-with-count?
112
     * @param ResourceSet              $resourceSet          The entity set containing the entities to fetch
113
     * @param FilterInfo|null          $filterInfo           The $filter parameter of the OData query.  NULL if absent
114
     * @param null|InternalOrderByInfo $orderBy              sorted order if we want to get the data in some
115
     *                                                       specific order
116
     * @param int|null                 $top                  number of records which need to be retrieved
117 3
     * @param int|null                 $skip                 number of records which need to be skipped
118 1
     * @param SkipTokenInfo|null       $skipToken            value indicating what records to skip
119 1
     * @param string[]|null            $eagerLoad            array of relations to eager load
120 1
     * @param Model|Relation|null      $sourceEntityInstance Starting point of query
121 1
     *
122 1
     * @return QueryResult
123 3
     * @throws InvalidOperationException
124 3
     * @throws ODataException
125
     * @throws \ReflectionException
126
     */
127 3
    public function getResourceSet(
128 3
        QueryType $queryType,
129 3
        ResourceSet $resourceSet,
130
        $filterInfo = null,
131
        $orderBy = null,
132
        $top = null,
133
        $skip = null,
134
        $skipToken = null,
135
        array $eagerLoad = null,
136
        $sourceEntityInstance = null
137
    ) {
138
        /** @var Model|Relation|null $source */
139
        $source = $this->unpackSourceEntity($sourceEntityInstance);
140
        return $this->getReader()->getResourceSet(
141
            $queryType,
142
            $resourceSet,
143
            $filterInfo,
144
            $orderBy,
145
            $top,
146
            $skip,
147
            $skipToken,
148
            $eagerLoad,
149
            $source
150
        );
151
    }
152
    /**
153
     * Gets an entity instance from an entity set identified by a key
154
     * IE: http://host/EntitySet(1L)
155
     * http://host/EntitySet(KeyA=2L,KeyB='someValue').
156
     *
157
     * @param ResourceSet        $resourceSet   The entity set containing the entity to fetch
158
     * @param KeyDescriptor|null $keyDescriptor The key identifying the entity to fetch
159
     * @param string[]|null      $eagerLoad     array of relations to eager load
160
     *
161
     * @return Model|null Returns entity instance if found else null
162
     * @throws \Exception
163
     */
164
    public function getResourceFromResourceSet(
165
        ResourceSet $resourceSet,
166
        KeyDescriptor $keyDescriptor = null,
167
        array $eagerLoad = null
168
    ) {
169
        return $this->getReader()->getResourceFromResourceSet($resourceSet, $keyDescriptor, $eagerLoad);
170
    }
171
172
    /**
173
     * Get related resource set for a resource
174
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection
175
     * http://host/EntitySet?$expand=NavigationPropertyToCollection.
176
     *
177
     * @param QueryType          $queryType            Is this is a query for a count, entities, or entities-with-count
178
     * @param ResourceSet        $sourceResourceSet    The entity set containing the source entity
179
     * @param object             $sourceEntityInstance The source entity instance
180
     * @param ResourceSet        $targetResourceSet    The resource set pointed to by the navigation property
181
     * @param ResourceProperty   $targetProperty       The navigation property to retrieve
182
     * @param FilterInfo|null    $filter               The $filter parameter of the OData query.  NULL if none specified
183
     * @param mixed|null         $orderBy              sorted order if we want to get the data in some specific order
184
     * @param int|null           $top                  number of records which need to be retrieved
185
     * @param int|null           $skip                 number of records which need to be skipped
186
     * @param SkipTokenInfo|null $skipToken            value indicating what records to skip
187
     *
188
     * @return QueryResult
189
     * @throws \Exception
190
     */
191
    public function getRelatedResourceSet(
192
        QueryType $queryType,
193
        ResourceSet $sourceResourceSet,
194
        $sourceEntityInstance,
195
        ResourceSet $targetResourceSet,
196
        ResourceProperty $targetProperty,
197
        FilterInfo $filter = null,
198
        $orderBy = null,
199 3
        $top = null,
200
        $skip = null,
201
        $skipToken = null
202
    ) {
203
        $source = $this->unpackSourceEntity($sourceEntityInstance);
204
        return $this->getReader()->getRelatedResourceSet(
205
            $queryType,
206
            $sourceResourceSet,
207
            $source,
208
            $targetResourceSet,
209
            $targetProperty,
210 3
            $filter,
211 3
            $orderBy,
212
            $top,
213 3
            $skip,
214 3
            $skipToken
215 3
        );
216 3
    }
217 3
218 3
    /**
219 3
     * Gets a related entity instance from an entity set identified by a key
220
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33).
221 3
     *
222
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
223
     * @param object           $sourceEntityInstance the source entity instance
224
     * @param ResourceSet      $targetResourceSet    The entity set containing the entity to fetch
225
     * @param ResourceProperty $targetProperty       the metadata of the target property
226
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
227
     *
228
     * @return Model|null Returns entity instance if found else null
229
     * @throws \Exception
230
     */
231
    public function getResourceFromRelatedResourceSet(
232
        ResourceSet $sourceResourceSet,
233
        $sourceEntityInstance,
234
        ResourceSet $targetResourceSet,
235
        ResourceProperty $targetProperty,
236
        KeyDescriptor $keyDescriptor
237
    ) {
238
        $source = $this->unpackSourceEntity($sourceEntityInstance);
239
        return $this->getReader()->getResourceFromRelatedResourceSet(
240
            $sourceResourceSet,
241
            $source,
242
            $targetResourceSet,
243
            $targetProperty,
244
            $keyDescriptor
245
        );
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 nav property
256
     * @param ResourceProperty $targetProperty       The navigation property to fetch
257
     *
258
     * @return Model|null The related resource if found else null
259
     * @throws \Exception
260
     */
261
    public function getRelatedResourceReference(
262
        ResourceSet $sourceResourceSet,
263
        $sourceEntityInstance,
264
        ResourceSet $targetResourceSet,
265
        ResourceProperty $targetProperty
266
    ) {
267
        $source = $this->unpackSourceEntity($sourceEntityInstance);
268
269
        $result = $this->getReader()->getRelatedResourceReference(
270
            $sourceResourceSet,
271
            $source,
272
            $targetResourceSet,
273
            $targetProperty
274
        );
275
        return $result;
276
    }
277
278
    /**
279
     * Updates a resource.
280
     *
281
     * @param ResourceSet       $sourceResourceSet    The entity set containing the source entity
282
     * @param Model|Relation    $sourceEntityInstance The source entity instance
283
     * @param KeyDescriptor     $keyDescriptor        The key identifying the entity to fetch
284
     * @param object            $data                 the New data for the entity instance
285
     * @param bool              $shouldUpdate         Should undefined values be updated or reset to default
286
     *
287
     * @return Model|null the new resource value if it is assignable or throw exception for null
288
     * @throws \Exception
289
     */
290
    public function updateResource(
291
        ResourceSet $sourceResourceSet,
292 1
        $sourceEntityInstance,
293
        KeyDescriptor $keyDescriptor,
294
        $data,
295
        $shouldUpdate = false
296
    ) {
297
        $verb = 'update';
298
        return $this->createUpdateMainWrapper($sourceResourceSet, $sourceEntityInstance, $data, $verb);
299 1
    }
300 1
    /**
301
     * Delete resource from a resource set.
302 1
     *
303
     * @param ResourceSet $sourceResourceSet
304 1
     * @param object      $sourceEntityInstance
305
     *
306 1
     * @return bool true if resources sucessfully deteled, otherwise false
307
     * @throws \Exception
308
     */
309 1
    public function deleteResource(
310
        ResourceSet $sourceResourceSet,
311
        $sourceEntityInstance
312
    ) {
313
        $source = $this->unpackSourceEntity($sourceEntityInstance);
314
315
        $verb = 'delete';
316
        if (!($source instanceof Model)) {
317
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
318 1
        }
319
320
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
321
        $id = $source->getKey();
322 1
        $name = $source->getKeyName();
323 1
        $data = [$name => $id];
324 1
325 1
        $data = $this->getWriter()->createUpdateDeleteCore($source, $data, $class, $verb);
326 1
327
        $success = isset($data['id']);
328 1
        if ($success) {
329
            return true;
330 1
        }
331 1
        throw new ODataException('Target model not successfully deleted', 422);
332
    }
333
    /**
334 1
     * @param ResourceSet     $resourceSet          The entity set containing the entity to fetch
335
     * @param Model|Relation  $sourceEntityInstance The source entity instance
336
     * @param object          $data                 the New data for the entity instance
337
     *
338
     * @return Model|null                           returns the newly created model if successful,
339
     *                                              or null if model creation failed.
340
     * @throws \Exception
341
     */
342
    public function createResourceforResourceSet(
343 1
        ResourceSet $resourceSet,
344
        $sourceEntityInstance,
345
        $data
346
    ) {
347
        $verb = 'create';
348 1
        return $this->createUpdateMainWrapper($resourceSet, $sourceEntityInstance, $data, $verb);
349 1
    }
350
351 1
    /**
352
     * Puts an entity instance to entity set identified by a key.
353 1
     *
354
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
355 1
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
356
     * @param $data
357
     *
358 1
     * @return bool|null Returns result of executing query
359
     */
360
    public function putResource(
361
        ResourceSet $resourceSet,
362
        KeyDescriptor $keyDescriptor,
363
        $data
364
    ) {
365
        // TODO: Implement putResource() method.
366
        return true;
367
    }
368
369
    /**
370 3
     * Create multiple new resources in a resource set.
371
     *
372 3
     * @param ResourceSet $sourceResourceSet The entity set containing the entity to fetch
373 3
     * @param object[]    $data              The new data for the entity instance
374
     *
375 3
     * @return object[] returns the newly created model if successful, or throws an exception if model creation failed
376
     * @throws InvalidOperationException
377
     * @throws \ReflectionException
378 3
     * @throw  \Exception
379 3
     */
380
    public function createBulkResourceforResourceSet(
381
        ResourceSet $sourceResourceSet,
382
        array $data
383
    ) {
384
        return $this->getBulk()->createBulkResourceForResourceSet($sourceResourceSet, $data);
385 3
    }
386
387
    /**
388 3
     * Updates a group of resources in a resource set.
389 2
     *
390 2
     * @param ResourceSet     $sourceResourceSet    The entity set containing the source entity
391 3
     * @param Model|Relation  $sourceEntityInstance The source entity instance
392
     * @param KeyDescriptor[] $keyDescriptor        The key identifying the entity to fetch
393
     * @param object[]        $data                 The new data for the entity instances
394
     * @param bool            $shouldUpdate         Should undefined values be updated or reset to default
395
     *
396
     * @return object[] the new resource value if it is assignable, or throw exception for null
397 3
     * @throw  \Exception
398 3
     * @throws InvalidOperationException
399 3
     */
400 3
    public function updateBulkResource(
401 3
        ResourceSet $sourceResourceSet,
402
        $sourceEntityInstance,
403 3
        array $keyDescriptor,
404 3
        array $data,
405 3
        $shouldUpdate = false
406 3
    ) {
407 2
        return $this->getBulk()
408 2
            ->updateBulkResource(
409 2
                $sourceResourceSet,
410 2
                $sourceEntityInstance,
411 2
                $keyDescriptor,
412
                $data,
413
                $shouldUpdate
414
            );
415 2
    }
416
417
    /**
418 3
     * Attaches child model to parent model.
419 3
     *
420
     * @param ResourceSet $sourceResourceSet
421 3
     * @param Model       $sourceEntityInstance
422
     * @param ResourceSet $targetResourceSet
423 3
     * @param Model       $targetEntityInstance
424
     * @param $navPropName
425
     *
426 3
     * @return bool
427 3
     * @throws InvalidOperationException
428 3
     */
429 3
    public function hookSingleModel(
430
        ResourceSet $sourceResourceSet,
431 3
        $sourceEntityInstance,
432
        ResourceSet $targetResourceSet,
433
        $targetEntityInstance,
434 3
        $navPropName
435
    ) {
436
        return $this->getModelHook()->hookSingleModel(
437
            $sourceResourceSet,
438
            $sourceEntityInstance,
439 3
            $targetResourceSet,
440
            $targetEntityInstance,
441
            $navPropName
442
        );
443
    }
444
445
    /**
446
     * Removes child model from parent model.
447
     *
448
     * @param ResourceSet $sourceResourceSet
449
     * @param Model       $sourceEntityInstance
450
     * @param ResourceSet $targetResourceSet
451
     * @param Model       $targetEntityInstance
452
     * @param $navPropName
453
     *
454
     * @return bool
455
     * @throws InvalidOperationException
456
     */
457
    public function unhookSingleModel(
458
        ResourceSet $sourceResourceSet,
459
        $sourceEntityInstance,
460
        ResourceSet $targetResourceSet,
461
        $targetEntityInstance,
462
        $navPropName
463
    ) {
464
        return $this->getModelHook()->unhookSingleModel(
465
            $sourceResourceSet,
466
            $sourceEntityInstance,
467
            $targetResourceSet,
468
            $targetEntityInstance,
469
            $navPropName
470
        );
471
    }
472
473
    /**
474
     * Start database transaction.
475
     * @param bool $isBulk
476
     */
477
    public function startTransaction($isBulk = false)
478
    {
479
        self::$touchList = [];
480
        self::$inBatch = true === $isBulk;
481
        DB::beginTransaction();
482
    }
483
484
    /**
485
     * Commit database transaction.
486
     */
487
    public function commitTransaction()
488
    {
489
        // fire model save again, to give Laravel app final chance to finalise anything that needs finalising after
490
        // batch processing
491
        foreach (self::$touchList as $model) {
492
            $model->save();
493
        }
494
495
        DB::commit();
496
        self::$touchList = [];
497
        self::$inBatch = false;
498
    }
499
500
    /**
501
     * Abort database transaction.
502
     */
503
    public function rollBackTransaction()
504
    {
505
        DB::rollBack();
506
        self::$touchList = [];
507
        self::$inBatch = false;
508
    }
509
510
    public static function queueModel(Model &$model)
511
    {
512
        // if we're not processing a batch, don't queue anything
513
        if (!self::$inBatch) {
514
            return;
515
        }
516
        // if we are in a batch, add to queue to process on transaction commit
517
        self::$touchList[] = $model;
518
    }
519
520
    /**
521
     * @param ResourceSet $resourceSet
522
     * @param Model|Relation|null $sourceEntityInstance
523
     * @param mixed $data
524
     * @param mixed $verb
525
     * @return Model|null
526
     * @throws InvalidOperationException
527
     * @throws ODataException
528
     */
529
    protected function createUpdateMainWrapper(ResourceSet $resourceSet, $sourceEntityInstance, $data, $verb)
530
    {
531
        /** @var Model|null $source */
532
        $source = $this->unpackSourceEntity($sourceEntityInstance);
533
534
        $result = $this->getWriter()->createUpdateCoreWrapper($resourceSet, $data, $verb, $source);
535
        if (null !== $result) {
536
            self::queueModel($result);
537
        }
538
        return $result;
539
    }
540
541
    /**
542
     * @return LaravelWriteQuery
543
     */
544
    public function getWriter()
545
    {
546
        return $this->writer;
547
    }
548
}
549