Completed
Push — master ( 9f24e3...f5ae7e )
by Alex
16s queued 12s
created

createUpdateDeleteProcessInput()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 21
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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