• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

api-platform / core / 14516256486

17 Apr 2025 01:00PM UTC coverage: 6.974% (-1.6%) from 8.532%
14516256486

push

github

web-flow
feat(hydra): use `TypeInfo`'s `Type` (#7099)

Co-authored-by: Mathias Arlaud <mathias.arlaud@gmail.com>

40 of 95 new or added lines in 2 files covered. (42.11%)

399 existing lines in 8 files now uncovered.

11059 of 158579 relevant lines covered (6.97%)

6.2 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/Elasticsearch/Util/FieldDatatypeTrait.php
1
<?php
2

3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <dunglas@gmail.com>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11

12
declare(strict_types=1);
13

14
namespace ApiPlatform\Elasticsearch\Util;
15

16
use ApiPlatform\Metadata\Exception\PropertyNotFoundException;
17
use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18
use ApiPlatform\Metadata\ResourceClassResolverInterface;
19
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
20
use Symfony\Component\PropertyInfo\Type as LegacyType;
21
use Symfony\Component\TypeInfo\Type;
22
use Symfony\Component\TypeInfo\Type\CollectionType;
23
use Symfony\Component\TypeInfo\Type\CompositeTypeInterface;
24
use Symfony\Component\TypeInfo\Type\ObjectType;
25
use Symfony\Component\TypeInfo\Type\WrappingTypeInterface;
26

27
/**
28
 * Field datatypes helpers.
29
 *
30
 * @internal
31
 *
32
 * @experimental
33
 *
34
 * @author Baptiste Meyer <baptiste.meyer@gmail.com>
35
 */
36
trait FieldDatatypeTrait
37
{
38
    private readonly PropertyMetadataFactoryInterface $propertyMetadataFactory;
39

40
    private readonly ResourceClassResolverInterface $resourceClassResolver;
41

42
    /**
43
     * Is the decomposed given property of the given resource class potentially mapped as a nested field in Elasticsearch?
44
     */
45
    private function isNestedField(string $resourceClass, string $property): bool
46
    {
UNCOV
47
        return null !== $this->getNestedFieldPath($resourceClass, $property);
×
48
    }
49

50
    /**
51
     * Get the nested path to the decomposed given property (e.g.: foo.bar.baz => foo.bar).
52
     *
53
     * Elasticsearch can save arrays of Objects as nested documents.
54
     * In the case of foo.bar.baz
55
     *   foo.bar will be returned if foo.bar is an array of objects.
56
     *   If neither foo nor bar is an array, it is not a nested property and will return null.
57
     */
58
    private function getNestedFieldPath(string $resourceClass, string $property): ?string
59
    {
UNCOV
60
        $properties = explode('.', $property);
×
UNCOV
61
        $currentProperty = array_shift($properties);
×
62

63
        if (!$properties) {
×
64
            return null;
×
65
        }
66

67
        try {
UNCOV
68
            $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $currentProperty);
×
69
        } catch (PropertyNotFoundException) {
×
UNCOV
70
            return null;
×
71
        }
72

73
        if (!method_exists(PropertyInfoExtractor::class, 'getType')) {
×
UNCOV
74
            $types = $propertyMetadata->getBuiltinTypes() ?? [];
×
75

UNCOV
76
            foreach ($types as $type) {
×
77
                if (
UNCOV
78
                    LegacyType::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()
×
UNCOV
79
                    && null !== ($nextResourceClass = $type->getClassName())
×
UNCOV
80
                    && $this->resourceClassResolver->isResourceClass($nextResourceClass)
×
81
                ) {
82
                    $nestedPath = $this->getNestedFieldPath($nextResourceClass, implode('.', $properties));
×
83

84
                    return null === $nestedPath ? $nestedPath : "$currentProperty.$nestedPath";
×
85
                }
86

87
                if (
88
                    null !== ($type = $type->getCollectionValueTypes()[0] ?? null)
×
UNCOV
89
                    && LegacyType::BUILTIN_TYPE_OBJECT === $type->getBuiltinType()
×
UNCOV
90
                    && null !== ($className = $type->getClassName())
×
UNCOV
91
                    && $this->resourceClassResolver->isResourceClass($className)
×
92
                ) {
UNCOV
93
                    $nestedPath = $this->getNestedFieldPath($className, implode('.', $properties));
×
94

UNCOV
95
                    return null === $nestedPath ? $currentProperty : "$currentProperty.$nestedPath";
×
96
                }
97
            }
98

UNCOV
99
            return null;
×
100
        }
101

UNCOV
102
        $type = $propertyMetadata->getNativeType();
×
103

UNCOV
104
        if (null === $type) {
×
UNCOV
105
            return null;
×
106
        }
107

108
        /** @var class-string|null $className */
UNCOV
109
        $className = null;
×
110

UNCOV
111
        $typeIsResourceClass = function (Type $type) use (&$typeIsResourceClass, &$className): bool {
×
UNCOV
112
            return match (true) {
×
UNCOV
113
                $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($typeIsResourceClass),
×
UNCOV
114
                $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($typeIsResourceClass),
×
UNCOV
115
                $type instanceof ObjectType => $this->resourceClassResolver->isResourceClass($className = $type->getClassName()),
×
UNCOV
116
                default => false,
×
UNCOV
117
            };
×
UNCOV
118
        };
×
119

UNCOV
120
        if ($type->isSatisfiedBy($typeIsResourceClass)) {
×
UNCOV
121
            $nestedPath = $this->getNestedFieldPath($className, implode('.', $properties));
×
122

UNCOV
123
            return null === $nestedPath ? $nestedPath : "$currentProperty.$nestedPath";
×
124
        }
125

UNCOV
126
        $collectionValueTypeIsResourceClass = function (Type $type) use (&$collectionValueTypeIsResourceClass, &$className): bool {
×
UNCOV
127
            return match (true) {
×
UNCOV
128
                $type instanceof CollectionType => $type->getCollectionValueType() instanceof ObjectType && $this->resourceClassResolver->isResourceClass($className = $type->getCollectionValueType()->getClassName()),
×
UNCOV
129
                $type instanceof WrappingTypeInterface => $type->wrappedTypeIsSatisfiedBy($collectionValueTypeIsResourceClass),
×
UNCOV
130
                $type instanceof CompositeTypeInterface => $type->composedTypesAreSatisfiedBy($collectionValueTypeIsResourceClass),
×
UNCOV
131
                default => false,
×
UNCOV
132
            };
×
UNCOV
133
        };
×
134

UNCOV
135
        if ($type->isSatisfiedBy($collectionValueTypeIsResourceClass)) {
×
UNCOV
136
            $nestedPath = $this->getNestedFieldPath($className, implode('.', $properties));
×
137

UNCOV
138
            return null === $nestedPath ? $currentProperty : "$currentProperty.$nestedPath";
×
139
        }
140

UNCOV
141
        return null;
×
142
    }
143
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc