Passed
Pull Request — 2.4 (#2676)
by
unknown
08:56 queued 04:15
created

PropertyHelperTrait   A

Complexity

Total Complexity 38

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 38
eloc 77
dl 0
loc 189
rs 9.36
c 0
b 0
f 0

8 Methods

Rating   Name   Duplication   Size   Complexity  
B splitPropertyParts() 0 38 8
A getClassMetadata() 0 6 1
A getNestedMetadata() 0 13 3
A isPropertyNested() 0 20 6
A isPropertyMapped() 0 11 4
A isPropertyEmbedded() 0 3 2
C isPropertyEnabled() 0 36 13
A getDoctrineFieldType() 0 6 1
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 ApiPlatform\Core\Exception\PropertyNotFoundException;
17
use ApiPlatform\Core\Metadata\Property\Factory\PropertyMetadataFactoryInterface;
18
use ApiPlatform\Core\Metadata\Property\PropertyMetadataFactoryOptionsTrait;
19
use Doctrine\Common\Persistence\ManagerRegistry;
20
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
21
use Doctrine\DBAL\Types\Type;
22
23
/**
24
 * Helper trait for getting information regarding a property using the resource metadata.
25
 *
26
 * @author Kévin Dunglas <[email protected]>
27
 * @author Théo FIDRY <[email protected]>
28
 * @author Alan Poulain <[email protected]>
29
 */
30
trait PropertyHelperTrait
31
{
32
    use PropertyMetadataFactoryOptionsTrait;
33
34
    /** @var PropertyMetadataFactoryInterface|null */
35
    protected $propertyMetadataFactory;
36
37
    abstract protected function getManagerRegistry(): ManagerRegistry;
38
39
    /**
40
     * Determines whether the given property is enabled.
41
     */
42
    protected function isPropertyEnabled(string $property/*, string $resourceClass, array $context = []*/): bool
0 ignored issues
show
Unused Code Comprehensibility introduced by
47% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
43
    {
44
        if (\func_num_args() < 3 && __CLASS__ !== \get_class($this) && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName()) {
45
            if (\func_num_args() < 2) {
46
                @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);
47
            }
48
49
            @trigger_error(sprintf('Method %s() will have a third "$context" argument in API Platform 3.0. Not defining it is deprecated since API Platform 2.4.', __FUNCTION__), E_USER_DEPRECATED);
50
        }
51
52
        $resourceClass = 1 < \func_num_args() ? (string) func_get_arg(1) : null;
53
        $context = 2 < \func_num_args() ? (array) func_get_arg(2) : [];
54
55
        if (null !== $this->properties) {
56
            return \array_key_exists($property, $this->properties);
57
        }
58
59
        if (null !== $resourceClass && null !== $this->propertyMetadataFactory) {
60
            try {
61
                $propertyMetadata = $this->propertyMetadataFactory->create($resourceClass, $property, $this->getFactoryOptions($context));
62
            } catch (PropertyNotFoundException $e) {
63
                return false;
64
            }
65
66
            // to ensure sanity, unreadable properties must still be explicitly enabled
67
            if (!$propertyMetadata->isReadable()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $propertyMetadata->isReadable() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
68
                return false;
69
            }
70
        }
71
72
        // to ensure sanity, nested properties must still be explicitly enabled
73
        if (null === $resourceClass) {
74
            return !$this->isPropertyNested($property);
75
        }
76
77
        return !$this->isPropertyNested($property, $resourceClass);
78
    }
79
80
    /**
81
     * Determines whether the given property is mapped.
82
     */
83
    protected function isPropertyMapped(string $property, string $resourceClass, bool $allowAssociation = false): bool
84
    {
85
        if ($this->isPropertyNested($property, $resourceClass)) {
86
            $propertyParts = $this->splitPropertyParts($property, $resourceClass);
87
            $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
88
            $property = $propertyParts['field'];
89
        } else {
90
            $metadata = $this->getClassMetadata($resourceClass);
91
        }
92
93
        return $metadata->hasField($property) || ($allowAssociation && $metadata->hasAssociation($property));
94
    }
95
96
    /**
97
     * Determines whether the given property is nested.
98
     */
99
    protected function isPropertyNested(string $property/*, string $resourceClass*/): bool
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
100
    {
101
        if (\func_num_args() > 1) {
102
            $resourceClass = (string) func_get_arg(1);
103
        } else {
104
            if (__CLASS__ !== \get_class($this)) {
105
                $r = new \ReflectionMethod($this, __FUNCTION__);
106
                if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
107
                    @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);
108
                }
109
            }
110
            $resourceClass = null;
111
        }
