Skip to content

Commit 28ca1e3

Browse files
committed
wip
1 parent 11c8075 commit 28ca1e3

File tree

12 files changed

+187
-94
lines changed

12 files changed

+187
-94
lines changed

src/Factory.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ protected function normalizeParameter(string $field, mixed $value): mixed
235235
);
236236
}
237237

238-
return \is_object($value) ? $this->normalizeObject($value) : $value;
238+
return \is_object($value) ? $this->normalizeObject($field, $value) : $value;
239239
}
240240

241241
/**
@@ -253,7 +253,7 @@ protected function normalizeCollection(string $field, FactoryCollection $collect
253253
/**
254254
* @internal
255255
*/
256-
protected function normalizeObject(object $object): object
256+
protected function normalizeObject(string $field, object $object): object
257257
{
258258
return $object;
259259
}

src/ORM/OrmV2PersistenceStrategy.php

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
use Doctrine\ORM\Mapping\ClassMetadataInfo;
1717
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
1818
use Doctrine\Persistence\Mapping\MappingException;
19-
use Zenstruck\Foundry\Persistence\InverseRelationshipMetadata;
19+
use Zenstruck\Foundry\Persistence\Relationship\OneToManyRelationship;
20+
use Zenstruck\Foundry\Persistence\Relationship\OneToOneRelationship;
21+
use Zenstruck\Foundry\Persistence\Relationship\RelationshipMetadata;
2022

