Completed
Branch v4 (4e54dd)
by Pieter
03:26
created

ApiResourceFacade::createResponse()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 1
nop 2
dl 0
loc 7
rs 10
1
<?php
2
namespace W2w\Lib\Apie\Core;
3
4
use Psr\Http\Message\RequestInterface;
5
use Psr\Http\Message\ServerRequestInterface;
6
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
7
use W2w\Lib\Apie\Core\Models\ApiResourceFacadeResponse;
8
use W2w\Lib\Apie\Core\SearchFilters\SearchFilterRequest;
9
use W2w\Lib\Apie\Events\DeleteResourceEvent;
10
use W2w\Lib\Apie\Events\ModifySingleResourceEvent;
11
use W2w\Lib\Apie\Events\RetrievePaginatedResourcesEvent;
12
use W2w\Lib\Apie\Events\RetrieveSingleResourceEvent;
13
use W2w\Lib\Apie\Events\StoreExistingResourceEvent;
14
use W2w\Lib\Apie\Events\StoreNewResourceEvent;
15
use W2w\Lib\Apie\Exceptions\MethodNotAllowedException;
16
use W2w\Lib\Apie\Interfaces\FormatRetrieverInterface;
17
use W2w\Lib\Apie\Interfaces\ResourceSerializerInterface;
18
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubAction;
19
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubActionContainer;
20
use W2w\Lib\Apie\OpenApiSchema\SubActions\SubActionFactory;
21
use W2w\Lib\Apie\PluginInterfaces\ResourceLifeCycleInterface;
22
use W2w\Lib\Apie\Plugins\Core\Serializers\SymfonySerializerAdapter;
23
24
class ApiResourceFacade
25
{
26
    /**
27
     * @var ApiResourceRetriever
28
     */
29
    private $retriever;
30
31
    /**
32
     * @var ApiResourcePersister
33
     */
34
    private $persister;
35
36
    /**
37
     * @var ClassResourceConverter
38
     */
39
    private $converter;
40
41
    /**
42
     * @var ResourceSerializerInterface
43
     */
44
    private $serializer;
45
46
    /**
47
     * @var FormatRetrieverInterface
48
     */
49
    private $formatRetriever;
50
51
    /**
52
     * @var ResourceLifeCycleInterface[]
53
     */
54
    private $resourceLifeCycles;
55
56
    /**
57
     * @var SubActionContainer
58
     */
59
    private $subActionContainer;
60
61
    /**
62
     * @var NameConverterInterface
63
     */
64
    private $nameConverter;
65
66
    /**
67
     * @var ApiResourceFacadeResponseFactory
68
     */
69
    private $responseFactory;
70
71
    public function __construct(
72
        ApiResourceRetriever $retriever,
73
        ApiResourcePersister $persister,
74
        ClassResourceConverter $converter,
75
        ResourceSerializerInterface $serializer,
76
        FormatRetrieverInterface $formatRetriever,
77
        SubActionContainer $subActionContainer,
78
        NameConverterInterface $nameConverter,
79
        ApiResourceFacadeResponseFactory  $responseFactory,
80
        iterable $resourceLifeCycles
81
    ) {
82
        $this->retriever = $retriever;
83
        $this->persister = $persister;
84
        $this->converter = $converter;
85
        $this->serializer = $serializer;
86
        $this->formatRetriever = $formatRetriever;
87
        $this->subActionContainer = $subActionContainer;
88
        $this->nameConverter = $nameConverter;
89
        $this->responseFactory = $responseFactory;
90
        $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...
91
    }
92
93
    private function runLifeCycleEvent(string $event, ...$args)
94
    {
95
        foreach ($this->resourceLifeCycles as $resourceLifeCycle) {
96
            $resourceLifeCycle->$event(...$args);
97
        }
98
    }
99
100
    /**
101
     * Does a DELETE instance call.
102
     *
103
     * @param string $resourceClass
104
     * @param string $id
105
     * @return ApiResourceFacadeResponse
106
     */
107
    public function delete(string $resourceClass, string $id): ApiResourceFacadeResponse
108
    {
109
        $event = new DeleteResourceEvent($resourceClass, $id);
110
        $this->runLifeCycleEvent('onPreDeleteResource', $event);
111
        $this->persister->delete($resourceClass, $id);
112
        $this->runLifeCycleEvent('onPostDeleteResource', $event);
113
114
        return new ApiResourceFacadeResponse(
115
            $this->serializer,
116
            null,
117
            'application/json'
118
        );
119
    }
120
121
    /**
122
     * Does a GET instance call.
123
     *
124
     * @param string $resourceClass
125
     * @param string $id
126
     * @param RequestInterface|null $request
127
     * @return ApiResourceFacadeResponse
128
     */
129
    public function get(string $resourceClass, string $id, ?RequestInterface $request): ApiResourceFacadeResponse
130
    {
131
        $event = new RetrieveSingleResourceEvent($resourceClass, $id, $request);
132
        $this->runLifeCycleEvent('onPreRetrieveResource', $event);
133
        // preRetrieveResource event could override resource...
134
        if (!$event->getResource()) {
135
            $event->setResource($this->retriever->retrieve($resourceClass, $id));
136
        }
137
        $this->runLifeCycleEvent('onPostRetrieveResource', $event);
138
139
        return $this->responseFactory->createResponseForResource($event->getResource(), $request);
140
    }
141
142
    /**
143
     * Does a GET all call.
144
     *
145
     * @param string $resourceClass
146
     * @param ServerRequestInterface|null $request
147
     * @return ApiResourceFacadeResponse
148
     */
149
    public function getAll(string $resourceClass, ?ServerRequestInterface $request): ApiResourceFacadeResponse
150
    {
151
        $searchFilterRequest = new SearchFilterRequest();
152
        if ($request) {
153
            $searchFilterRequest = SearchFilterRequest::createFromPsrRequest($request);
154
        }
155
        $event = new RetrievePaginatedResourcesEvent($resourceClass, $searchFilterRequest, $request);
156
        $this->runLifeCycleEvent('onPreRetrieveAllResources', $event);
157
        if (null === $event->getResources()) {
158
            $event->setResources($this->retriever->retrieveAll($resourceClass, $searchFilterRequest));
159
        }
160
        $this->runLifeCycleEvent('onPostRetrieveAllResources', $event);
161
162
        return $this->responseFactory->createResponseListForResource($event->getResources(), $resourceClass, $searchFilterRequest, $request);
163
    }
164
165
    /**
166
     * Does a PUT instance call.
167
     *
168
     * @param string $resourceClass
169
     * @param string $id
170
     * @param RequestInterface $request
171
     * @return ApiResourceFacadeResponse
172
     */
173
    public function put(string $resourceClass, string $id, RequestInterface $request): ApiResourceFacadeResponse
174
    {
175
        $resource = $this->get($resourceClass, $id, $request)->getResource();
176
        $event = new ModifySingleResourceEvent($resource, $id, $request);
177
        $this->runLifeCycleEvent('onPreModifyResource', $event);
178
        $request = $event->getRequest();
179
180
        $event->setResource(
181
            $this->serializer->putData(
182
                $event->getResource(),
183
                (string) $request->getBody(),
184
                $request->getHeader('Content-Type')[0] ?? 'application/json'
185
            )
186
        );
187
        $this->runLifeCycleEvent('onPostModifyResource', $event);
188
189
        $event = new StoreExistingResourceEvent($event);
190
        $this->runLifeCycleEvent('onPrePersistExistingResource', $event);
191
        $event->setResource($this->persister->persistExisting($event->getResource(), $id));
192
        $this->runLifeCycleEvent('onPostPersistExistingResource', $event);
193
194
        return $this->responseFactory->createResponseForResource($event->getResource(), $request);
195
    }
196
197
    /**
198
     * Does a POST new instance call.
199
     *
200
     * @param string $resourceClass
201
     * @param RequestInterface $request
202
     * @return ApiResourceFacadeResponse
203
     */
204
    public function post(string $resourceClass, RequestInterface $request): ApiResourceFacadeResponse
205
    {
206
        $event = new StoreNewResourceEvent($resourceClass, $request);
207
        $this->runLifeCycleEvent('onPreCreateResource', $event);
208
        if (!$event->getResource()) {
209
            $event->setResource($this->serializer->postData(
210
                $resourceClass,
211
                (string)$event->getRequest()->getBody(),
212
                $event->getRequest()->getHeader('Content-Type')[0] ?? 'application/json'
213
            ));
214
        }
215
        $this->runLifeCycleEvent('onPostCreateResource', $event);
216
        $event = new StoreExistingResourceEvent($event);
217
        $this->runLifeCycleEvent('onPrePersistNewResource', $event);
218
        $event->setResource($this->persister->persistNew($event->getResource()));
219
        $this->runLifeCycleEvent('onPostPersistNewResource', $event);
220
221
        return $this->responseFactory->createResponseForResource($event->getResource(), $request);
222
    }
223
224
    /**
225
     * Runs a sub action.
226
     * @param string $resourceClass
227
     * @param string $id
228
     * @param string $actionName
229
     * @param RequestInterface $request
230
     * @return ApiResourceFacadeResponse
231
     * @todo move logic to SubActionContainer
232
     */
233
    public function postSubAction(string $resourceClass, string $id, string $actionName, RequestInterface $request): ApiResourceFacadeResponse
234
    {
235
        $subActions = $this->subActionContainer->getSubActionsForResourceClass($resourceClass);
236
        if (empty($subActions[$actionName])) {
237
            throw new MethodNotAllowedException('POST');
238
        }
239
        /** @var SubAction $subAction */
240
        $subAction = $subActions[$actionName];
241
        $resource = $this->get($resourceClass, $id, $request)->getResource();
242
        $reflectionMethod = $subAction->getReflectionMethod();
243
        $context = [
244
            'initial-arguments' => [],
245
            'object-instance' => $subAction->getObject(),
246
        ];
247
        assert($this->serializer instanceof SymfonySerializerAdapter);
248
        $parameters = $reflectionMethod->getParameters();
249
        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...
250
            $context['initial-arguments'][$this->nameConverter->normalize($parameters[0]->getName())] = $resource;
251
        }
252
        $data = $this->serializer->getSerializer()->deserialize(
253
            $request->getBody(),
254
            'ReflectionMethod::' . get_class($subAction->getObject()) . '::' . $reflectionMethod->getName(),
255
            'json',
256
            $context
257
        );
258
        return $this->responseFactory->createResponseForResource($data, $request);
259
    }
260
}
261