Passed
Pull Request — master (#160)
by Alex
04:16
created

LaravelHookQuery   A

Complexity

Total Complexity 20

Size/Duplication

Total Lines 142
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 20
dl 0
loc 142
rs 10
c 1
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 2
A getMetadataProvider() 0 3 1
B hookSingleModel() 0 25 5
B isModelHookInputsOk() 0 17 5
C unhookSingleModel() 0 46 7
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
        LaravelQuery::queueModel($sourceEntityInstance);
72
        LaravelQuery::queueModel($targetEntityInstance);
73
        return true;
74
    }
75
76
    /**
77
     * Removes child model from parent model.
78
     *
79
     * @param ResourceSet $sourceResourceSet
80
     * @param object      $sourceEntityInstance
81
     * @param ResourceSet $targetResourceSet
82
     * @param object      $targetEntityInstance
83
     * @param $navPropName
84
     *
85
     * @return bool
86
     */
87
    public function unhookSingleModel(
88
        ResourceSet $sourceResourceSet,
89
        $sourceEntityInstance,
90
        ResourceSet $targetResourceSet,
91
        $targetEntityInstance,
92
        $navPropName
93
    ) {
94
        $relation = $this->isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName);
95
        assert(
96
            $sourceEntityInstance instanceof Model && $targetEntityInstance instanceof Model,
97
            'Both input entities must be Eloquent models'
98
        );
99
        // in case the fake 'PrimaryKey' attribute got set inbound for a polymorphic-affected model, flatten it now
100
        unset($targetEntityInstance->PrimaryKey);
101
102
        $changed = false;
103
104
        if ($relation instanceof BelongsTo) {
105
            $relation->dissociate();
106
            $changed = true;
107
        } elseif ($relation instanceof BelongsToMany) {
108
            $relation->detach($targetEntityInstance);
109
            $changed = true;
110
        } elseif ($relation instanceof HasOneOrMany) {
111
            // dig up inverse property name, so we can pass it to unhookSingleModel with source and target elements
112
            // swapped
113
            $otherPropName = $this->getMetadataProvider()
114
                ->resolveReverseProperty($sourceEntityInstance, $navPropName);
115
            if (null === $otherPropName) {
116
                $srcClass = get_class($sourceEntityInstance);
117
                $msg = 'Bad navigation property, ' . $navPropName . ', on source model ' . $srcClass;
118
                throw new \InvalidArgumentException($msg);
119
            }
120
            $this->unhookSingleModel(
121
                $targetResourceSet,
122
                $targetEntityInstance,
123
                $sourceResourceSet,
124
                $sourceEntityInstance,
125
                $otherPropName
126
            );
127
        }
128
        if ($changed) {
129
            LaravelQuery::queueModel($sourceEntityInstance);
130
            LaravelQuery::queueModel($targetEntityInstance);
131
        }
132
        return true;
133
    }
134
135
    /**
136
     * @param $sourceEntityInstance
137
     * @param $targetEntityInstance
138
     * @param $navPropName
139
     * @throws \InvalidArgumentException
140
     * @return Relation
141
     */
142
    protected function isModelHookInputsOk($sourceEntityInstance, $targetEntityInstance, $navPropName)
143
    {
144
        if (!$sourceEntityInstance instanceof Model || !$targetEntityInstance instanceof Model) {
145
            $msg = 'Both source and target must be Eloquent models';
146
            throw new \InvalidArgumentException($msg);
147
        }
148
        $relation = $sourceEntityInstance->$navPropName();
149
        if (!$relation instanceof Relation) {
150
            $msg = 'Navigation property must be an Eloquent relation';
151
            throw new \InvalidArgumentException($msg);
152
        }
153
        $targType = $relation->getRelated();
154
        if (!$targetEntityInstance instanceof $targType) {
155
            $msg = 'Target instance must be of type compatible with relation declared in method ' . $navPropName;
156
            throw new \InvalidArgumentException($msg);
157
        }
158
        return $relation;
159
    }
160
161
    /**
162
     * Dig out local copy of POData-Laravel metadata provider.
163
     *
164
     * @return MetadataProvider
165
     */
166
    public function getMetadataProvider()
167
    {
168
        return $this->metadataProvider;
169
    }
170
}
171