Completed
Pull Request — master (#198)
by Alex
24:54
created

LaravelWriteQuery::putResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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