112
113
        $pos = strpos($property, '.');
114
        if (false === $pos) {
115
            return false;
116
        }
117
118
        return null !== $resourceClass && $this->getClassMetadata($resourceClass)->hasAssociation(substr($property, 0, $pos));
119
    }
120
121
    /**
122
     * Determines whether the given property is embedded.
123
     */
124
    protected function isPropertyEmbedded(string $property, string $resourceClass): bool
125
    {
126
        return false !== strpos($property, '.') && $this->getClassMetadata($resourceClass)->hasField($property);
127
    }
128
129
    /**
130
     * Splits the given property into parts.
131
     *
132
     * Returns an array with the following keys:
133
     *   - associations: array of associations according to nesting order
134
     *   - field: string holding the actual field (leaf node)
135
     */
136
    protected function splitPropertyParts(string $property/*, string $resourceClass*/): array
0 ignored issues
show
Unused Code Comprehensibility introduced by
40% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
137
    {
138
        $resourceClass = null;
139
        $parts = explode('.', $property);
140
141
        if (\func_num_args() > 1) {
142
            $resourceClass = func_get_arg(1);
143
        } elseif (__CLASS__ !== \get_class($this)) {
144
            $r = new \ReflectionMethod($this, __FUNCTION__);
145
            if (__CLASS__ !== $r->getDeclaringClass()->getName()) {
146
                @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);
147
            }
148
        }
149
150
        if (null === $resourceClass) {
151
            return [
152
                'associations' => \array_slice($parts, 0, -1),
153
                'field' => end($parts),
154
            ];
155
        }
156
157
        $metadata = $this->getClassMetadata($resourceClass);
158
        $slice = 0;
159
160
        foreach ($parts as $part) {
161
            if ($metadata->hasAssociation($part)) {
162
                $metadata = $this->getClassMetadata($metadata->getAssociationTargetClass($part));
163
                ++$slice;
164
            }
165
        }
166
167
        if (\count($parts) === $slice) {
168
            --$slice;
169
        }
170
171
        return [
172
            'associations' => \array_slice($parts, 0, $slice),
173
            'field' => implode('.', \array_slice($parts, $slice)),
174
        ];
175
    }
176
177
    /**
178
     * Gets the Doctrine Type of a given property/resourceClass.
179
     *
180
     * @return Type|string|null
181
     */
182
    protected function getDoctrineFieldType(string $property, string $resourceClass)
183
    {
184
        $propertyParts = $this->splitPropertyParts($property, $resourceClass);
185
        $metadata = $this->getNestedMetadata($resourceClass, $propertyParts['associations']);
186
187
        return $metadata->getTypeOfField($propertyParts['field']);
188
    }
189
190
    /**
191
     * Gets nested class metadata for the given resource.
192
     *
193
     * @param string[] $associations
194
     */
195
    protected function getNestedMetadata(string $resourceClass, array $associations): ClassMetadata
196
    {
197
        $metadata = $this->getClassMetadata($resourceClass);
198
199
        foreach ($associations as $association) {
200
            if ($metadata->hasAssociation($association)) {
201
                $associationClass = $metadata->getAssociationTargetClass($association);
202
203
                $metadata = $this->getClassMetadata($associationClass);
204
            }
205
        }
206
207
        return $metadata;
208
    }
209
210
    /**
211
     * Gets class metadata for the given resource.
212
     */
213
    protected function getClassMetadata(string $resourceClass): ClassMetadata
214
    {
215
        return $this
216
            ->getManagerRegistry()
217
            ->getManagerForClass($resourceClass)
218
            ->getClassMetadata($resourceClass);
219
    }
220
}
221