2123
/**
2224
* @internal
@@ -25,49 +27,39 @@
2527
*/
2628
final class OrmV2PersistenceStrategy extends AbstractORMPersistenceStrategy
2729
{
28-
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?InverseRelationshipMetadata
30+
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?RelationshipMetadata
2931
{
30-
$metadata = $this->classMetadata($child);
32+
$associationMapping = $this->getAssociationMapping($parent, $child, $field);
3133

32-
$inversedAssociation = $this->getAssociationMapping($parent, $child, $field);
33-
34-
if (null === $inversedAssociation || !$metadata instanceof ClassMetadataInfo) {
34+
if (null === $associationMapping) {
3535
return null;
3636
}
3737

3838
if (!\is_a(
3939
$child,
40-
$inversedAssociation['targetEntity'],
40+
$associationMapping['targetEntity'],
4141
allow_string: true
4242
)) { // is_a() handles inheritance as well
4343
throw new \LogicException("Cannot find correct association named \"{$field}\" between classes [parent: \"{$parent}\", child: \"{$child}\"]");
4444
}
4545

46-
// exclude "owning" side of the association (owning OneToOne or ManyToOne)
47-
if (!\in_array(
48-
$inversedAssociation['type'],
49-
[ClassMetadataInfo::ONE_TO_MANY, ClassMetadataInfo::ONE_TO_ONE],
50-
true
51-
)
52-
|| !isset($inversedAssociation['mappedBy'])
53-
) {
54-
return null;
55-
}
46+
$inverseField = $associationMapping['isOwningSide'] ? $associationMapping['inversedBy'] ?? null : $associationMapping['mappedBy'] ?? null;
5647

57-
$association = $metadata->getAssociationMapping($inversedAssociation['mappedBy']);
58-
59-
// only keep *ToOne associations
60-
if (!$metadata->isSingleValuedAssociation($association['fieldName'])) {
48+
if (null === $inverseField) {
6149
return null;
6250
}
6351

64-
$inversedAssociationMetadata = $this->classMetadata($inversedAssociation['sourceEntity']);
65-
66-
return new InverseRelationshipMetadata(
67-
inverseField: $association['fieldName'],
68-
isCollection: $inversedAssociationMetadata->isCollectionValuedAssociation($inversedAssociation['fieldName']),
69-
collectionIndexedBy: $inversedAssociation['indexBy'] ?? null
70-
);
52+
return match (true) {
53+
ClassMetadataInfo::ONE_TO_MANY === $associationMapping['type'] => new OneToManyRelationship(
54+
inverseField: $inverseField,
55+
collectionIndexedBy: $associationMapping['indexBy'] ?? null
56+
),
57+
ClassMetadataInfo::ONE_TO_ONE === $associationMapping['type'] => new OneToOneRelationship(
58+
inverseField: $inverseField,
59+
isOwning: $associationMapping['isOwningSide'] ?? false
60+
),
61+
default => null,
62+
};
7163
}
7264

7365
/**

src/ORM/OrmV3PersistenceStrategy.php

Lines changed: 22 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,50 +14,49 @@
1414
namespace Zenstruck\Foundry\ORM;
1515

1616
use Doctrine\ORM\Mapping\AssociationMapping;
17-
use Doctrine\ORM\Mapping\ClassMetadata;
18-
use Doctrine\ORM\Mapping\InverseSideMapping;
1917
use Doctrine\ORM\Mapping\MappingException as ORMMappingException;
20-
use Doctrine\ORM\Mapping\ToManyAssociationMapping;
18+
use Doctrine\ORM\Mapping\OneToManyAssociationMapping;
19+
use Doctrine\ORM\Mapping\OneToOneAssociationMapping;
2120
use Doctrine\Persistence\Mapping\MappingException;
22-
use Zenstruck\Foundry\Persistence\InverseRelationshipMetadata;
21+
use Zenstruck\Foundry\Persistence\Relationship\OneToManyRelationship;
22+
use Zenstruck\Foundry\Persistence\Relationship\OneToOneRelationship;
23+
use Zenstruck\Foundry\Persistence\Relationship\RelationshipMetadata;
2324

2425
final class OrmV3PersistenceStrategy extends AbstractORMPersistenceStrategy
2526
{
26-
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?InverseRelationshipMetadata
27+
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?RelationshipMetadata
2728
{
28-
$metadata = $this->classMetadata($child);
29+
$associationMapping = $this->getAssociationMapping($parent, $child, $field);
2930

30-
$inversedAssociation = $this->getAssociationMapping($parent, $child, $field);
31-
32-
if (null === $inversedAssociation || !$metadata instanceof ClassMetadata) {
31+
if (null === $associationMapping) {
3332
return null;
3433
}
3534

3635
if (!\is_a(
3736
$child,
38-
$inversedAssociation->targetEntity,
37+
$associationMapping->targetEntity,
3938
allow_string: true
4039
)) { // is_a() handles inheritance as well
4140
throw new \LogicException("Cannot find correct association named \"{$field}\" between classes [parent: \"{$parent}\", child: \"{$child}\"]");
4241
}
4342

44-
// exclude "owning" side of the association (owning OneToOne or ManyToOne)
45-
if (!$inversedAssociation instanceof InverseSideMapping) {
46-
return null;
47-
}
48-
49-
$association = $metadata->getAssociationMapping($inversedAssociation->mappedBy);
43+
$inverseField = $associationMapping->isOwningSide() ? $associationMapping->inversedBy : $associationMapping->mappedBy;
5044

51-
// only keep *ToOne associations
52-
if (!$metadata->isSingleValuedAssociation($association->fieldName)) {
45+
if (null === $inverseField) {
5346
return null;
5447
}
5548

56-
return new InverseRelationshipMetadata(
57-
inverseField: $association->fieldName,
58-
isCollection: $inversedAssociation instanceof ToManyAssociationMapping,
59-
collectionIndexedBy: $inversedAssociation->isIndexed() ? $inversedAssociation->indexBy() : null
60-
);
49+
return match (true) {
50+
$associationMapping instanceof OneToManyAssociationMapping => new OneToManyRelationship(
51+
inverseField: $inverseField,
52+
collectionIndexedBy: $associationMapping->isIndexed() ? $associationMapping->indexBy() : null
53+
),
54+
$associationMapping instanceof OneToOneAssociationMapping => new OneToOneRelationship(
55+
inverseField: $inverseField,
56+
isOwning: $associationMapping->isOwningSide()
57+
),
58+
default => null,
59+
};
6160
}
6261

6362
/**

src/Persistence/PersistenceManager.php

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
use Zenstruck\Foundry\ORM\AbstractORMPersistenceStrategy;
2020
use Zenstruck\Foundry\Persistence\Exception\NoPersistenceStrategy;
2121
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
22+
use Zenstruck\Foundry\Persistence\Relationship\RelationshipMetadata;
2223
use Zenstruck\Foundry\Persistence\ResetDatabase\ResetDatabaseManager;
2324

2425
/**
@@ -88,7 +89,7 @@ public function save(object $object): object
8889
* This prevents such code to persist the whole batch of objects in the normalization phase:
8990
* ```php
9091
* SomeFactory::createOne(['item' => lazy(fn() => OtherFactory::createOne())]);
91-
* ```
92+
* ```.
9293
*/
9394
public function startTransaction(): void
9495
{
@@ -138,11 +139,11 @@ public function scheduleForInsert(object $object, array $afterPersistCallbacks =
138139
$object = unproxy($object);
139140
}
140141

141-
// if (0 === \count($this->objectsToPersist)) {
142-
// throw new \LogicException('No transaction started yet.');
143-
// }
142+
// if (0 === \count($this->objectsToPersist)) {
143+
// throw new \LogicException('No transaction started yet.');
144+
// }
144145

145-
// $transactionCount = \count($this->objectsToPersist) - 1;
146+
// $transactionCount = \count($this->objectsToPersist) - 1;
146147
$this->objectsToPersist[] = $object;
147148

148149
$this->afterPersistCallbacks = [
@@ -309,7 +310,7 @@ public function repositoryFor(string $class): ObjectRepository
309310
* @param class-string $parent
310311
* @param class-string $child
311312
*/
312-
public function inverseRelationshipMetadata(string $parent, string $child, string $field): ?InverseRelationshipMetadata
313+
public function inverseRelationshipMetadata(string $parent, string $child, string $field): ?RelationshipMetadata
313314
{
314315
$parent = unproxy($parent);
315316
$child = unproxy($child);

src/Persistence/PersistenceStrategy.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Doctrine\Persistence\Mapping\ClassMetadata;
1616
use Doctrine\Persistence\Mapping\MappingException;
1717
use Doctrine\Persistence\ObjectManager;
18+
use Zenstruck\Foundry\Persistence\Relationship\RelationshipMetadata;
1819

1920
/**
2021
* @author Kevin Bond <[email protected]>
@@ -63,7 +64,7 @@ public function objectManagers(): array
6364
* @param class-string $parent
6465
* @param class-string $child
6566
*/
66-
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?InverseRelationshipMetadata
67+
public function inversedRelationshipMetadata(string $parent, string $child, string $field): ?RelationshipMetadata
6768
{
6869
return null;
6970
}

src/Persistence/PersistentObjectFactory.php

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
use Zenstruck\Foundry\ObjectFactory;
2323
use Zenstruck\Foundry\Persistence\Exception\NotEnoughObjects;
2424
use Zenstruck\Foundry\Persistence\Exception\RefreshObjectFailed;
25+
use Zenstruck\Foundry\Persistence\Relationship\OneToManyRelationship;
26+
use Zenstruck\Foundry\Persistence\Relationship\OneToOneRelationship;
2527

2628
use function Zenstruck\Foundry\get;
2729
use function Zenstruck\Foundry\set;
@@ -299,11 +301,11 @@ protected function normalizeParameter(string $field, mixed $value): mixed
299301
if ($value instanceof self) {
300302
$pm = Configuration::instance()->persistence();
301303

302-
$inversedRelationshipMetadata = $pm->inverseRelationshipMetadata(static::class(), $value::class(), $field);
304+
$relationshipMetadata = $pm->inverseRelationshipMetadata(static::class(), $value::class(), $field);
303305

304306
// handle inversed OneToOne
305-
if ($inversedRelationshipMetadata && !$inversedRelationshipMetadata->isCollection) {
306-
$inverseField = $inversedRelationshipMetadata->inverseField;
307+
if ($relationshipMetadata instanceof OneToOneRelationship && !$relationshipMetadata->isOwning) {
308+
$inverseField = $relationshipMetadata->inverseField();
307309

308310
// we need to handle the circular dependency involved by inversed one-to-one relationship:
309311
// a placeholder object is used, which will be replaced by the real object, after its instantiation
@@ -340,9 +342,9 @@ protected function normalizeCollection(string $field, FactoryCollection $collect
340342

341343
$inverseRelationshipMetadata = $pm->inverseRelationshipMetadata(static::class(), $collection->factory::class(), $field);
342344

343-
if ($inverseRelationshipMetadata && $inverseRelationshipMetadata->isCollection) {
345+
if ($inverseRelationshipMetadata instanceof OneToManyRelationship) {
344346
$this->tempAfterInstantiate[] = function(object $object) use ($collection, $inverseRelationshipMetadata, $field) {
345-
$inverseField = $inverseRelationshipMetadata->inverseField;
347+
$inverseField = $inverseRelationshipMetadata->inverseField();
346348

347349
$inverseObjects = $collection->withPersistMode(
348350
$this->isPersisting() ? PersistMode::NO_PERSIST_BUT_SCHEDULE_FOR_INSERT : PersistMode::WITHOUT_PERSISTING
@@ -374,24 +376,39 @@ protected function normalizeCollection(string $field, FactoryCollection $collect
374376
*
375377
* @internal
376378
*/
377-
protected function normalizeObject(object $object): object
379+
protected function normalizeObject(string $field, object $object): object
378380
{
379381
$configuration = Configuration::instance();
380382

381-
if (
382-
!$this->isPersisting()
383-
|| !$configuration->isPersistenceAvailable()
384-
) {
383+
$object = unproxy($object, withAutoRefresh: false);
384+
385+
if (!$configuration->isPersistenceAvailable()) {
385386
return $object;
386387
}
387388

388-
$object = unproxy($object, withAutoRefresh: false);
389-
390389
$persistenceManager = $configuration->persistence();
390+
391391
if (!$persistenceManager->hasPersistenceFor($object)) {
392392
return $object;
393393
}
394394

395+
$inverseRelationship = $persistenceManager->inverseRelationshipMetadata(static::class(), $object::class, $field);
396+
397+
if ($inverseRelationship instanceof OneToOneRelationship) {
398+
$this->tempAfterInstantiate[] = static function(object $newObject) use ($object, $inverseRelationship) {
399+
try {
400+
set($object, $inverseRelationship->inverseField(), $newObject);
401+
} catch (\Throwable) {
402+
}
403+
};
404+
}
405+
406+
if (
407+
!$this->isPersisting()
408+
) {
409+
return $object;
410+
}
411+
395412
if (!$persistenceManager->isPersisted($object)) {
396413
$persistenceManager->scheduleForInsert($object);
397414

@@ -439,12 +456,15 @@ static function(object $object, array $parameters, PersistentObjectFactory $fact
439456
Configuration::instance()->persistence()->scheduleForInsert($object, $afterPersistCallbacks);
440457
}
441458
)
442-
// ->afterPersist(
443-
// static function(object $object): void {
444-
// Configuration::instance()->persistence()->refresh($object);
445-
// }
446-
// )
447-
;
459+
->afterPersist(
460+
static function(object $object): void {
461+
try {
462+
Configuration::instance()->persistence()->refresh($object);
463+
} catch (RefreshObjectFailed) {
464+
}
465+
}
466+
)
467+
;
448468
}
449469

450470
private function throwIfCannotCreateObject(): void
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?php
22

3+
declare(strict_types=1);
4+
35
/*
46
* This file is part of the zenstruck/foundry package.
57
*
@@ -9,19 +11,18 @@
911
* file that was distributed with this source code.
1012
*/
1113

12-
namespace Zenstruck\Foundry\Persistence;
14+
namespace Zenstruck\Foundry\Persistence\Relationship;
1315

14-
/**
15-
* @author Kevin Bond <[email protected]>
16-
*
17-
* @internal
18-
*/
19-
final class InverseRelationshipMetadata
16+
final class OneToManyRelationship implements RelationshipMetadata
2017
{
2118
public function __construct(
22-
public readonly string $inverseField,
23-
public readonly bool $isCollection,
19+
private readonly string $inverseField,
2420
public readonly ?string $collectionIndexedBy,
2521
) {
2622
}
23+
24+
public function inverseField(): string
25+
{
26+
return $this->inverseField;
27+
}
2728
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the zenstruck/foundry package.
7+
*
8+
* (c) Kevin Bond <[email protected]>
9+
*
10+
* For the full copyright and license information, please view the LICENSE
11+
* file that was distributed with this source code.
12+
*/
13+
14+
namespace Zenstruck\Foundry\Persistence\Relationship;
15+
16+
final class OneToOneRelationship implements RelationshipMetadata
17+
{
18+
public function __construct(
19+
private readonly string $inverseField,
20+
public readonly bool $isOwning,
21+
) {
22+
}
23+
24+
public function inverseField(): string
25+
{
26+
return $this->inverseField;
27+
}
28+
}

0 commit comments

Comments
 (0)