Completed
Branch 3.3 (d40063)
by Pieter
04:00
created

ApiResourceFacade::postSubAction()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 26
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 19
c 0
b 0
f 0
nc 3
nop 4
dl 0
loc 26
rs 9.6333
1
<?php
2
namespace W2w\Lib\Apie\Core;
3
4
use Psr\Http\Message\RequestInterface;
5
use Psr\Http\Message\ServerRequestInterface;
6
use W2w\Lib\Apie\Core\Models\ApiResourceFacadeResponse;
7
use W2w\Lib\Apie\Core\SearchFilters\SearchFilterRequest;
8
use W2w\Lib\Apie\Events\DeleteResourceEvent;
9
use W2w\Lib\Apie\Events\ModifySingleResourceEvent;
10
use W2w\Lib\Apie\Events\RetrievePaginatedResourcesEvent;
11
use W2w\Lib\Apie\Events\RetrieveSingleResourceEvent;
12
use W2w\Lib\Apie\Events\StoreExistingResourceEvent;
13
use W2w\Lib\Apie\Events\StoreNewResourceEvent;
14
use W2w\Lib\Apie\Exceptions\MethodNotAllowedException;
15
use W2w\Lib\Apie\Interfaces\FormatRetrieverInterface;
16
use W2w\Lib\Apie\Interfaces\ResourceSerializerInterface;
17
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubAction;
18
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubActionContainer;
19
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubActionFactory;
20
use W2w\Lib\Apie\PluginInterfaces\ResourceLifeCycleInterface;
21
use W2w\Lib\Apie\Plugins\Core\Serializers\SymfonySerializerAdapter;
22
23
class ApiResourceFacade
24
{
25
    /**
26
     * @var ApiResourceRetriever
27
     */
28
    private $retriever;
29
30
    /**
31
     * @var ApiResourcePersister
32
     */
33
    private $persister;
34
35
    /**
36
     * @var ClassResourceConverter
37
     */
38
    private $converter;
39
40
    /**
41
     * @var ResourceSerializerInterface
42
     */
43
    private $serializer;
44
45
    /**
46
     * @var FormatRetrieverInterface
47
     */
48
    private $formatRetriever;
49
50
    /**
51
     * @var ResourceLifeCycleInterface[]
52
     */
53
    private $resourceLifeCycles;
54
55
    /**
56
     * @var SubActionContainer
57
     */
58
    private $subActionContainer;
59
60
    public function __construct(
61
        ApiResourceRetriever $retriever,
62
        ApiResourcePersister $persister,
63
        ClassResourceConverter $converter,
64
        ResourceSerializerInterface $serializer,
65
        FormatRetrieverInterface $formatRetriever,
66
        SubActionContainer $subActionContainer,
67
        iterable $resourceLifeCycles
68
    ) {
69
        $this->retriever = $retriever;
70
        $this->persister = $persister;
71
        $this->converter = $converter;
72
        $this->serializer = $serializer;
73
        $this->formatRetriever = $formatRetriever;
74
        $this->subActionContainer = $subActionContainer;
75
        $this->resourceLifeCycles = $resourceLifeCycles;
0 ignored issues
show
Documentation Bug introduced by
It seems like $resourceLifeCycles of type iterable is incompatible with the declared type W2w\Lib\Apie\PluginInter...rceLifeCycleInterface[] of property $resourceLifeCycles.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
76
    }
77
78
    private function runLifeCycleEvent(string $event, ...$args)
79
    {
80
        foreach ($this->resourceLifeCycles as $resourceLifeCycle) {
81
            $resourceLifeCycle->$event(...$args);
82
        }
83
    }
84
85
    /**
86
     * Does a DELETE instance call.
87
     *
88
     * @param string $resourceClass
89
     * @param string $id
90
     * @return ApiResourceFacadeResponse
91
     */
92
    public function delete(string $resourceClass, string $id): ApiResourceFacadeResponse
93
    {
94
        $event = new DeleteResourceEvent($resourceClass, $id);
95
        $this->runLifeCycleEvent('onPreDeleteResource', $event);
96
        $this->persister->delete($resourceClass, $id);
97
        $this->runLifeCycleEvent('onPostDeleteResource', $event);
98
99
        return new ApiResourceFacadeResponse(
100
            $this->serializer,
101
            null,
102
            'application/json'
103
        );
104
    }
105
106
    /**
107
     * Does a GET instance call.
108
     *
109
     * @param string $resourceClass
110
     * @param string $id
111
     * @param RequestInterface|null $request
112
     * @return ApiResourceFacadeResponse
113
     */
114
    public function get(string $resourceClass, string $id, ?RequestInterface $request): ApiResourceFacadeResponse
115
    {
116
        $event = new RetrieveSingleResourceEvent($resourceClass, $id, $request);
117
        $this->runLifeCycleEvent('onPreRetrieveResource', $event);
118
        // preRetrieveResource event could override resource...
119
        if (!$event->getResource()) {
120
            $event->setResource($this->retriever->retrieve($resourceClass, $id));
121
        }
122
        $this->runLifeCycleEvent('onPostRetrieveResource', $event);
123
124
        return $this->createResponse($event->getResource(), $request);
125
    }
126
127
    /**
128
     * Does a GET all call.
129
     *
130
     * @param string $resourceClass
131
     * @param ServerRequestInterface|null $request
132
     * @return ApiResourceFacadeResponse
133
     */
134
    public function getAll(string $resourceClass, ?ServerRequestInterface $request): ApiResourceFacadeResponse
135
    {
136
        $searchFilterRequest = new SearchFilterRequest();
137
        if ($request) {
138
            $searchFilterRequest = SearchFilterRequest::createFromPsrRequest($request);
139
        }
140
        $event = new RetrievePaginatedResourcesEvent($resourceClass, $searchFilterRequest, $request);
141
        $this->runLifeCycleEvent('onPreRetrieveAllResources', $event);
142
        if (null === $event->getResources()) {
143
            $event->setResources($this->retriever->retrieveAll($resourceClass, $searchFilterRequest));
144
        }
145
        $this->runLifeCycleEvent('onPostRetrieveAllResources', $event);
146
147
        return $this->createResponse($event->getResources(), $request);
148
    }
149
150
    /**
151
     * Does a PUT instance call.
152
     *
153
     * @param string $resourceClass
154
     * @param string $id
155
     * @param RequestInterface $request
156
     * @return ApiResourceFacadeResponse
157
     */
158
    public function put(string $resourceClass, string $id, RequestInterface $request): ApiResourceFacadeResponse
159
    {
160
        $resource = $this->get($resourceClass, $id, $request)->getResource();
161
        $event = new ModifySingleResourceEvent($resource, $id, $request);
162
        $this->runLifeCycleEvent('onPreModifyResource', $event);
163
        $request = $event->getRequest();
164
165
        $event->setResource(
166
            $this->serializer->putData(
167
                $event->getResource(),
168
                (string) $request->getBody(),
169
                $request->getHeader('Content-Type')[0] ?? 'application/json'
170
            )
171
        );
172
        $this->runLifeCycleEvent('onPostModifyResource', $event);
173
174
        $event = new StoreExistingResourceEvent($event);
175
        $this->runLifeCycleEvent('onPrePersistExistingResource', $event);
176
        $event->setResource($this->persister->persistExisting($event->getResource(), $id));
177
        $this->runLifeCycleEvent('onPostPersistExistingResource', $event);
178
179
        return $this->createResponse($event->getResource(), $request);
180
    }
181
182
    /**
183
     * Does a POST new instance call.
184
     *
185
     * @param string $resourceClass
186
     * @param RequestInterface $request
187
     * @return ApiResourceFacadeResponse
188
     */
189
    public function post(string $resourceClass, RequestInterface $request): ApiResourceFacadeResponse
190
    {
191
        $event = new StoreNewResourceEvent($resourceClass, $request);
192
        $this->runLifeCycleEvent('onPreCreateResource', $event);
193
        if (!$event->getResource()) {
194
            $event->setResource($this->serializer->postData(
195
                $resourceClass,
196
                (string)$event->getRequest()->getBody(),
197
                $event->getRequest()->getHeader('Content-Type')[0] ?? 'application/json'
198
            ));
199
        }
200
        $this->runLifeCycleEvent('onPostCreateResource', $event);
201
        $event = new StoreExistingResourceEvent($event);
202
        $this->runLifeCycleEvent('onPrePersistNewResource', $event);
203
        $event->setResource($this->persister->persistNew($event->getResource()));
204
        $this->runLifeCycleEvent('onPostPersistNewResource', $event);
205
206
207
        return $this->createResponse($event->getResource(), $request);
208
    }
209
210
    /**
211
     * Runs a sub action.
212
     * @param string $resourceClass
213
     * @param string $id
214
     * @param string $actionName
215
     * @param RequestInterface $request
216
     * @return ApiResourceFacadeResponse
217
     * @throws \Symfony\Component\Serializer\Exception\ExceptionInterface
218
     */
219
    public function postSubAction(string $resourceClass, string $id, string $actionName, RequestInterface $request): ApiResourceFacadeResponse
220
    {
221
        $subActions = $this->subActionContainer->getSubActionsForResourceClass($resourceClass);
222
        if (empty($subActions[$actionName])) {
223
            throw new MethodNotAllowedException('POST');
224
        }
225
        /** @var SubAction $subAction */
226
        $subAction = $subActions[$actionName];
227
        $resource = $this->get($resourceClass, $id, $request)->getResource();
228
        $reflectionMethod = $subAction->getReflectionMethod();
229
        $context = [
230
            'initial-arguments' => [],
231
            'object-instance' => $subAction->getObject(),
232
        ];
233
        assert($this->serializer instanceof SymfonySerializerAdapter);
234
        $parameters = $reflectionMethod->getParameters();
235
        if ($parameters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameters of type ReflectionParameter[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
236
            $context['initial-arguments'][$parameters[0]->getName()] = $resource;
237
        }
238
        $data = $this->serializer->getSerializer()->deserialize(
239
            $request->getBody(),
240
            'ReflectionMethod::' . get_class($subAction->getObject()) . '::' . $reflectionMethod->getName(),
241
            'json',
242
            $context
243
        );
244
        return $this->createResponse($data, $request);
245
    }
246
247
    /**
248
     * Creates a ApiResourceFacadeResponse instance.
249
     *
250
     * @param mixed $resource
251
     * @param RequestInterface|null $request
252
     * @return ApiResourceFacadeResponse
253
     */
254
    private function createResponse($resource, ?RequestInterface $request): ApiResourceFacadeResponse
255
    {
256
        return new ApiResourceFacadeResponse(
257
            $this->serializer,
258
            $resource,
259
            ($request && $request->hasHeader('Accept')) ? $request->getHeader('Accept')[0] : 'application/json',
260
            $this->resourceLifeCycles
261
        );
262
    }
263
}
264