Test Failed
Pull Request — master (#100)
by Alex
04:49
created

LaravelQuery::updateBulkResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
440
                continue;
441
            }
442
            // TODO: Give this smarts and actively pick up instantiation details
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
443
            $var = new $varType();
444
            if ($spec['isRequest']) {
445
                $var->setMethod('POST');
446
                $var->request = new \Symfony\Component\HttpFoundation\ParameterBag($data);
447
            }
448
            $parms[] = $var;
449
        }
450
        return $parms;
451
    }
452
453
    /**
454
     * @param $result
455
     * @return array|mixed
456
     * @throws ODataException
457
     */
458
    private function createUpdateDeleteProcessOutput($result)
459
    {
460
        if (!($result instanceof \Illuminate\Http\JsonResponse)) {
461
            throw ODataException::createInternalServerError('Controller response not well-formed json.');
462
        }
463
        $outData = $result->getData();
464
        if (is_object($outData)) {
465
            $outData = (array)$outData;
466
        }
467
468
        if (!is_array($outData)) {
469
            throw ODataException::createInternalServerError('Controller response does not have an array.');
470
        }
471
        if (!(key_exists('id', $outData) && key_exists('status', $outData) && key_exists('errors', $outData))) {
472
            throw ODataException::createInternalServerError(
473
                'Controller response array missing at least one of id, status and/or errors fields.'
474
            );
475
        }
476
        return $outData;
477
    }
478
479
    /**
480
     * @param $sourceEntityInstance
481
     * @return mixed|null|\object[]
482
     */
483
    private function unpackSourceEntity($sourceEntityInstance)
484
    {
485
        if ($sourceEntityInstance instanceof QueryResult) {
486
            $source = $sourceEntityInstance->results;
487
            $source = (is_array($source)) ? $source[0] : $source;
488
            return $source;
489
        }
490
        return $sourceEntityInstance;
491
    }
492
493
    /**
494
     * Create multiple new resources in a resource set.
495
     * @param ResourceSet $sourceResourceSet The entity set containing the entity to fetch
496
     * @param object[] $data The new data for the entity instance
497
     *
498
     * @return object[]|null returns the newly created model if successful, or null if model creation failed
499
     */
500
    public function createBulkResourceforResourceSet(
501
        ResourceSet $sourceResourceSet,
0 ignored issues
show
Unused Code introduced by
The parameter $sourceResourceSet is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
502
        array $data
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
503
    ) {
504
        // TODO: Implement createBulkResourceforResourceSet() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
505
    }
506
507
    /**
508
     * Updates a group of resources in a resource set.
509
     *
510
     * @param ResourceSet $sourceResourceSet The entity set containing the source entity
511
     * @param object $sourceEntityInstance The source entity instance
512
     * @param KeyDescriptor[] $keyDescriptor The key identifying the entity to fetch
513
     * @param object[] $data The new data for the entity instances
514
     * @param bool $shouldUpdate Should undefined values be updated or reset to default
515
     *
516
     * @return object[]|null the new resource value if it is assignable, or throw exception for null
517
     */
518
    public function updateBulkResource(
519
        ResourceSet $sourceResourceSet,
520
        $sourceEntityInstance,
0 ignored issues
show
Unused Code introduced by
The parameter $sourceEntityInstance is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
521
        array $keyDescriptor,
0 ignored issues
show
Unused Code introduced by
The parameter $keyDescriptor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
522
        array $data,
0 ignored issues
show
Unused Code introduced by
The parameter $data is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
523
        $shouldUpdate = false
1 ignored issue
show
Unused Code introduced by
The parameter $shouldUpdate is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
524
    ) {
525
        // TODO: Implement updateBulkResource() method.
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
526
    }
527
528
    /**
529
     * Attaches child model to parent model
530
     *
531
     * @param ResourceSet $sourceResourceSet
532
     * @param object $sourceEntityInstance
533
     * @param ResourceSet $targetResourceSet
534
     * @param object $targetEntityInstance
535
     * @param $navPropName
536
     *
537
     * @return bool
538
     */
