Passed
Pull Request — master (#155)
by Alex
06:39
created

LaravelHookQuery::isModelHookInputsOk()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 17
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 17
rs 8.8571
c 1
b 0
f 0
cc 5
eloc 12
nc 4
nop 3
1
<?php
2
3
namespace AlgoWeb\PODataLaravel\Query;
4
5
use AlgoWeb\PODataLaravel\Auth\NullAuthProvider;
6
use AlgoWeb\PODataLaravel\Enums\ActionVerb;
7
use AlgoWeb\PODataLaravel\Interfaces\AuthInterface;
8
use AlgoWeb\PODataLaravel\Providers\MetadataProvider;
9
use Illuminate\Database\Eloquent\Model;
10
use Illuminate\Database\Eloquent\Relations\BelongsTo;
11
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
12
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;
13
use Illuminate\Database\Eloquent\Relations\Relation;
14
use Illuminate\Support\Facades\App;
15
use POData\Common\InvalidOperationException;
16
use POData\Common\ODataException;
17
use POData\Providers\Metadata\ResourceProperty;
18
use POData\Providers\Metadata\ResourceSet;
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
27
class LaravelHookQuery
28
{
29
    protected $auth;
30
    protected $metadataProvider;
31
32
    public function __construct(AuthInterface $auth = null)
33
    {
34
        $this->auth = isset($auth) ? $auth : new NullAuthProvider();
35
        $this->metadataProvider = new MetadataProvider(App::make('app'));
36
    }
37
38
    /**
39
     * Attaches child model to parent model.
40
     *
41
     * @param ResourceSet $sourceResourceSet
42
     * @param object      $sourceEntityInstance
43
     * @param ResourceSet $targetResourceSet
44
     * @param object      $targetEntityInstance
45
     * @param $navPropName
46
     *
47
     * @return bool
48
     */
49
    public function hookSingleModel(
50
        ResourceSet $sourceResourceSet,
51
        $sourceEntityInstance,
52
        ResourceSet $targetResourceSet,
53
        $targetEntityInstance,
54
        $navPropName
55
    ) {
56
        $relation = $this->isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName);
57
        assert(
58
            $sourceEntityInstance instanceof Model && $targetEntityInstance instanceof Model,
59
            'Both input entities must be Eloquent models'
60
        );
61
        // in case the fake 'PrimaryKey' attribute got set inbound for a polymorphic-affected model, flatten it now
62
        unset($targetEntityInstance->PrimaryKey);
63
64
        if ($relation instanceof BelongsTo) {
65
            $relation->associate($targetEntityInstance);
66
        } elseif ($relation instanceof BelongsToMany) {
67
            $relation->attach($targetEntityInstance);
68
        } elseif ($relation instanceof HasOneOrMany) {
69
            $relation->save($targetEntityInstance);
70
        }
71
        return true;
72
    }
73
74
    /**
75
     * Removes child model from parent model.
76
     *
77
     * @param ResourceSet $sourceResourceSet
78
     * @param object      $sourceEntityInstance
79
     * @param ResourceSet $targetResourceSet
80
     * @param object      $targetEntityInstance
81
     * @param $navPropName
82
     *
83
     * @return bool
84
     */
85
    public function unhookSingleModel(
86
        ResourceSet $sourceResourceSet,
87
        $sourceEntityInstance,
88
        ResourceSet $targetResourceSet,
89
        $targetEntityInstance,
90
        $navPropName
91
    ) {
92
        $relation = $this->isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName);
93
        assert(
94
            $sourceEntityInstance instanceof Model && $targetEntityInstance instanceof Model,
95
            'Both input entities must be Eloquent models'
96
        );
97
        // in case the fake 'PrimaryKey' attribute got set inbound for a polymorphic-affected model, flatten it now
98
        unset($targetEntityInstance->PrimaryKey);
99
100
        if ($relation instanceof BelongsTo) {
101
            $relation->dissociate();
102
        } elseif ($relation instanceof BelongsToMany) {
103
            $relation->detach($targetEntityInstance);
104
        } elseif ($relation instanceof HasOneOrMany) {
105
            // dig up inverse property name, so we can pass it to unhookSingleModel with source and target elements
106
            // swapped
107
            $otherPropName = $this->getMetadataProvider()
108
                ->resolveReverseProperty($sourceEntityInstance, $navPropName);
109
            if (null === $otherPropName) {
110
                $srcClass = get_class($sourceEntityInstance);
111
                $msg = 'Bad navigation property, ' . $navPropName . ', on source model ' . $srcClass;
112
                throw new \InvalidArgumentException($msg);
113
            }
114
            $this->unhookSingleModel(
115
                $targetResourceSet,
116
                $targetEntityInstance,
117
                $sourceResourceSet,
118
                $sourceEntityInstance,
119
                $otherPropName
120
            );
121
        }
122
        return true;
123
    }
124
125
    /**
126
     * @param $sourceEntityInstance
127
     * @param $targetEntityInstance
128
     * @param $navPropName
129
     * @throws \InvalidArgumentException
130
     * @return Relation
131
     */
132
    protected function isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName)
133
    {
134
        if (!$sourceEntityInstance instanceof Model || !$targetEntityInstance instanceof Model) {
135
            $msg = 'Both source and target must be Eloquent models';
136
            throw new \InvalidArgumentException($msg);
137
        }
138
        $relation = $sourceEntityInstance->$navPropName();
139
        if (!$relation instanceof Relation) {
140
            $msg = 'Navigation property must be an Eloquent relation';
141
            throw new \InvalidArgumentException($msg);
142
        }
143
        $targType = $relation->getRelated();
144
        if (!$targetEntityInstance instanceof $targType) {
145
            $msg = 'Target instance must be of type compatible with relation declared in method ' . $navPropName;
146
            throw new \InvalidArgumentException($msg);
147
        }
148
        return $relation;
149
    }
150
151
    /**
152
     * Dig out local copy of POData-Laravel metadata provider.
153
     *
154
     * @return MetadataProvider
155
     */
156
    public function getMetadataProvider()
157
    {
158
        return $this->metadataProvider;
159
    }
160
}
161