Completed
Push — master ( e3d126...e49edb )
by Alex
19s queued 11s
created

createUpdateDeleteProcessInput()   A

Complexity

Conditions 6
Paths 7

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 9.2222
cc 6
nc 7
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[] = $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
        $keys = array_keys($outData);
64
        $expKeys = ['id', 'status', 'errors'];
65
        $isGood = $expKeys === (array_intersect($expKeys, $keys));
66
67
        if (!$isGood) {
68
            throw ODataException::createInternalServerError(
69
                'Controller response array missing at least one of id, status and/or errors fields.'
70
            );
71
        }
72
        return $outData;
73
    }
74
75
    /**
76
     * @param ResourceSet                $sourceResourceSet
77
     * @param mixed[]                    $data
78
     * @param string                     $verb
79
     * @param  Model|null                $source
80
     * @param bool                       $shouldUpdate
81
     * @throws InvalidOperationException
82
     * @throws ODataException
83
     * @throws \Exception
84
     * @return Model|null
85
     */
86
    protected function createUpdateCoreWrapper(
87
        ResourceSet $sourceResourceSet,
88
        array $data,
89
        string $verb,
90
        Model $source = null,
91
        bool $shouldUpdate
92
    ) {
93
        $lastWord = 'update' == $verb ? 'updated' : 'created';
94
        $class    = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
95
        if (!$this->getAuth()->canAuth($this->getVerbMap()[$verb], $class, $source)) {
96
            throw new ODataException('Access denied', 403);
97
        }
98
99
        $payload = $this->createUpdateDeleteCore($source, $data, $class, $verb);
100
101
        $success = isset($payload['id']);
102
103
        if ($success) {
104
            try {
105
                return $class::findOrFail($payload['id']);
106
            } catch (\Exception $e) {
107
                throw new ODataException($e->getMessage(), 500);
108
            }
109
        }
110
        throw new ODataException('Target model not successfully ' . $lastWord, 422);
111
    }
112
113
114
    /**
115
     * @param Model|null $sourceEntityInstance
116
     * @param mixed[] $data
117
     * @param string $class
118
     * @param string $verb
119
     *
120
     * @throws ODataException
121
     * @throws InvalidOperationException
122
     * @return array|mixed
123
     */
124
    protected function createUpdateDeleteCore(?Model $sourceEntityInstance, array $data, string $class, string $verb)
125
    {
126
        $raw = $this->getControllerContainer();
127
        $map = $raw->getMetadata();
128
129
        if (!array_key_exists($class, $map)) {
130
            throw new InvalidOperationException('Controller mapping missing for class ' . $class . '.');
131
        }
132
        $goal = $raw->getMapping($class, $verb);
133
        if (null == $goal) {
134
            throw new InvalidOperationException(
135
                'Controller mapping missing for ' . $verb . ' verb on class ' . $class . '.'
136
            );
137
        }
138
139
        $controlClass = $goal['controller'];
140
        $method       = $goal['method'];
141
        $paramList    = $goal['parameters'];
142
        $controller   = App::make($controlClass);
143
        $parms        = $this->createUpdateDeleteProcessInput($data, $paramList, $sourceEntityInstance);
144
        unset($data);
145
146
        $result = call_user_func_array(array($controller, $method), $parms);
147
148
        return $this->createUpdateDeleteProcessOutput($result);
149
    }
150
151
    /**
152
     * Delete resource from a resource set.
153
     *
154
     * @param ResourceSet $sourceResourceSet
155
     * @param object      $sourceEntityInstance
156
     *
157
     * @throws \Exception
158
     * @return bool       true if resources successfully deleted, otherwise false
159
     */