539
    public function hookSingleModel(
1 ignored issue
show
Coding Style introduced by
function hookSingleModel() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
540
        ResourceSet $sourceResourceSet,
541
        $sourceEntityInstance,
542
        ResourceSet $targetResourceSet,
543
        $targetEntityInstance,
544
        $navPropName
545
    ) {
546
        $relation = $this->isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName);
547
        assert($sourceEntityInstance instanceof Model && $targetEntityInstance instanceof Model);
548
549
        if ($relation instanceof BelongsTo) {
550
            $relation->associate($targetEntityInstance);
551
        } elseif ($relation instanceof BelongsToMany) {
552
            $relation->attach($targetEntityInstance);
553
        } elseif ($relation instanceof HasOneOrMany) {
554
            $relation->save($targetEntityInstance);
555
        }
556
        return true;
557
    }
558
559
    /**
560
     * Removes child model from parent model
561
     *
562
     * @param ResourceSet $sourceResourceSet
563
     * @param object $sourceEntityInstance
564
     * @param ResourceSet $targetResourceSet
565
     * @param object $targetEntityInstance
566
     * @param $navPropName
567
     *
568
     * @return bool
569
     */
570
    public function unhookSingleModel(
1 ignored issue
show
Coding Style introduced by
function unhookSingleModel() does not seem to conform to the naming convention (^(?:is|has|should|may|supports)).

This check examines a number of code elements and verifies that they conform to the given naming conventions.

You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.

Loading history...
571
        ResourceSet $sourceResourceSet,
572
        $sourceEntityInstance,
573
        ResourceSet $targetResourceSet,
574
        $targetEntityInstance,
575
        $navPropName
576
    ) {
577
        $relation = $this->isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName);
578
        assert($sourceEntityInstance instanceof Model && $targetEntityInstance instanceof Model);
579
580
        if ($relation instanceof BelongsTo) {
581
            $relation->dissociate();
582
        } elseif ($relation instanceof BelongsToMany) {
583
            $relation->detach($targetEntityInstance);
584
        } elseif ($relation instanceof HasOneOrMany) {
585
            // dig up inverse property name, so we can pass it to unhookSingleModel with source and target elements
586
            // swapped
587
            $otherPropName = $this->getMetadataProvider()
588
                ->resolveReverseProperty($sourceEntityInstance, $targetEntityInstance, $navPropName);
589
            if (null === $otherPropName) {
590
                $msg = 'Bad navigation property, '.$navPropName.', on source model '.get_class($sourceEntityInstance);
591
                throw new \InvalidArgumentException($msg);
592
            }
593
            $this->unhookSingleModel(
594
                $targetResourceSet,
595
                $targetEntityInstance,
596
                $sourceResourceSet,
597
                $sourceEntityInstance,
598
                $otherPropName
599
            );
600
        }
601
        return true;
602
    }
603
604
    /**
605
     * @param $sourceEntityInstance
606
     * @param $targetEntityInstance
607
     * @param $navPropName
608
     * @return Relation
609
     * @throws \InvalidArgumentException
610
     */
611
    protected function isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName)
612
    {
613
        if (!$sourceEntityInstance instanceof Model || !$targetEntityInstance instanceof Model) {
614
            $msg = 'Both source and target must be Eloquent models';
615
            throw new \InvalidArgumentException($msg);
616
        }
617
        $relation = $sourceEntityInstance->$navPropName();
618
        if (!$relation instanceof Relation) {
619
            $msg = 'Navigation property must be an Eloquent relation';
620
            throw new \InvalidArgumentException($msg);
621
        }
622
        $targType = $relation->getRelated();
623
        if (!$targetEntityInstance instanceof $targType) {
624
            $msg = 'Target instance must be of type compatible with relation declared in method '.$navPropName;
625
            throw new \InvalidArgumentException($msg);
626
        }
627
        return $relation;
628
    }
629
}
630