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

LaravelQuery::getRelatedResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 1

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 24
ccs 10
cts 10
cp 1
rs 9.8666
c 0
b 0
f 0
cc 1
nc 1
nop 10
crap 1

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