Passed
Pull Request — master (#182)
by Alex
07:12
created

LaravelQuery::getResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 2
Bugs 0 Features 0
Metric Value
eloc 11
c 2
b 0
f 0
dl 0
loc 23
ccs 0
cts 16
cp 0
rs 9.9
cc 1
nc 1
nop 9
crap 2

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
    public $queryProviderClassName;
34
    private $verbMap = [];
35
    protected $controllerContainer;
36
    private static $touchList = [];
37
    private static $inBatch;
38
39
    public function __construct(AuthInterface $auth = null)
40
    {
41
        parent::__construct($auth);
42
        /* MySQLExpressionProvider();*/
43
        $this->expression = new LaravelExpressionProvider(); //PHPExpressionProvider('expression');
44
        $this->queryProviderClassName = get_class($this);
45
        $this->reader = new LaravelReadQuery($this->getAuth());
46
        $this->modelHook = new LaravelHookQuery($this->getAuth());
47
        $this->bulk = new LaravelBulkQuery($this, $this->getAuth());
48
49
        $this->controllerContainer = App::make('metadataControllers');
50
        self::$touchList = [];
51
        self::$inBatch = false;
52
    }
53
54
    /**
55
     * Indicates if the QueryProvider can handle ordered paging, this means respecting order, skip, and top parameters
56
     * If the query provider can not handle ordered paging, it must return the entire result set and POData will
57
     * perform the ordering and paging.
58
     *
59
     * @return Boolean True if the query provider can handle ordered paging, false if POData should perform the paging
60
     */
61
    public function handlesOrderedPaging()
62
    {
63
        return true;
64
    }
65
66
    /**
67
     * Gets the expression provider used by to compile OData expressions into expression used by this query provider.
68
     *
69
     * @return \POData\Providers\Expression\IExpressionProvider
70 3
     */
71
    public function getExpressionProvider()
72
    {
73
        return $this->expression;
74
    }
75
76
    /**
77
     * Gets the LaravelReadQuery instance used to handle read queries (repetitious, nyet?).
78
     *
79 3
     * @return LaravelReadQuery
80
     */
81
    public function getReader()
82 3
    {
83 1
        return $this->reader;
84 1
    }
85
86 3
    /**
87 3
     * Gets the LaravelHookQuery instance used to handle hook/unhook queries (repetitious, nyet?).
88 3
     *
89
     * @return LaravelHookQuery
90 3
     */
91
    public function getModelHook()
92
    {
93
        return $this->modelHook;
94
    }
95
96
    /**
97 1
     * Gets the LaravelBulkQuery instance used to handle bulk queries (repetitious, nyet?).
98
     *
99
     * @return LaravelBulkQuery
100 3
     */
101 1
    public function getBulk()
102 1
    {
103 3
        return $this->bulk;
104 1
    }
105 1
106
    /**
107 3
     * Dig out local copy of controller metadata mapping.
108
     *
109 3
     * @return MetadataControllerContainer
110
     * @throws InvalidOperationException
111
     */
112
    public function getControllerContainer()
113
    {
114
        if (null === $this->controllerContainer) {
115
            throw new InvalidOperationException('Controller container must not be null');
116
        }
117 3
        return $this->controllerContainer;
118 1
    }
119 1
120 1
    public function getVerbMap()
121 1
    {
122 1
        if (0 == count($this->verbMap)) {
123 3
            $this->verbMap['create'] = ActionVerb::CREATE();
124 3
            $this->verbMap['update'] = ActionVerb::UPDATE();
125
            $this->verbMap['delete'] = ActionVerb::DELETE();
126
        }
127 3
        return $this->verbMap;
128 3
    }
129 3
130
    /**
131
     * Gets collection of entities belongs to an entity set
132
     * IE: http://host/EntitySet
133
     *  http://host/EntitySet?$skip=10&$top=5&filter=Prop gt Value.
134
     *
135
     * @param QueryType                $queryType            Is this is a query for a count, entities,
136
     *                                                       or entities-with-count?
137
     * @param ResourceSet              $resourceSet          The entity set containing the entities to fetch
138
     * @param FilterInfo|null          $filterInfo           The $filter parameter of the OData query.  NULL if absent
139
     * @param null|InternalOrderByInfo $orderBy              sorted order if we want to get the data in some
140
     *                                                       specific order
141
     * @param int|null                 $top                  number of records which need to be retrieved
142
     * @param int|null                 $skip                 number of records which need to be skipped
143
     * @param SkipTokenInfo|null       $skipToken            value indicating what records to skip
144
     * @param string[]|null            $eagerLoad            array of relations to eager load
145
     * @param Model|Relation|null      $sourceEntityInstance Starting point of query
146
     *
147
     * @return QueryResult
148
     * @throws InvalidOperationException
149
     * @throws ODataException
150
     * @throws \ReflectionException
151
     */
152
    public function getResourceSet(
153
        QueryType $queryType,
154
        ResourceSet $resourceSet,
155
        $filterInfo = null,
156
        $orderBy = null,
157
        $top = null,
158
        $skip = null,
159
        $skipToken = null,
160
        array $eagerLoad = null,
161
        $sourceEntityInstance = null
162
    ) {
163
        /** @var Model|Relation|null $source */
164
        $source = $this->unpackSourceEntity($sourceEntityInstance);
165
        return $this->getReader()->getResourceSet(
166
            $queryType,
167
            $resourceSet,
168
            $filterInfo,
169
            $orderBy,
170
            $top,
171
            $skip,
172
            $skipToken,
173
            $eagerLoad,
174
            $source
175
        );
176
    }
177
    /**
178
     * Gets an entity instance from an entity set identified by a key
179
     * IE: http://host/EntitySet(1L)
180
     * http://host/EntitySet(KeyA=2L,KeyB='someValue').
181
     *
182
     * @param ResourceSet        $resourceSet   The entity set containing the entity to fetch
183
     * @param KeyDescriptor|null $keyDescriptor The key identifying the entity to fetch
184
     * @param string[]|null      $eagerLoad     array of relations to eager load
185
     *
186
     * @return Model|null Returns entity instance if found else null
187
     * @throws \Exception
188
     */
189
    public function getResourceFromResourceSet(
190
        ResourceSet $resourceSet,
191
        KeyDescriptor $keyDescriptor = null,
192
        array $eagerLoad = null
193
    ) {
194
        return $this->getReader()->getResourceFromResourceSet($resourceSet, $keyDescriptor, $eagerLoad);
195
    }
196
197
    /**
198
     * Get related resource set for a resource
199 3
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection
200
     * http://host/EntitySet?$expand=NavigationPropertyToCollection.
201
     *
202
     * @param QueryType          $queryType            Is this is a query for a count, entities, or entities-with-count
203
     * @param ResourceSet        $sourceResourceSet    The entity set containing the source entity
204
     * @param object             $sourceEntityInstance The source entity instance
205
     * @param ResourceSet        $targetResourceSet    The resource set pointed to by the navigation property
206
     * @param ResourceProperty   $targetProperty       The navigation property to retrieve
207
     * @param FilterInfo|null    $filter               The $filter parameter of the OData query.  NULL if none specified
208
     * @param mixed|null         $orderBy              sorted order if we want to get the data in some specific order
209
     * @param int|null           $top                  number of records which need to be retrieved
210 3
     * @param int|null           $skip                 number of records which need to be skipped
211 3
     * @param SkipTokenInfo|null $skipToken            value indicating what records to skip
212
     *
213 3
     * @return QueryResult
214 3
     * @throws \Exception
215 3
     */
216 3
    public function getRelatedResourceSet(
217 3
        QueryType $queryType,
218 3
        ResourceSet $sourceResourceSet,
219 3
        $sourceEntityInstance,
220
        ResourceSet $targetResourceSet,
221 3
        ResourceProperty $targetProperty,
222
        FilterInfo $filter = null,
223
        $orderBy = null,
224
        $top = null,
225
        $skip = null,
226
        $skipToken = null
227
    ) {
228
        $source = $this->unpackSourceEntity($sourceEntityInstance);
229
        return $this->getReader()->getRelatedResourceSet(
230
            $queryType,
231
            $sourceResourceSet,
232
            $source,
233
            $targetResourceSet,
234
            $targetProperty,
235
            $filter,
236
            $orderBy,
237
            $top,
238
            $skip,
239
            $skipToken
240
        );
241
    }
242
243
    /**
244
     * Gets a related entity instance from an entity set identified by a key
245
     * IE: http://host/EntitySet(1L)/NavigationPropertyToCollection(33).
246
     *
247
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
248
     * @param object           $sourceEntityInstance the source entity instance
249
     * @param ResourceSet      $targetResourceSet    The entity set containing the entity to fetch
250
     * @param ResourceProperty $targetProperty       the metadata of the target property
251
     * @param KeyDescriptor    $keyDescriptor        The key identifying the entity to fetch
252
     *
253
     * @return Model|null Returns entity instance if found else null
254
     * @throws \Exception
255
     */
256
    public function getResourceFromRelatedResourceSet(
257
        ResourceSet $sourceResourceSet,
258
        $sourceEntityInstance,
259
        ResourceSet $targetResourceSet,
260
        ResourceProperty $targetProperty,
261
        KeyDescriptor $keyDescriptor
262
    ) {
263
        $source = $this->unpackSourceEntity($sourceEntityInstance);
264
        return $this->getReader()->getResourceFromRelatedResourceSet(
265
            $sourceResourceSet,
266
            $source,
267
            $targetResourceSet,
268
            $targetProperty,
269
            $keyDescriptor
270
        );
271
    }
272
273
    /**
274
     * Get related resource for a resource
275
     * IE: http://host/EntitySet(1L)/NavigationPropertyToSingleEntity
276
     * http://host/EntitySet?$expand=NavigationPropertyToSingleEntity.
277
     *
278
     * @param ResourceSet      $sourceResourceSet    The entity set containing the source entity
279
     * @param object           $sourceEntityInstance the source entity instance
280
     * @param ResourceSet      $targetResourceSet    The entity set containing the entity pointed to by the nav property
281
     * @param ResourceProperty $targetProperty       The navigation property to fetch
282
     *
283
     * @return Model|null The related resource if found else null
284
     * @throws \Exception
285
     */
286
    public function getRelatedResourceReference(
287
        ResourceSet $sourceResourceSet,
288
        $sourceEntityInstance,
289
        ResourceSet $targetResourceSet,
290
        ResourceProperty $targetProperty
291
    ) {
292 1
        $source = $this->unpackSourceEntity($sourceEntityInstance);
293
294
        $result = $this->getReader()->getRelatedResourceReference(
295
            $sourceResourceSet,
296
            $source,
297
            $targetResourceSet,
298
            $targetProperty
299 1
        );
300 1
        return $result;
301
    }
302 1
303
    /**
304 1
     * Updates a resource.
305
     *
306 1
     * @param ResourceSet       $sourceResourceSet    The entity set containing the source entity
307
     * @param Model|Relation    $sourceEntityInstance The source entity instance
308
     * @param KeyDescriptor     $keyDescriptor        The key identifying the entity to fetch
309 1
     * @param object            $data                 the New data for the entity instance
310
     * @param bool              $shouldUpdate         Should undefined values be updated or reset to default
311
     *
312
     * @return Model|null the new resource value if it is assignable or throw exception for null
313
     * @throws \Exception
314
     */
315
    public function updateResource(
316
        ResourceSet $sourceResourceSet,
317
        $sourceEntityInstance,
318 1
        KeyDescriptor $keyDescriptor,
319
        $data,
320
        $shouldUpdate = false
321
    ) {
322 1
        $verb = 'update';
323 1
        return $this->createUpdateMainWrapper($sourceResourceSet, $sourceEntityInstance, $data, $verb);
324 1
    }
325 1
    /**
326 1
     * Delete resource from a resource set.
327
     *
328 1
     * @param ResourceSet $sourceResourceSet
329
     * @param object      $sourceEntityInstance
330 1
     *
331 1
     * @return bool true if resources sucessfully deteled, otherwise false
332
     * @throws \Exception
333
     */
334 1
    public function deleteResource(
335
        ResourceSet $sourceResourceSet,
336
        $sourceEntityInstance
337
    ) {
338
        $source = $this->unpackSourceEntity($sourceEntityInstance);
339
340
        $verb = 'delete';
341
        if (!($source instanceof Model)) {
342
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
343 1
        }
344
345
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
346
        $id = $source->getKey();
347
        $name = $source->getKeyName();
348 1
        $data = [$name => $id];
349 1
350
        $data = $this->createUpdateDeleteCore($source, $data, $class, $verb);
351 1
352
        $success = isset($data['id']);
353 1
        if ($success) {
354
            return true;
355 1
        }
356
        throw new ODataException('Target model not successfully deleted', 422);
357
    }
358 1
    /**
359
     * @param ResourceSet     $resourceSet          The entity set containing the entity to fetch
360
     * @param Model|Relation  $sourceEntityInstance The source entity instance
361
     * @param object          $data                 the New data for the entity instance
362
     *
363
     * @return Model|null                           returns the newly created model if successful,
364
     *                                              or null if model creation failed.
365
     * @throws \Exception
366
     */
367
    public function createResourceforResourceSet(
368
        ResourceSet $resourceSet,
369
        $sourceEntityInstance,
370 3
        $data
371
    ) {
372 3
        $verb = 'create';
373 3
        return $this->createUpdateMainWrapper($resourceSet, $sourceEntityInstance, $data, $verb);
374
    }
375 3
376
    /**
377
     * @param $sourceEntityInstance
378 3
     * @param $data
379 3
     * @param $class
380
     * @param string $verb
381
     *
382
     * @throws ODataException
383
     * @throws InvalidOperationException
384
     * @return array|mixed
385 3
     */
386
    private function createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb)
