Skip to content

Commit 44eaeb0

Browse files
authored
Merge pull request #509: change behavior of EntityFactory
2 parents a366d01 + cc5fc55 commit 44eaeb0

File tree

18 files changed

+1992
-1046
lines changed

18 files changed

+1992
-1046
lines changed

.github/workflows/main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ jobs:
2424
- "8.3"
2525
- "8.4"
2626
steps:
27+
- name: Install ODBC driver.
28+
run: |
29+
sudo curl https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list
30+
sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18
2731
- name: Checkout
2832
uses: actions/checkout@v2
2933
- name: Setup DB services

composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@
5151
"mockery/mockery": "^1.1",
5252
"phpunit/phpunit": "^9.5",
5353
"ramsey/uuid": "^4.0",
54-
"spiral/tokenizer": "^2.8 || ^3.0",
5554
"spiral/code-style": "~2.2.0",
55+
"spiral/tokenizer": "^2.8 || ^3.0",
5656
"symfony/var-dumper": "^5.2 || ^6.0 || ^7.0",
57-
"vimeo/psalm": "5.21"
57+
"vimeo/psalm": "5.21 || ^6.8"
5858
},
5959
"autoload": {
6060
"psr-4": {

psalm-baseline.xml

Lines changed: 1848 additions & 965 deletions
Large diffs are not rendered by default.

psalm.xml

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,14 @@
1313
</ignoreFiles>
1414
</projectFiles>
1515
<issueHandlers>
16+
<MissingClassConstType errorLevel="suppress" />
17+
<UnusedClass errorLevel="suppress" />
1618
<UndefinedAttributeClass>
1719
<errorLevel type="suppress">
1820
<referencedClass name="JetBrains\PhpStorm\ExpectedValues" />
1921
<referencedClass name="JetBrains\PhpStorm\Deprecated" />
2022
<referencedClass name="JetBrains\PhpStorm\Pure" />
2123
</errorLevel>
2224
</UndefinedAttributeClass>
23-
<UndefinedClass>
24-
<errorLevel type="suppress">
25-
<referencedClass name="BackedEnum" />
26-
</errorLevel>
27-
</UndefinedClass>
2825
</issueHandlers>
2926
</psalm>

src/ORM.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616
use Cycle\ORM\Service\Implementation\MapperProvider;
1717
use Cycle\ORM\Service\Implementation\RelationProvider;
1818
use Cycle\ORM\Service\Implementation\RepositoryProvider;
19+
use Cycle\ORM\Service\Implementation\RoleResolver;
1920
use Cycle\ORM\Service\Implementation\SourceProvider;
2021
use Cycle\ORM\Service\Implementation\TypecastProvider;
2122
use Cycle\ORM\Service\IndexProviderInterface;
2223
use Cycle\ORM\Service\MapperProviderInterface;
2324
use Cycle\ORM\Service\RelationProviderInterface;
2425
use Cycle\ORM\Service\RepositoryProviderInterface;
26+
use Cycle\ORM\Service\RoleResolverInterface;
2527
use Cycle\ORM\Service\SourceProviderInterface;
2628
use Cycle\ORM\Service\TypecastProviderInterface;
2729
use Cycle\ORM\Transaction\CommandGenerator;
@@ -38,11 +40,12 @@ final class ORM implements ORMInterface
3840
private RelationProvider $relationProvider;
3941
private SourceProvider $sourceProvider;
4042
private TypecastProvider $typecastProvider;
41-
private EntityFactory $entityFactory;
43+
private EntityFactoryInterface $entityFactory;
4244
private IndexProvider $indexProvider;
4345
private MapperProvider $mapperProvider;
4446
private RepositoryProvider $repositoryProvider;
4547
private EntityProvider $entityProvider;
48+
private RoleResolverInterface $roleResolver;
4649

4750
public function __construct(
4851
private FactoryInterface $factory,
@@ -57,7 +60,7 @@ public function __construct(
5760

5861
public function resolveRole(string|object $entity): string
5962
{
60-
return $this->entityFactory->resolveRole($entity);
63+
return $this->roleResolver->resolveRole($entity);
6164
}
6265

6366
public function get(string $role, array $scope, bool $load = true): ?object
@@ -242,13 +245,15 @@ private function resetRegistry(): void
242245
$this->factory,
243246
);
244247
$this->entityProvider = new EntityProvider($this->heap, $this->repositoryProvider);
248+
$this->roleResolver = new RoleResolver($this->schema, $this->heap);
245249

246250
$this->entityFactory = new EntityFactory(
247251
$this->heap,
248252
$this->schema,
249253
$this->mapperProvider,
250254
$this->relationProvider,
251255
$this->indexProvider,
256+
$this->roleResolver,
252257
);
253258
}
254259
}

src/ORMInterface.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use Cycle\ORM\Service\MapperProviderInterface;
1313
use Cycle\ORM\Service\RelationProviderInterface;
1414
use Cycle\ORM\Service\RepositoryProviderInterface;
15+
use Cycle\ORM\Service\RoleResolverInterface;
1516
use Cycle\ORM\Service\SourceProviderInterface;
1617
use Cycle\ORM\Transaction\CommandGeneratorInterface;
1718

@@ -25,13 +26,9 @@ interface ORMInterface extends
2526
MapperProviderInterface,
2627
RepositoryProviderInterface,
2728
RelationProviderInterface,
29+
RoleResolverInterface,
2830
IndexProviderInterface
2931
{
30-
/**
31-
* Automatically resolve role based on object name or instance.
32-
*/
33-
public function resolveRole(string|object $entity): string;
34-
3532
/**
3633
* Create new entity based on given role and input data. Method will attempt to re-use
3734
* already loaded entity.

src/Service/EntityFactoryInterface.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,22 @@
44

55
namespace Cycle\ORM\Service;
66

7+
use Cycle\ORM\Exception\MapperException;
78
use Cycle\ORM\Heap\Node;
89

910
interface EntityFactoryInterface
1011
{
1112
/**
1213
* Create new entity based on given role and input data.
1314
*
14-
* @param string $role Entity role.
15-
* @param array<string, mixed> $data Entity data.
15+
* @template T
16+
*
17+
* @param non-empty-string|class-string<T> $role Entity role.
18+
* @param array<non-empty-string, mixed> $data Entity data.
1619
* @param bool $typecast Indicates that data is raw, and typecasting should be applied.
20+
*
21+
* @return T
22+
* @throws MapperException
1723
*/
1824
public function make(string $role, array $data = [], int $status = Node::NEW, bool $typecast = false): object;
1925
}

src/Service/Implementation/EntityFactory.php

Lines changed: 20 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

55
namespace Cycle\ORM\Service\Implementation;
66

7-
use Cycle\ORM\EntityProxyInterface;
8-
use Cycle\ORM\Exception\ORMException;
97
use Cycle\ORM\Heap\HeapInterface;
108
use Cycle\ORM\Heap\Node;
9+
use Cycle\ORM\Reference\ReferenceInterface;
1110
use Cycle\ORM\Service\EntityFactoryInterface;
1211
use Cycle\ORM\Service\IndexProviderInterface;
1312
use Cycle\ORM\Service\MapperProviderInterface;
1413
use Cycle\ORM\Service\RelationProviderInterface;
1514
use Cycle\ORM\SchemaInterface;
1615
use Cycle\ORM\Select\LoaderInterface;
16+
use Cycle\ORM\Service\RoleResolverInterface;
1717

1818
/**
1919
* @internal
@@ -26,6 +26,7 @@ public function __construct(
2626
private MapperProviderInterface $mapperProvider,
2727
private RelationProviderInterface $relationProvider,
2828
private IndexProviderInterface $indexProvider,
29+
private RoleResolverInterface $roleResolver,
2930
) {}
3031

3132
public function make(
@@ -37,7 +38,7 @@ public function make(
3738
$role = $data[LoaderInterface::ROLE_KEY] ?? $role;
3839
unset($data[LoaderInterface::ROLE_KEY]);
3940
// Resolved role
40-
$rRole = $this->resolveRole($role);
41+
$rRole = $this->roleResolver->resolveRole($role);
4142
$relMap = $this->relationProvider->getRelationMap($rRole);
4243
$mapper = $this->mapperProvider->getMapper($rRole);
4344

@@ -63,10 +64,25 @@ public function make(
6364
$e = $this->heap->find($rRole, $ids);
6465

6566
if ($e !== null) {
67+
// Get not resolved relations (references)
68+
$refs = \array_filter(
69+
$mapper->fetchRelations($e),
70+
fn($v) => $v instanceof ReferenceInterface,
71+
);
72+
73+
if ($refs === []) {
74+
return $e;
75+
}
76+
6677
$node = $this->heap->get($e);
6778
\assert($node !== null);
6879

69-
return $mapper->hydrate($e, $relMap->init($this, $node, $castedData));
80+
// Replace references with actual relation data
81+
return $mapper->hydrate($e, $relMap->init(
82+
$this,
83+
$node,
84+
\array_intersect_key($castedData, $refs),
85+
));
7086
}
7187
}
7288
}
@@ -79,31 +95,4 @@ public function make(
7995

8096
return $mapper->hydrate($e, $relMap->init($this, $node, $castedData));
8197
}
82-
83-
public function resolveRole(object|string $entity): string
84-
{
85-
if (\is_object($entity)) {
86-
$node = $this->heap->get($entity);
87-
if ($node !== null) {
88-
return $node->getRole();
89-
}
90-
91-
$class = $entity::class;
92-
if (!$this->schema->defines($class)) {
93-
$parentClass = \get_parent_class($entity);
94-
95-
if ($parentClass === false
96-
|| !$entity instanceof EntityProxyInterface
97-
|| !$this->schema->defines($parentClass)
98-
) {
99-
throw new ORMException("Unable to resolve role of `$class`.");
100-
}
101-
$class = $parentClass;
102-
}
103-
104-
$entity = $class;
105-
}
106-
107-
return $this->schema->resolveAlias($entity) ?? throw new ORMException("Unable to resolve role `$entity`.");
108-
}
10998
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\ORM\Service\Implementation;
6+
7+
use Cycle\ORM\EntityProxyInterface;
8+
use Cycle\ORM\Exception\ORMException;
9+
use Cycle\ORM\Heap\HeapInterface;
10+
use Cycle\ORM\SchemaInterface;
11+
use Cycle\ORM\Service\RoleResolverInterface;
12+
13+
/**
14+
* @internal
15+
*/
16+
final class RoleResolver implements RoleResolverInterface
17+
{
18+
private SchemaInterface $schema;
19+
private HeapInterface $heap;
20+
21+
public function __construct(SchemaInterface $schema, HeapInterface $heap)
22+
{
23+
$this->schema = $schema;
24+
$this->heap = $heap;
25+
}
26+
27+
public function resolveRole(object|string $entity): string
28+
{
29+
if (\is_object($entity)) {
30+
$node = $this->heap->get($entity);
31+
if ($node !== null) {
32+
return $node->getRole();
33+
}
34+
35+
/** @var class-string $class */
36+
$class = $entity::class;
37+
if (!$this->schema->defines($class)) {
38+
$parentClass = \get_parent_class($entity);
39+
40+
if ($parentClass === false
41+
|| !$entity instanceof EntityProxyInterface
42+
|| !$this->schema->defines($parentClass)
43+
) {
44+
throw new ORMException("Unable to resolve role of `$class`.");
45+
}
46+
$class = $parentClass;
47+
}
48+
49+
$entity = $class;
50+
}
51+
52+
return $this->schema->resolveAlias($entity) ?? throw new ORMException("Unable to resolve role `$entity`.");
53+
}
54+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\ORM\Service;
6+
7+
interface RoleResolverInterface
8+
{
9+
/**
10+
* Automatically resolve role based on object name or instance.
11+
*
12+
* @return non-empty-string
13+
*/
14+
public function resolveRole(string|object $entity): string;
15+
}

0 commit comments

Comments
 (0)