1+ <?php
2+ /*
3+ * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4+ *
5+ * Copyright (C) 2019 - 2025 Jan Böhmer (https://github.com/jbtronics)
6+ *
7+ * This program is free software: you can redistribute it and/or modify
8+ * it under the terms of the GNU Affero General Public License as published
9+ * by the Free Software Foundation, either version 3 of the License, or
10+ * (at your option) any later version.
11+ *
12+ * This program is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+ * GNU Affero General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Affero General Public License
18+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
19+ */
20+
21+ declare (strict_types=1 );
22+
23+
24+ namespace App \ApiPlatform ;
25+
26+ use ApiPlatform \Metadata \Property \Factory \PropertyNameCollectionFactoryInterface ;
27+ use ApiPlatform \Metadata \Property \PropertyNameCollection ;
28+ use Symfony \Component \DependencyInjection \Attribute \AsDecorator ;
29+ use function Symfony \Component \String \u ;
30+
31+ /**
32+ * This decorator removes all camelCase property names from the property name collection, if a snake_case version exists.
33+ * This is a fix for https://github.com/Part-DB/Part-DB-server/issues/862, as the openapi schema generator wrongly collects
34+ * both camelCase and snake_case property names, which leads to duplicate properties in the schema.
35+ * This seems to come from the fact that the openapi schema generator uses no serializerContext, which seems then to collect
36+ * the getters too...
37+ */
38+ #[AsDecorator('api_platform.metadata.property.name_collection_factory ' )]
39+ class NormalizePropertyNameCollectionFactory implements PropertyNameCollectionFactoryInterface
40+ {
41+ public function __construct (private readonly PropertyNameCollectionFactoryInterface $ decorated )
42+ {
43+ }
44+
45+ public function create (string $ resourceClass , array $ options = []): PropertyNameCollection
46+ {
47+ // Get the default properties from the decorated service
48+ $ propertyNames = $ this ->decorated ->create ($ resourceClass , $ options );
49+
50+ //Only become active in the context of the openapi schema generation
51+ if (!isset ($ options ['schema_type ' ])) {
52+ return $ propertyNames ;
53+ }
54+
55+ //If we are not in the jsonapi generator (which sets no serializer groups), return the property names as is
56+ if (isset ($ options ['serializer_groups ' ])) {
57+ return $ propertyNames ;
58+ }
59+
60+ //Remove all camelCase property names from the collection, if a snake_case version exists
61+ $ properties = iterator_to_array ($ propertyNames );
62+
63+ foreach ($ properties as $ property ) {
64+ if (str_contains ($ property , '_ ' )) {
65+ $ camelized = u ($ property )->camel ()->toString ();
66+
67+ //If the camelized version exists, remove it from the collection
68+ $ index = array_search ($ camelized , $ properties );
69+ if ($ index !== false ) {
70+ unset($ properties [$ index ]);
71+ }
72+ }
73+ }
74+
75+ return new PropertyNameCollection ($ properties );
76+ }
77+ }
0 commit comments