387
    {
388 3
        $raw = App::make('metadataControllers');
389 2
        $map = $raw->getMetadata();
390 2
391 3
        if (!array_key_exists($class, $map)) {
392
            throw new InvalidOperationException('Controller mapping missing for class ' . $class . '.');
393
        }
394
        $goal = $raw->getMapping($class, $verb);
395
        if (null == $goal) {
396
            throw new InvalidOperationException(
397 3
                'Controller mapping missing for ' . $verb . ' verb on class ' . $class . '.'
398 3
            );
399 3
        }
400 3
401 3
        if (null === $data) {
402
            $msg = 'Data must not be null';
403 3
            throw new InvalidOperationException($msg);
404 3
        }
405 3
        if (is_object($data)) {
406 3
            $arrayData = (array) $data;
407 2
        } else {
408 2
            $arrayData = $data;
409 2
        }
410 2
        if (!is_array($arrayData)) {
411 2
            throw ODataException::createPreConditionFailedError(
412
                'Data not resolvable to key-value array.'
413
            );
414
        }
415 2
416
        $controlClass = $goal['controller'];
417
        $method = $goal['method'];
418 3
        $paramList = $goal['parameters'];
419 3
        $controller = App::make($controlClass);
420
        $parms = $this->createUpdateDeleteProcessInput($arrayData, $paramList, $sourceEntityInstance);
421 3
        unset($data);
422
423 3
        $result = call_user_func_array(array($controller, $method), $parms);
424
425
        return $this->createUpdateDeleteProcessOutput($result);
426 3
    }
427 3
428 3
    /**
429 3
     * Puts an entity instance to entity set identified by a key.
430
     *
431 3
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
432
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
433
     * @param $data
434 3
     *
435
     * @return bool|null Returns result of executing query
436
     */
437
    public function putResource(
438
        ResourceSet $resourceSet,
439 3
        KeyDescriptor $keyDescriptor,
440
        $data
441
    ) {
442
        // TODO: Implement putResource() method.
443
        return true;
444
    }
445
446
    /**
447
     * @param ResourceSet $sourceResourceSet
448
     * @param $data
449
     * @param                            $verb
450
     * @param  Model|null                $source
451
     * @throws InvalidOperationException
452
     * @throws ODataException
453
     * @throws \Exception
454
     * @return Model|null
455
     */
456
    protected function createUpdateCoreWrapper(ResourceSet $sourceResourceSet, $data, $verb, Model $source = null)
457
    {
458
        $lastWord = 'update' == $verb ? 'updated' : 'created';
459
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
460
        if (!$this->getAuth()->canAuth($this->getVerbMap()[$verb], $class, $source)) {
461
            throw new ODataException('Access denied', 403);
462
        }
463
464
        $payload = $this->createUpdateDeleteCore($source, $data, $class, $verb);
465
466
        $success = isset($payload['id']);
467
468
        if ($success) {
469
            try {
470
                return $class::findOrFail($payload['id']);
471
            } catch (\Exception $e) {
472
                throw new ODataException($e->getMessage(), 500);
473
            }
474
        }
475
        throw new ODataException('Target model not successfully ' . $lastWord, 422);
476
    }