160
    public function deleteResource(
161
        ResourceSet $sourceResourceSet,
162
        $sourceEntityInstance
163
    ) {
164
        $source = $this->unpackSourceEntity($sourceEntityInstance);
165
166
        $verb = 'delete';
167
        if (!($source instanceof Model)) {
168
            throw new InvalidArgumentException('Source entity must be an Eloquent model.');
169
        }
170
171
        $class = $sourceResourceSet->getResourceType()->getInstanceType()->getName();
172
        $id    = $source->getKey();
173
        $name  = $source->getKeyName();
174
175
        $data  = [$name => $id];
176
177
        $data = $this->createUpdateDeleteCore($source, $data, $class, $verb);
178
179
        $success = isset($data['id']);
180
        if ($success) {
181
            return true;
182
        }
183
        throw new ODataException('Target model not successfully deleted', 422);
184
    }
185
186
    /**
187
     * @param ResourceSet           $resourceSet          The entity set containing the entity to fetch
188
     * @param Model|Relation|null   $sourceEntityInstance The source entity instance
189
     * @param object                $data                 The new data for the entity instance
190
     *
191
     * @throws \Exception
192
     * @return Model|null returns the newly created model if successful,
193
     *                    or null if model creation failed
194
     */
195
    public function createResourceforResourceSet(
196
        ResourceSet $resourceSet,
197
        $sourceEntityInstance,
198
        $data
199
    ) {
200
        $verb = 'create';
201
        $data = (array) $data;
202
        return $this->createUpdateMainWrapper($resourceSet, $sourceEntityInstance, $data, $verb, false);
203
    }
204
205
    /**
206
     * Updates a resource.
207
     *
208
     * @param ResourceSet    $sourceResourceSet    The entity set containing the source entity
209
     * @param Model|Relation $sourceEntityInstance The source entity instance
210
     * @param KeyDescriptor  $keyDescriptor        The key identifying the entity to fetch
211
     * @param object         $data                 the New data for the entity instance
212
     * @param bool           $shouldUpdate         Should undefined values be updated or reset to default
213
     *
214
     * @throws \Exception
215
     * @return Model|null the new resource value if it is assignable or throw exception for null
216
     */
217
    public function updateResource(
218
        ResourceSet $sourceResourceSet,
219
        $sourceEntityInstance,
220
        KeyDescriptor $keyDescriptor,
221
        $data,
222
        bool $shouldUpdate = false
223
    ) {
224
        $verb = 'update';
225
        $data = (array) $data;
226
        return $this->createUpdateMainWrapper($sourceResourceSet, $sourceEntityInstance, $data, $verb, $shouldUpdate);
227
    }
228
229
    /**
230
     * Puts an entity instance to entity set identified by a key.
231
     *
232
     * @param ResourceSet   $resourceSet   The entity set containing the entity to update
233
     * @param KeyDescriptor $keyDescriptor The key identifying the entity to update
234
     * @param mixed[]       $data
235
     *
236
     * @return bool|null Returns result of executing query
237
     */
238
    public function putResource(
239
        ResourceSet $resourceSet,
240
        KeyDescriptor $keyDescriptor,
241
        $data
242
    ) {
243
        // TODO: Implement putResource() method.
244
        return true;
245
    }
246
247
248
    /**
249
     * @param  ResourceSet               $resourceSet
250
     * @param  Model|Relation|null       $sourceEntityInstance
251
     * @param  mixed[]                   $data
252
     * @param  string                    $verb
253
     * @param  bool                      $shouldUpdate
254
     * @throws InvalidOperationException
255
     * @throws ODataException
256
     * @return Model|null
257
     */
258
    protected function createUpdateMainWrapper(
259
        ResourceSet $resourceSet,
260
        $sourceEntityInstance,
261
        array $data,
262
        string $verb,
263
        bool $shouldUpdate
264
    ) {
265
        /** @var Model|null $source */
266
        $source = $this->unpackSourceEntity($sourceEntityInstance);
267
268
        $result = $this->createUpdateCoreWrapper($resourceSet, $data, $verb, $source, false);
269
        if (null !== $result) {
270
            LaravelQuery::queueModel($result);
271
        }
272
        return $result;
273
    }
274
}
275