Passed
Pull Request — master (#203)
by Alex
04:49
created

LaravelWriteQuery::createResourceforResourceSet()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 2
c 1
b 0
f 0
dl 0
loc 7
rs 10
cc 1
nc 1
nop 3
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: alex
5
 * Date: 29/09/19
6
 * Time: 6:08 PM
7
 */
8
9
namespace AlgoWeb\PODataLaravel\Query;
10
11
use Illuminate\Database\Eloquent\Model;
12
use Illuminate\Database\Eloquent\Relations\Relation;
13
use Illuminate\Http\Request;
14
use Illuminate\Support\Facades\App;
15
use POData\Common\InvalidOperationException;
16
use POData\Common\ODataException;
17
use POData\Providers\Metadata\ResourceSet;
18
use POData\UriProcessor\ResourcePathProcessor\SegmentParser\KeyDescriptor;
19
use Symfony\Component\Process\Exception\InvalidArgumentException;
20
21
class LaravelWriteQuery extends LaravelBaseQuery
22
{
23
    /**
24
     * @param $data
25
     * @param $paramList
26
     * @param Model|null $sourceEntityInstance
27
     * @return array
28
     */
29
    protected function createUpdateDeleteProcessInput($data, $paramList, Model $sourceEntityInstance)
30
    {
31
        $parms = [];
32
33
        foreach ($paramList as $spec) {
34
            $varType = isset($spec['type']) ? $spec['type'] : null;
35
            $varName = $spec['name'];
36
            if (null == $varType) {
37
                $parms[] = ('id' == $varName) ? $sourceEntityInstance->getKey() : $sourceEntityInstance->$varName;
38
                continue;
39
            }
40
            // TODO: Give this smarts and actively pick up instantiation details
41
            /** @var Request $var */
42
            $var = new $varType();
43
            if ($spec['isRequest']) {
44
                $var->setMethod('POST');
45
                $var->request = new \Symfony\Component\HttpFoundation\ParameterBag($data);
46
            }
47
            $parms[] = $var;
48
        }
49
        return $parms;
50
    }
51
52
53
    /**
54
     * @param $result
55
     * @throws ODataException
56
     * @return array|mixed
57
     */
58
    protected function createUpdateDeleteProcessOutput($result)
59
    {
60
        if (!($result instanceof \Illuminate\Http\JsonResponse)) {
61
            throw ODataException::createInternalServerError('Controller response not well-formed json.');
62
        }
63
        $outData = $result->getData();
64
        if (is_object($outData)) {
65
            $outData = (array) $outData;
66
        }
67
68
        if (!is_array($outData)) {
69
            throw ODataException::createInternalServerError('Controller response does not have an array.');
70
        }
71
        if (!(key_exists('id', $outData) && key_exists('status', $outData) && key_exists('errors', $outData))) {
72
            throw ODataException::createInternalServerError(
73
                'Controller response array missing at least one of id, status and/or errors fields.'
74
            );
75
        }
76
        return $outData;
77
    }
78
79
    /**
80
     * @param ResourceSet $sourceResourceSet
81
     * @param $data
82
     * @param                            $verb
83
     * @param  Model|null                $source
84
     * @throws InvalidOperationException
85
     * @throws ODataException
86
     * @throws \Exception
87
     * @return Model|null
88
     */
89
    protected function createUpdateCoreWrapper(ResourceSet $sourceResourceSet, $data, $verb, Model $source = null)
90
    {
91
        $lastWord = 'update' == $verb ? 'updated' : 'created';
92
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
93
        if (!$this->getAuth()->canAuth($this->getVerbMap()[$verb], $class, $source)) {
94
            throw new ODataException('Access denied', 403);
95
        }
96
97
        $payload = $this->createUpdateDeleteCore($source, $data, $class, $verb);
98
99
        $success = isset($payload['id']);
100
101
        if ($success) {
102
            try {
103
                return $class::findOrFail($payload['id']);
104
            } catch (\Exception $e) {
105
                throw new ODataException($e->getMessage(), 500);
106
            }
107
        }
108
        throw new ODataException('Target model not successfully ' . $lastWord, 422);
109
    }
110
111
112
    /**
113
     * @param $sourceEntityInstance
114
     * @param $data
115
     * @param $class
116
     * @param string $verb
117
     *
118
     * @throws ODataException
119
     * @throws InvalidOperationException
120
     * @return array|mixed
121
     */
122
    protected function createUpdateDeleteCore($sourceEntityInstance, $data, $class, $verb)
123
    {
124
        $raw = $this->getControllerContainer();
125
        $map = $raw->getMetadata();
126
127
        if (!array_key_exists($class, $map)) {
128
            throw new InvalidOperationException('Controller mapping missing for class ' . $class . '.');
129
        }
130
        $goal = $raw->getMapping($class, $verb);
131
        if (null == $goal) {
132
            throw new InvalidOperationException(
133
                'Controller mapping missing for ' . $verb . ' verb on class ' . $class . '.'
134
            );
135
        }
136
137
        $arrayData = is_object($data) ? (array)$data : $data;
138
        if (!is_array($arrayData)) {
139
            throw ODataException::createPreConditionFailedError(
140
                'Data not resolvable to key-value array.'
141
            );
142
        }
143
144
        $controlClass = $goal['controller'];
145
        $method = $goal['method'];
146
        $paramList = $goal['parameters'];
147
        $controller = App::make($controlClass);
148
        $parms = $this->createUpdateDeleteProcessInput($arrayData, $paramList, $sourceEntityInstance);
149
        unset($data);
150
151
        $result = call_user_func_array(array($controller, $method), $parms);
152
153
        return $this->createUpdateDeleteProcessOutput($result);
154
    }
155
156
    /**
157
     * Delete resource from a resource set.
158
     *
159
     * @param ResourceSet $sourceResourceSet
160
     * @param object      $sourceEntityInstance
161
     *
162
     * @return bool true if resources sucessfully deteled, otherwise false
163
     * @throws \Exception
164
     */
165
    public function deleteResource(
166
        ResourceSet $sourceResourceSet,
167
        $sourceEntityInstance
168
    ) {
169
        $source = $this->unpackSourceEntity($sourceEntityInstance);
170
171
        $verb = 'delete';
172
        if (!($source instanceof Model)) {
173
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
174
        }
175
176
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
177
        $id = $source->getKey();
178
        $name = $source->getKeyName();
179
        $data = [$name => $id];
180
181
        $data = $this->createUpdateDeleteCore($source, $data, $class, $verb);
182
183
        $success = isset($data['id']);
184
        if ($success) {
185
            return true;
186
        }
187
        throw new ODataException('Target model not successfully deleted', 422);
188
    }
189
190
    /**
191
     * @param ResourceSet     $resourceSet          The entity set containing the entity to fetch
192
     * @param Model|Relation  $sourceEntityInstance The source entity instance
193
     * @param object          $data                 the New data for the entity instance
194
     *
195
     * @return Model|null                           returns the newly created model if successful,
196
     *                                              or null if model creation failed.
197
     * @throws \Exception
198
     */
199
    public function createResourceforResourceSet(
200
        ResourceSet $resourceSet,
201
        $sourceEntityInstance,
202
        $data
203
    ) {
204
        $verb = 'create';
205
        return $this->createUpdateMainWrapper($resourceSet, $sourceEntityInstance, $data, $verb);
206
    }
207
208
    /**
209
     * Updates a resource.
210
     *
211
     * @param ResourceSet       $sourceResourceSet    The entity set containing the source entity
212
     * @param Model|Relation    $sourceEntityInstance The source entity instance
213
     * @param KeyDescriptor     $keyDescriptor        The key identifying the entity to fetch
214
     * @param object            $data                 the New data for the entity instance
215
     * @param bool              $shouldUpdate         Should undefined values be updated or reset to default
216
     *
217
     * @return Model|null the new resource value if it is assignable or throw exception for null
218
     * @throws \Exception
219
     */
220
    public function updateResource(
221
        ResourceSet $sourceResourceSet,
222
        $sourceEntityInstance,
223
        KeyDescriptor $keyDescriptor,
224
        $data,
225
        $shouldUpdate = false
226
    ) {
227
        $verb = 'update';
228
        return $this->createUpdateMainWrapper($sourceResourceSet, $sourceEntityInstance, $data, $verb);
229
    }
230
231
    /**
232
     * Puts an entity instance to entity set identified by a key.
233
     *
234
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
235
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
236
     * @param $data
237
     *
238
     * @return bool|null Returns result of executing query
239
     */
240
    public function putResource(
241
        ResourceSet $resourceSet,
242
        KeyDescriptor $keyDescriptor,
243
        $data
244
    ) {
245
        // TODO: Implement putResource() method.
246
        return true;
247
    }
248
249
250
    /**
251
     * @param ResourceSet $resourceSet
252
     * @param Model|Relation|null $sourceEntityInstance
253
     * @param mixed $data
254
     * @param mixed $verb
255
     * @return Model|null
256
     * @throws InvalidOperationException
257
     * @throws ODataException
258
     */
259
    protected function createUpdateMainWrapper(ResourceSet $resourceSet, $sourceEntityInstance, $data, $verb)
260
    {
261
        /** @var Model|null $source */
262
        $source = $this->unpackSourceEntity($sourceEntityInstance);
263
264
        $result = $this->createUpdateCoreWrapper($resourceSet, $data, $verb, $source);
265
        if (null !== $result) {
266
            LaravelQuery::queueModel($result);
267
        }
268
        return $result;
269
    }
270
}
271