477
478
    /**
479
     * @param $data
480
     * @param $paramList
481
     * @param Model|null $sourceEntityInstance
482
     * @return array
483
     */
484
    protected function createUpdateDeleteProcessInput($data, $paramList, Model $sourceEntityInstance)
485
    {
486
        $parms = [];
487
488
        foreach ($paramList as $spec) {
489
            $varType = isset($spec['type']) ? $spec['type'] : null;
490
            $varName = $spec['name'];
491
            if (null == $varType) {
492
                $parms[] = ('id' == $varName) ? $sourceEntityInstance->getKey() : $sourceEntityInstance->$varName;
493
                continue;
494
            }
495
            // TODO: Give this smarts and actively pick up instantiation details
496
            $var = new $varType();
497
            if ($spec['isRequest']) {
498
                $var->setMethod('POST');
499
                $var->request = new \Symfony\Component\HttpFoundation\ParameterBag($data);
500
            }
501
            $parms[] = $var;
502
        }
503
        return $parms;
504
    }
505
506
    /**
507
     * @param $result
508
     * @throws ODataException
509
     * @return array|mixed
510
     */
511
    private function createUpdateDeleteProcessOutput($result)
512
    {
513
        if (!($result instanceof \Illuminate\Http\JsonResponse)) {
514
            throw ODataException::createInternalServerError('Controller response not well-formed json.');
515
        }
516
        $outData = $result->getData();
517
        if (is_object($outData)) {
518
            $outData = (array) $outData;
519
        }
520
521
        if (!is_array($outData)) {
522
            throw ODataException::createInternalServerError('Controller response does not have an array.');
523
        }
524
        if (!(key_exists('id', $outData) && key_exists('status', $outData) && key_exists('errors', $outData))) {
525
            throw ODataException::createInternalServerError(
526
                'Controller response array missing at least one of id, status and/or errors fields.'
527
            );
528
        }
529
        return $outData;
530
    }
531
532
    /**
533
     * @param $sourceEntityInstance
534
     * @return mixed|null|\object[]
535
     */
536
    private function unpackSourceEntity($sourceEntityInstance)
537
    {
538
        if ($sourceEntityInstance instanceof QueryResult) {
539
            $source = $sourceEntityInstance->results;
540
            $source = (is_array($source)) ? $source[0] : $source;
541
            return $source;
542
        }
543
        return $sourceEntityInstance;
544
    }
545
546
    /**
547
     * Create multiple new resources in a resource set.
548
     *
549
     * @param ResourceSet $sourceResourceSet The entity set containing the entity to fetch
550
     * @param object[]    $data              The new data for the entity instance
551
     *
552
     * @return object[] returns the newly created model if successful, or throws an exception if model creation failed
553
     * @throws InvalidOperationException
554
     * @throws \ReflectionException
555
     * @throw  \Exception
556
     */
557
    public function createBulkResourceforResourceSet(
558
        ResourceSet $sourceResourceSet,
559
        array $data
560
    ) {
561
        return $this->getBulk()->createBulkResourceForResourceSet($sourceResourceSet, $data);
562
    }
563
564
    /**
565
     * Updates a group of resources in a resource set.
566
     *
567
     * @param ResourceSet     $sourceResourceSet    The entity set containing the source entity
568
     * @param Model|Relation  $sourceEntityInstance The source entity instance
569
     * @param KeyDescriptor[] $keyDescriptor        The key identifying the entity to fetch
570
     * @param object[]        $data                 The new data for the entity instances
571
     * @param bool            $shouldUpdate         Should undefined values be updated or reset to default
572
     *
573
     * @return object[] the new resource value if it is assignable, or throw exception for null
574
     * @throw  \Exception
575
     * @throws InvalidOperationException
576
     */
