Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
88.49% covered (warning)
88.49%
123 / 139
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
OpenApiOperationAddedEventSubscriber
88.49% covered (warning)
88.49%
123 / 139
66.67% covered (warning)
66.67%
2 / 3
15.34
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getSubscribedEvents
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 onOperationAdded
88.15% covered (warning)
88.15%
119 / 135
0.00% covered (danger)
0.00%
0 / 1
13.28
1<?php
2namespace Apie\RestApi\EventListeners;
3
4use Apie\Common\Actions\CreateObjectAction;
5use Apie\Common\Actions\GetItemAction;
6use Apie\Common\Actions\GetListAction;
7use Apie\Common\Actions\ModifyObjectAction;
8use Apie\Core\BoundedContext\BoundedContextId;
9use Apie\Core\Context\ApieContext;
10use Apie\Core\ContextConstants;
11use Apie\Core\Datalayers\ApieDatalayer;
12use Apie\Core\Datalayers\ApieDatalayerWithFilters;
13use Apie\Core\Enums\RequestMethod;
14use Apie\Core\Metadata\MetadataFactory;
15use Apie\RestApi\Events\OpenApiOperationAddedEvent;
16use Apie\TypeConverter\ReflectionTypeFactory;
17use cebe\openapi\spec\Parameter;
18use cebe\openapi\spec\Response;
19use cebe\openapi\spec\Schema;
20use ReflectionClass;
21use Symfony\Component\EventDispatcher\EventSubscriberInterface;
22
23class OpenApiOperationAddedEventSubscriber implements EventSubscriberInterface
24{
25    public function __construct(
26        private readonly ApieDatalayer $apieDatalayer
27    ) {
28    }
29
30    public static function getSubscribedEvents(): array
31    {
32        return [
33            OpenApiOperationAddedEvent::class => 'onOperationAdded'
34        ];
35    }
36
37    public function onOperationAdded(OpenApiOperationAddedEvent $event): void
38    {
39        $responses = $event->operation->responses ?? [];
40
41        if ($event->routeDefinition->getAction() === GetItemAction::class) {
42            $parameters = $event->operation->parameters ?? [];
43            $parameters[] = new Parameter([
44                'name' => 'If-None-Match',
45                'in' => 'header',
46                'required' => false,
47                'explode' => false,
48                'schema' => new Schema([
49                    'type' => 'array',
50                    'items' => new Schema([
51                        'type' => 'string',
52                    ]),
53                ]),
54            ]);
55            $event->operation->parameters = $parameters;
56            $responses = $event->operation->responses ?? [];
57            $responses[304] = new Response([
58                'description' => 'Not Modified'
59            ]);
60            $event->operation->responses = $responses;
61            return;
62        }
63
64        if ($event->routeDefinition->getAction() === ModifyObjectAction::class) {
65            $parameters = $event->operation->parameters ?? [];
66            $parameters[] = new Parameter([
67                'name' => 'If-Match',
68                'in' => 'header',
69                'required' => false,
70                'explode' => false,
71                'schema' => new Schema([
72                    'type' => 'array',
73                    'items' => new Schema([
74                        'type' => 'string',
75                    ]),
76                ]),
77            ]);
78            $event->operation->parameters = $parameters;
79            return;
80        }
81
82        if (
83            $event->routeDefinition->getAction() === CreateObjectAction::class
84            && $event->method === RequestMethod::PUT
85        ) {
86            $parameters = $event->operation->parameters ?? [];
87            $parameters[] = new Parameter([
88                'name' => 'If-Match',
89                'in' => 'header',
90                'required' => false,
91                'explode' => false,
92                'schema' => new Schema([
93                    'type' => 'array',
94                    'items' => new Schema([
95                        'type' => 'string',
96                    ]),
97                ]),
98            ]);
99            $event->operation->parameters = $parameters;
100            return;
101        }
102
103        if ($this->apieDatalayer instanceof ApieDatalayerWithFilters) {
104            $routeAttributes = $event->routeDefinition->getRouteAttributes();
105            $resourceName = $routeAttributes[ContextConstants::RESOURCE_NAME] ?? null;
106            $boundedContextId = $routeAttributes[ContextConstants::BOUNDED_CONTEXT_ID] ?? 'default';
107            if ($resourceName && $event->routeDefinition->getAction() === GetListAction::class) {
108                $refl = new ReflectionClass($resourceName);
109                $fieldMetadata = MetadataFactory::getResultMetadata(
110                    $refl,
111                    new ApieContext()
112                )->getHashmap();
113                $filterColumns = $this->apieDatalayer->getFilterColumns($refl, new BoundedContextId($boundedContextId));
114                // TODO: this assumes that if there are no filters, there is also no global search?
115                if (null === $filterColumns) {
116                    return;
117                }
118                $operation = $event->operation;
119                $parameters = $operation->parameters ?? [];
120                $parameters ??= [];
121                $parameters[] = new Parameter([
122                    'name' => 'items_per_page',
123                    'in' => 'query',
124                    'schema' => new Schema([
125                        'type' => 'integer',
126                        'minimum' => 1
127                    ])
128                ]);
129                $parameters[] = new Parameter([
130                    'name' => 'page',
131                    'in' => 'query',
132                    'schema' => new Schema([
133                        'type' => 'integer',
134                        'minimum' => 0
135                    ])
136                ]);
137                $parameters[] = new Parameter([
138                    'name' => 'search',
139                    'in' => 'query',
140                    'schema' => new Schema([
141                        'type' => 'string',
142                        'minLength' => 1,
143                    ])
144                ]);
145                foreach ($filterColumns as $filterColumn) {
146                    $schema = new Schema([
147                        'type' => 'string',
148                        'minLength' => 1,
149                    ]);
150                    if (isset($fieldMetadata[$filterColumn])) {
151                        $typehint = ReflectionTypeFactory::createReflectionType('string');
152                        $schema = $event->componentsBuilder->getSchemaForType(
153                            $typehint,
154                            false,
155                            true,
156                            $fieldMetadata[$filterColumn]->allowsNull()
157                        );
158                    }
159
160                    $parameters[] = new Parameter([
161                        'name' => 'query[' . $filterColumn . ']',
162                        'in' => 'query',
163                        'schema' => $schema,
164                    ]);
165                }
166                $orderByColumns = $this->apieDatalayer->getOrderByColumns($refl, new BoundedContextId($boundedContextId));
167                if ($orderByColumns?->count()) {
168                    $values = [];
169                    foreach ($orderByColumns as $orderByColumn) {
170                        array_push(
171                            $values,
172                            $orderByColumn,
173                            '+' . $orderByColumn,
174                            '-' . $orderByColumn
175                        );
176                    }
177
178                    $parameters[] = new Parameter([
179                        'name' => 'order_by',
180                        'in' => 'query',
181                        'schema' => new Schema([
182                            'type' => 'array',
183                            'items' => [
184                                'type' => 'string',
185                                'enum' => $values,
186                            ]
187                        ])
188                    ]);
189                }
190                $operation->parameters = $parameters;
191            }
192        }
193    }
194}