Issues (332)

src/Bridge/Doctrine/Common/PropertyHelperTrait.php (1 issue)

1
<?php
2
3
/*
4
 * This file is part of the API Platform project.
5
 *
6
 * (c) Kévin Dunglas <[email protected]>
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\Core\Bridge\Doctrine\Common;
15
16
use Doctrine\Common\Persistence\ManagerRegistry;
17
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
18
use Doctrine\DBAL\Types\Type;
19
20
/**
21
 * Helper trait for getting information regarding a property using the resource metadata.
22
 *
23
 * @author Kévin Dunglas <[email protected]>
24
 * @author Théo FIDRY <[email protected]>
25
 * @author Alan Poulain <[email protected]>
26
 */
27
trait PropertyHelperTrait
28
{
29
    abstract protected function getManagerRegistry(): ManagerRegistry;
30
31
    /**
32
     * Determines whether the given property is mapped.
33
     */
34
    protected function isPropertyMapped(string $property, string $resourceClass, bool $allowAssociation = false): bool
35
    {
36
        if ($this->isPropertyNested($property, $resourceClass)) {
37
            $propertyParts = $this->splitPropertyParts($property, $resourceClass);
38
            $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
39
            $property = $propertyParts['field'];
40
        } else {
41
            $metadata = $this->getClassMetadata($resourceClass);
42
        }
43
44
        return $metadata->hasField($property) || ($allowAssociation && $metadata->hasAssociation($property));
45
    }
46
47
    /**
48
     * Determines whether the given property is nested.
49
     */
50
    protected function isPropertyNested(string $property/*, string $resourceClass*/): bool
51
    {
52
        if (\func_num_args() > 1) {
53
            $resourceClass = (string) func_get_arg(1);
54
        } else {
55
            if (__CLASS__ !== \get_class($this)) {
56
                $r = new \ReflectionMethod($this, __FUNCTION__);
57
                if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
58
                    @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), E_USER_DEPRECATED);
59
                }
60
            }
61
            $resourceClass = null;
62
        }
63
64
        $pos = strpos($property, '.');
65
        if (false === $pos) {
66
            return false;
67
        }
68
69
        return null !== $resourceClass && $this->getClassMetadata($resourceClass)->hasAssociation(substr($property, 0, $pos));
70
    }
71
72
    /**
73
     * Determines whether the given property is embedded.
74
     */
75
    protected function isPropertyEmbedded(string $property, string $resourceClass): bool
76
    {
77
        return false !== strpos($property, '.') && $this->getClassMetadata($resourceClass)->hasField($property);
78
    }
79
80
    /**
81
     * Splits the given property into parts.
82
     *
83
     * Returns an array with the following keys:
84
     *   - associations: array of associations according to nesting order
85
     *   - field: string holding the actual field (leaf node)
86
     */
87
    protected function splitPropertyParts(string $property/*, string $resourceClass*/): array
88
    {
89
        $resourceClass = null;
90
        $parts = explode('.', $property);
91
92
        if (\func_num_args() > 1) {
93
            $resourceClass = func_get_arg(1);
94
        } elseif (__CLASS__ !== \get_class($this)) {
95
            $r = new \ReflectionMethod($this, __FUNCTION__);
96
            if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
97
                @trigger_error(sprintf('Method %s() will have a second `$resourceClass` argument in version API Platform 3.0. Not defining it is deprecated since API Platform 2.1.', __FUNCTION__), E_USER_DEPRECATED);
98
            }
99
        }
100
101
        if (null === $resourceClass) {
102
            return [
103
                'associations' => \array_slice($parts, 0, -1),
104
                'field' => end($parts),
105
            ];
106
        }
107
108
        $metadata = $this->getClassMetadata($resourceClass);
109
        $slice = 0;
110
111
        foreach ($parts as $part) {
112
            if ($metadata->hasAssociation($part)) {
113
                $metadata = $this->getClassMetadata($metadata->getAssociationTargetClass($part));
114
                ++$slice;
115
            }
116
        }
117
118
        if (\count($parts) === $slice) {
119
            --$slice;
120
        }
121
122
        return [
123
            'associations' => \array_slice($parts, 0, $slice),
124
            'field' => implode('.', \array_slice($parts, $slice)),
125
        ];
126
    }
127
128
    /**
129
     * Gets the Doctrine Type of a given property/resourceClass.
130
     *
131
     * @return Type|string|null
132
     */
133
    protected function getDoctrineFieldType(string $property, string $resourceClass)
134
    {
135
        $propertyParts = $this->splitPropertyParts($property, $resourceClass);
136
        $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
137
138
        return $metadata->getTypeOfField($propertyParts['field']);
139
    }
140
141
    /**
142
     * Gets nested class metadata for the given resource.
143
     *
144
     * @param string[] $associations
145
     */
146
    protected function getNestedMetadata(string $resourceClass, array $associations): ClassMetadata
147
    {
148
        $metadata = $this->getClassMetadata($resourceClass);
149
150
        foreach ($associations as $association) {
151
            if ($metadata->hasAssociation($association)) {
152
                $associationClass = $metadata->getAssociationTargetClass($association);
153
154
                $metadata = $this->getClassMetadata($associationClass);
155
            }
156
        }
157
158
        return $metadata;
159
    }
160
161
    /**
162
     * Gets class metadata for the given resource.
163
     */
164
    protected function getClassMetadata(string $resourceClass): ClassMetadata
165
    {
166
        return $this
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getManager...etadata($resourceClass) returns the type Doctrine\Persistence\Mapping\ClassMetadata which includes types incompatible with the type-hinted return Doctrine\Common\Persistence\Mapping\ClassMetadata.
Loading history...
167
            ->getManagerRegistry()
168
            ->getManagerForClass($resourceClass)
169
            ->getClassMetadata($resourceClass);
170
    }
171
}
172