577
    public function updateBulkResource(
578
        ResourceSet $sourceResourceSet,
579
        $sourceEntityInstance,
580
        array $keyDescriptor,
581
        array $data,
582
        $shouldUpdate = false
583
    ) {
584
        return $this->getBulk()
585
            ->updateBulkResource(
586
                $sourceResourceSet,
587
                $sourceEntityInstance,
588
                $keyDescriptor,
589
                $data,
590
                $shouldUpdate
591
            );
592
    }
593
594
    /**
595
     * Attaches child model to parent model.
596
     *
597
     * @param ResourceSet $sourceResourceSet
598
     * @param Model       $sourceEntityInstance
599
     * @param ResourceSet $targetResourceSet
600
     * @param Model       $targetEntityInstance
601
     * @param $navPropName
602
     *
603
     * @return bool
604
     * @throws InvalidOperationException
605
     */
606
    public function hookSingleModel(
607
        ResourceSet $sourceResourceSet,
608
        $sourceEntityInstance,
609
        ResourceSet $targetResourceSet,
610
        $targetEntityInstance,
611
        $navPropName
612
    ) {
613
        return $this->getModelHook()->hookSingleModel(
614
            $sourceResourceSet,
615
            $sourceEntityInstance,
616
            $targetResourceSet,
617
            $targetEntityInstance,
618
            $navPropName
619
        );
620
    }
621
622
    /**
623
     * Removes child model from parent model.
624
     *
625
     * @param ResourceSet $sourceResourceSet
626
     * @param Model       $sourceEntityInstance
627
     * @param ResourceSet $targetResourceSet
628
     * @param Model       $targetEntityInstance
629
     * @param $navPropName
630
     *
631
     * @return bool
632
     * @throws InvalidOperationException
633
     */
634
    public function unhookSingleModel(
635
        ResourceSet $sourceResourceSet,
636
        $sourceEntityInstance,
637
        ResourceSet $targetResourceSet,
638
        $targetEntityInstance,
639
        $navPropName
640
    ) {
641
        return $this->getModelHook()->unhookSingleModel(
642
            $sourceResourceSet,
643
            $sourceEntityInstance,
644
            $targetResourceSet,
645
            $targetEntityInstance,
646
            $navPropName
647
        );
648
    }
649
650
    /**
651
     * Start database transaction.
652
     * @param bool $isBulk
653
     */
654
    public function startTransaction($isBulk = false)
655
    {
656
        self::$touchList = [];
657
        self::$inBatch = true === $isBulk;
658
        DB::beginTransaction();
659
    }
660
661
    /**
662
     * Commit database transaction.
663
     */
664
    public function commitTransaction()
665
    {
666
        // fire model save again, to give Laravel app final chance to finalise anything that needs finalising after
667
        // batch processing
668
        foreach (self::$touchList as $model) {
669
            $model->save();
670
        }
671
672
        DB::commit();
673
        self::$touchList = [];
674
        self::$inBatch = false;
675
    }
676
677
    /**
678
     * Abort database transaction.
679
     */
680
    public function rollBackTransaction()
681
    {
682
        DB::rollBack();
683
        self::$touchList = [];
684
        self::$inBatch = false;
685
    }
686
687
    public static function queueModel(Model &$model)
688
    {
689
        // if we're not processing a batch, don't queue anything
690
        if (!self::$inBatch) {
691
            return;
692
        }
693
        // if we are in a batch, add to queue to process on transaction commit
694
        self::$touchList[] = $model;
695
    }
696
697
    /**
698
     * @param ResourceSet $resourceSet
699
     * @param Model|Relation|null $sourceEntityInstance
700
     * @param mixed $data
701
     * @param mixed $verb
702
     * @return Model|null
703
     * @throws InvalidOperationException
704
     * @throws ODataException
705
     */
706
    protected function createUpdateMainWrapper(ResourceSet $resourceSet, $sourceEntityInstance, $data, $verb)
707
    {
708
        /** @var Model|null $source */
709
        $source = $this->unpackSourceEntity($sourceEntityInstance);
710
711
        $result = $this->createUpdateCoreWrapper($resourceSet, $data, $verb, $source);
712
        if (null !== $result) {
713
            self::queueModel($result);
714
        }
715
        return $result;
716
    }
717
}
718