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

api-platform / core / 15133993414

20 May 2025 09:30AM UTC coverage: 26.313% (-1.2%) from 27.493%
15133993414

Pull #7161

github

web-flow
Merge e2c03d45f into 5459ba375
Pull Request #7161: fix(metadata): infer parameter string type from schema

0 of 2 new or added lines in 1 file covered. (0.0%)

11019 existing lines in 363 files now uncovered.

12898 of 49018 relevant lines covered (26.31%)

34.33 hits per line

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

91.3
/src/Doctrine/Common/State/PersistProcessor.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\Doctrine\Common\State;
15

16
use ApiPlatform\Metadata\HttpOperation;
17
use ApiPlatform\Metadata\Operation;
18
use ApiPlatform\Metadata\Util\ClassInfoTrait;
19
use ApiPlatform\State\ProcessorInterface;
20
use Doctrine\Persistence\ManagerRegistry;
21
use Doctrine\Persistence\ObjectManager as DoctrineObjectManager;
22

23
final class PersistProcessor implements ProcessorInterface
24
{
25
    use ClassInfoTrait;
26
    use LinksHandlerTrait;
27

28
    public function __construct(private readonly ManagerRegistry $managerRegistry)
29
    {
UNCOV
30
    }
164✔
31

32
    /**
33
     * @template T
34
     *
35
     * @param T $data
36
     *
37
     * @return T
38
     */
39
    public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): mixed
40
    {
41
        if (
UNCOV
42
            !\is_object($data)
164✔
UNCOV
43
            || !$manager = $this->managerRegistry->getManagerForClass($class = $this->getObjectClass($data))
164✔
44
        ) {
45
            return $data;
×
46
        }
47

48
        // PUT: reset the existing object managed by Doctrine and merge data sent by the user in it
49
        // This custom logic is needed because EntityManager::merge() has been deprecated and UPSERT isn't supported:
50
        // https://github.com/doctrine/orm/issues/8461#issuecomment-1250233555
UNCOV
51
        if ($operation instanceof HttpOperation && 'PUT' === $operation->getMethod() && ($operation->getExtraProperties()['standard_put'] ?? true)) {
164✔
UNCOV
52
            \assert(method_exists($manager, 'getReference'));
53
            $newData = $data;
12✔
54
            $identifiers = array_reverse($uriVariables);
12✔
55
            $links = $this->getLinks($class, $operation, $context);
12✔
56
            $reflectionProperties = $this->getReflectionProperties($data);
12✔
57

58
            // TODO: the call to getReference is most likely to fail with complex identifiers
59
            if ($previousData = $context['previous_data']) {
12✔
60
                $classMetadata = $manager->getClassMetadata($class);
10✔
61
                $identifiers = $classMetadata->getIdentifierValues($previousData);
10✔
62
                $newData = 1 === \count($identifiers) ? $manager->getReference($class, current($identifiers)) : clone $previousData;
10✔
63

64
                foreach ($reflectionProperties as $propertyName => $reflectionProperty) {
10✔
65
                    // // Don't override the property if it's part of the subresource system
66
                    if (isset($identifiers[$propertyName]) || isset($uriVariables[$propertyName])) {
10✔
67
                        continue;
10✔
68
                    }
69

70
                    // Skip URI variables as sometime an uri variable is not the doctrine identifier
71
                    foreach ($links as $link) {
10✔
72
                        if (\in_array($propertyName, $link->getIdentifiers(), true)) {
10✔
73
                            continue 2;
×
74
                        }
75
                    }
76

77
                    if (($newValue = $reflectionProperty->getValue($data)) !== $reflectionProperty->getValue($newData)) {
10✔
78
                        $reflectionProperty->setValue($newData, $newValue);
6✔
79
                    }
80
                }
81
            } else {
82
                foreach (array_reverse($links) as $link) {
2✔
83
                    if ($link->getExpandedValue() || !$link->getFromClass()) {
2✔
84
                        continue;
×
85
                    }
86

87
                    $identifierProperties = $link->getIdentifiers();
2✔
88
                    $hasCompositeIdentifiers = 1 < \count($identifierProperties);
2✔
89

90
                    foreach ($identifierProperties as $identifierProperty) {
2✔
91
                        $reflectionProperty = $reflectionProperties[$identifierProperty];
2✔
92
                        $reflectionProperty->setValue($newData, $this->getIdentifierValue($identifiers, $hasCompositeIdentifiers ? $identifierProperty : null));
2✔
93
                    }
94
                }
95
            }
96

97
            $data = $newData;
12✔
98
        }
99

UNCOV
100
        if (!$manager->contains($data) || $this->isDeferredExplicit($manager, $data)) {
164✔
UNCOV
101
            $manager->persist($data);
128✔
102
        }
103

UNCOV
104
        $manager->flush();
164✔
UNCOV
105
        $manager->refresh($data);
164✔
106

UNCOV
107
        return $data;
164✔
108
    }
109

110
    /**
111
     * Checks if doctrine does not manage data automatically.
112
     */
113
    private function isDeferredExplicit(DoctrineObjectManager $manager, $data): bool
114
    {
115
        $classMetadata = $manager->getClassMetadata($this->getObjectClass($data));
39✔
116
        if ($classMetadata && method_exists($classMetadata, 'isChangeTrackingDeferredExplicit')) { // @phpstan-ignore-line metadata can be null
39✔
117
            return $classMetadata->isChangeTrackingDeferredExplicit();
39✔
118
        }
119

120
        return false;
×
121
    }
122

123
    /**
124
     * Get reflection properties indexed by property name.
125
     *
126
     * @return array<string, \ReflectionProperty>
127
     */
128
    private function getReflectionProperties(mixed $data): array
129
    {
130
        $ret = [];
12✔
131
        $r = new \ReflectionObject($data);
12✔
132

133
        do {
134
            $props = $r->getProperties(~\ReflectionProperty::IS_STATIC);
12✔
135

136
            foreach ($props as $prop) {
12✔
137
                $ret[$prop->getName()] = $prop;
12✔
138
            }
139
        } while ($r = $r->getParentClass());
12✔
140

141
        return $ret;
12✔
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

© 2025 Coveralls, Inc