LaravelQuery   A
last analyzed

Complexity

Total Complexity 26

Size/Duplication

Total Lines 499
Duplicated Lines 0 %

Test Coverage

Coverage 60.47%

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 26
eloc 105
c 5
b 0
f 0
dl 0
loc 499
rs 10
ccs 104
cts 172
cp 0.6047

24 Methods

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