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

brick / orm / 23255296146

18 Mar 2026 04:24PM UTC coverage: 47.104%. Remained the same
23255296146

push

github

BenMorel
Avoid \Exception that somehow confuses ECS

1 of 5 new or added lines in 1 file covered. (20.0%)

402 existing lines in 24 files now uncovered.

553 of 1174 relevant lines covered (47.1%)

10.6 hits per line

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

0.0
/src/EntityConfiguration.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Brick\ORM;
6

7
use InvalidArgumentException;
8
use LogicException;
9
use ReflectionClass;
10
use ReflectionException;
11
use ReflectionNamedType;
12

13
use function array_unique;
14
use function array_values;
15
use function count;
16
use function in_array;
17
use function sprintf;
18

19
class EntityConfiguration extends ClassConfiguration
20
{
21
    private ?string $belongsTo = null;
22

23
    private ?string $tableName = null;
24

25
    private bool $isAutoIncrement = false;
26

27
    /**
28
     * The list of identity properties, or null if not set.
29
     *
30
     * @var list<string>|null
31
     */
32
    private ?array $identityProperties = null;
33

34
    /**
35
     * The discriminator column name, or null if not set.
36
     */
37
    private ?string $discriminatorColumn = null;
38

39
    /**
40
     * A map of discriminator values to entity class names, or an empty array if not set.
41
     *
42
     * @var array<int|string, class-string>
43
     */
44
    private array $discriminatorMap = [];
45

46
    /**
47
     * Sets the root entity of the aggregate this entity belongs to.
48
     *
49
     * @param class-string $className
50
     */
51
    public function belongsTo(string $className): EntityConfiguration
52
    {
UNCOV
53
        $this->belongsTo = $className;
×
54

UNCOV
55
        return $this;
×
56
    }
57

58
    public function getBelongsTo(): ?string
59
    {
UNCOV
60
        return $this->belongsTo;
×
61
    }
62

63
    /**
64
     * Sets the table name.
65
     *
66
     * If not set, it will default to the entity short name (i.e. the name without the namespace).
67
     */
68
    public function setTableName(string $tableName): EntityConfiguration
69
    {
UNCOV
70
        $this->tableName = $tableName;
×
71

UNCOV
72
        return $this;
×
73
    }
74

75
    /**
76
     * Returns the table name.
77
     *
78
     * If not set, it will default to the entity short name (i.e. the name without the namespace).
79
     */
80
    public function getTableName(): string
81
    {
UNCOV
82
        if ($this->tableName !== null) {
×
UNCOV
83
            return $this->tableName;
×
84
        }
85

UNCOV
86
        return $this->reflectionClass->getShortName();
×
87
    }
88

89
    /**
90
     * Sets whether the database table uses an auto-increment identity field.
91
     */
92
    public function setAutoIncrement(): EntityConfiguration
93
    {
UNCOV
94
        $this->isAutoIncrement = true;
×
95

UNCOV
96
        return $this;
×
97
    }
98

99
    /**
100
     * Returns whether the database table uses an auto-increment identity field.
101
     *
102
     * @throws LogicException
103
     */
104
    public function isAutoIncrement(): bool
105
    {
106
        if ($this->isAutoIncrement) {
×
107
            $identityProperties = $this->getIdentityProperties();
×
108

UNCOV
109
            if (count($identityProperties) !== 1) {
×
110
                throw new LogicException(sprintf(
×
UNCOV
111
                    'The entity "%s" has multiple identity properties and cannot be mapped to an auto-increment table.',
×
112
                    $this->getClassName(),
×
UNCOV
113
                ));
×
114
            }
115

UNCOV
116
            $reflectionProperty = $this->reflectionClass->getProperty($identityProperties[0]);
×
117

118
            $propertyType = $reflectionProperty->getType();
×
119

120
            if ($propertyType instanceof ReflectionNamedType) {
×
121
                $type = $propertyType->getName();
×
122

123
                if ($type !== 'int' && $type !== 'string') {
×
UNCOV
124
                    throw new LogicException(sprintf(
×
UNCOV
125
                        'The entity "%s" has an auto-increment identity that maps to an unsupported type "%s", ' .
×
126
                        'only int and string are allowed.',
×
127
                        $this->getClassName(),
×
128
                        $type,
×
129
                    ));
×
130
                }
131
            } else {
UNCOV
132
                throw new LogicException(sprintf(
×
UNCOV
133
                    'The entity "%s" has an auto-increment identity that maps to an untyped or union type property, ' .
×
134
                    'only int and string are allowed.',
×
UNCOV
135
                    $this->getClassName(),
×
UNCOV
136
                ));
×
137
            }
138
        }
139

UNCOV
140
        return $this->isAutoIncrement;
×
141
    }
142

143
    /**
144
     * @throws InvalidArgumentException
145
     */
146
    public function setIdentityProperties(string ...$identityProperties): EntityConfiguration
147
    {
148
        if (count($identityProperties) === 0) {
×
UNCOV
149
            throw new InvalidArgumentException('The list of identity properties cannot be empty.');
×
150
        }
151

152
        $identityProperties = array_values($identityProperties);
×
153

UNCOV
154
        $this->checkProperties($identityProperties);
×
155

UNCOV
156
        $this->identityProperties = $identityProperties;
×
157

UNCOV
158
        return $this;
×
159
    }
160

161
    /**
162
     * Returns the list of properties that are part of the entity's identity.
163
     *
164
     * @return list<string>
165
     *
166
     * @throws LogicException
167
     */
168
    public function getIdentityProperties(): array
169
    {
170
        if ($this->identityProperties === null) {
×
171
            throw new LogicException(sprintf('No identity properties have been set for class %s.', $this->getClassName()));
×
172
        }
173

UNCOV
174
        foreach ($this->identityProperties as $identityProperty) {
×
UNCOV
175
            if (! in_array($identityProperty, $this->getPersistentProperties())) {
×
176
                throw new LogicException(sprintf('Identity property $%s in class %s is not persistent.', $identityProperty, $this->getClassName()));
×
177
            }
178
        }
179

UNCOV
180
        return $this->identityProperties;
×
181
    }
182

183
    /**
184
     * Sets the inheritance mapping for this entity.
185
     *
186
     * Every persistable class in the hierarchy must have an entry in the discriminator map. This excludes abstract
187
     * classes, and root classes that are common to several entities (so-called MappedSuperclass in other ORM).
188
     *
189
     * Note: only single table inheritance is supported for now.
190
     *
191
     * @param string                          $discriminatorColumn The discriminator column name.
192
     * @param array<int|string, class-string> $discriminatorMap    A map of discriminator value to concrete entity class name.
193
     *
194
     * @throws InvalidArgumentException If the discriminator map is empty, a class name does not exist, or is not a subclass of the root entity class.
195
     */
196
    public function setInheritanceMapping(string $discriminatorColumn, array $discriminatorMap): EntityConfiguration
197
    {
UNCOV
198
        if (! $discriminatorMap) {
×
UNCOV
199
            throw new InvalidArgumentException('The discriminator map cannot be empty.');
×
200
        }
201

202
        $rootEntityClassName = $this->reflectionClass->getName();
×
203

204
        foreach ($discriminatorMap as $discriminatorValue => $className) {
×
205
            try {
206
                $reflectionClass = new ReflectionClass($className);
×
UNCOV
207
            } catch (ReflectionException $e) {
×
UNCOV
208
                throw new InvalidArgumentException(sprintf('%s does not exist.', $className), 0, $e);
×
209
            }
210

UNCOV
211
            if ($reflectionClass->isAbstract()) {
×
UNCOV
212
                throw new InvalidArgumentException(sprintf('Abstract class %s cannot be part of the discriminator map.', $reflectionClass->getName()));
×
213
            }
214

UNCOV
215
            if ($reflectionClass->getName() !== $rootEntityClassName && ! $reflectionClass->isSubclassOf($rootEntityClassName)) {
×
UNCOV
216
                throw new InvalidArgumentException(sprintf('%s is not a subclass of %s and cannot be part of its discriminator map.', $reflectionClass->getName(), $rootEntityClassName));
×
217
            }
218

219
            // Override to fix potential wrong case
UNCOV
220
            $discriminatorMap[$discriminatorValue] = $reflectionClass->getName();
×
221
        }
222

223
        // Check that values are unique
UNCOV
224
        if (count(array_unique($discriminatorMap)) !== count($discriminatorMap)) {
×
UNCOV
225
            throw new InvalidArgumentException('Duplicate class names in discriminator map.');
×
226
        }
227

UNCOV
228
        $this->discriminatorColumn = $discriminatorColumn;
×
229
        $this->discriminatorMap = $discriminatorMap;
×
230

UNCOV
231
        return $this;
×
232
    }
233

234
    /**
235
     * Returns the discriminator column name, or null if inheritance is not in use.
236
     */
237
    public function getDiscriminatorColumn(): ?string
238
    {
UNCOV
239
        return $this->discriminatorColumn;
×
240
    }
241

242
    /**
243
     * Returns a map of discriminator values to fully-qualified entity class names.
244
     *
245
     * If no inheritance is mapped, an empty array is returned.
246
     *
247
     * @return array<int|string, class-string>
248
     */
249
    public function getDiscriminatorMap(): array
250
    {
UNCOV
251
        return $this->discriminatorMap;
×
252
    }
253

254
    /**
255
     * Returns the list of classes part of the hierarchy, starting with the root class (this entity).
256
     *
257
     * If this entity is part of an inheritance hierarchy, the result includes all the classes in the discriminator map,
258
     * plus any abstract class present between the root class and these classes.
259
     *
260
     * If this entity is not part of an inheritance hierarchy, an array with a single ReflectionClass instance, for this
261
     * entity, is returned.
262
     *
263
     * @return class-string[] The list of all class names in the hierarchy.
264
     */
265
    public function getClassHierarchy(): array
266
    {
267
        $classes = [
×
268
            $this->getClassName(), // root entity
×
269
        ];
×
270

271
        foreach ($this->discriminatorMap as $className) {
×
272
            $reflectionClass = new ReflectionClass($className);
×
273

274
            while ($reflectionClass->getName() !== $this->getClassName()) {
×
275
                $classes[] = $reflectionClass->getName();
×
276
                $reflectionClass = $reflectionClass->getParentClass();
×
277
            }
278
        }
279

280
        return array_values(array_unique($classes));
×
281
    }
282

283
    /**
284
     * @param list<string> $properties The list of property names to check.
285
     *
286
     * @throws InvalidArgumentException If a property does not exist.
287
     */
288
    private function checkProperties(array $properties): void
289
    {
UNCOV
290
        foreach ($properties as $property) {
×
291
            try {
292
                $reflectionProperty = $this->reflectionClass->getProperty($property);
×
UNCOV
293
            } catch (ReflectionException $e) {
×
294
                throw new InvalidArgumentException(sprintf('Class %s has no property named $%s.', $this->getClassName(), $property), 0, $e);
×
295
            }
296

UNCOV
297
            if ($reflectionProperty->isStatic()) {
×
UNCOV
298
                throw new InvalidArgumentException(sprintf('%s::$%s is static; static properties cannot be persisted.', $this->getClassName(), $property));
×
299
            }
300

UNCOV
301
            if ($reflectionProperty->isPrivate()) {
×
UNCOV
302
                throw new InvalidArgumentException(sprintf('%s::$%s is private; private properties are not supported.', $this->getClassName(), $property));
×
303
            }
304
        }
305
    }
306
}
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