Skip to content

Commit e04328c

Browse files
authored
Add RefersToMorphed relation attribute (#123)
1 parent 4c15bdd commit e04328c

12 files changed

Lines changed: 300 additions & 23 deletions

File tree

composer.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@
1818
"email": "wolfy-j@spiralscout.com"
1919
},
2020
{
21-
"name": "Aleksei Gagarin (roxblnfk)",
22-
"email": "alexey.gagarin@spiralscout.com"
21+
"name": "Aleksei Gagarin",
22+
"homepage": "https://github.com/roxblnfk"
2323
},
2424
{
25-
"name": "Pavel Butchnev (butschster)",
26-
"email": "pavel.buchnev@spiralscout.com"
25+
"name": "Pavel Butchnev",
26+
"homepage": "https://github.com/butschster"
2727
},
2828
{
2929
"name": "Maksim Smakouz (msmakouz)",
@@ -38,20 +38,20 @@
3838
],
3939
"require": {
4040
"php": ">=8.1",
41-
"cycle/database": "^2.16",
42-
"cycle/orm": "^2.15",
43-
"cycle/schema-builder": "^2.11.1",
44-
"spiral/attributes": "^2.8|^3.0",
45-
"spiral/tokenizer": "^2.8|^3.0",
46-
"doctrine/inflector": "^2.0"
41+
"cycle/database": "^2.20",
42+
"cycle/orm": "^2.18",
43+
"cycle/schema-builder": "^2.12",
44+
"spiral/attributes": "^2.8 || ^3.1",
45+
"spiral/tokenizer": "^2.8 || ^3.17",
46+
"doctrine/inflector": "^2.1"
4747
},
4848
"require-dev": {
4949
"buggregator/trap": "^1.15",
50-
"doctrine/annotations": "^1.14.3 || ^2.0.1",
51-
"phpunit/phpunit": "^10.1",
52-
"spiral/code-style": "^2.2",
50+
"doctrine/annotations": "^1.14 || ^2.0",
51+
"phpunit/phpunit": "^10.5",
52+
"spiral/code-style": "^2.3",
5353
"spiral/dumper": "^3.3",
54-
"vimeo/psalm": "^5.26 || ^6.0"
54+
"vimeo/psalm": "^5.26 || ^6.16"
5555
},
5656
"autoload": {
5757
"psr-4": {

psalm-baseline.xml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@
112112
<code><![CDATA[TYPE]]></code>
113113
</InvalidClassConstantType>
114114
</file>
115+
<file src="src/Annotation/Relation/Morphed/RefersToMorphed.php">
116+
<DeprecatedClass>
117+
<code><![CDATA[NamedArgumentConstructor]]></code>
118+
</DeprecatedClass>
119+
<InvalidClassConstantType>
120+
<code><![CDATA[TYPE]]></code>
121+
</InvalidClassConstantType>
122+
</file>
115123
<file src="src/Annotation/Relation/RefersTo.php">
116124
<DeprecatedClass>
117125
<code><![CDATA[NamedArgumentConstructor]]></code>

resources/relations.meta-storm.xml

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,43 @@
138138

139139
<!-- Belongs To Morphed -->
140140
<!-- target: -->
141-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="1">
141+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="0">
142142
<collection name="cycle/orm:entity-class" argument="0" />
143143
<collection name="cycle/orm:entity-role" argument="0" />
144144
</classConstructor>
145145
<!-- innerKey: -->
146-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="1" targetInArray="value">
146+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="3" targetInArray="value">
147147
<properties xpath="$containingClass">
148148
<filters>
149149
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
150150
</filters>
151151
</properties>
152152
</classConstructor>
153153
<!-- outerKey: -->
154-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="2" targetInArray="value">
154+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\BelongsToMorphed" argument="4" targetInArray="value">
155+
<properties xpath="$argument[0]">
156+
<filters>
157+
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
158+
</filters>
159+
</properties>
160+
</classConstructor>
161+
162+
<!-- Refers To Morphed -->
163+
<!-- target: -->
164+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\RefersToMorphed" argument="0">
165+
<collection name="cycle/orm:entity-class" argument="0" />
166+
<collection name="cycle/orm:entity-role" argument="0" />
167+
</classConstructor>
168+
<!-- innerKey: -->
169+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\RefersToMorphed" argument="3" targetInArray="value">
170+
<properties xpath="$containingClass">
171+
<filters>
172+
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
173+
</filters>
174+
</properties>
175+
</classConstructor>
176+
<!-- outerKey: -->
177+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\RefersToMorphed" argument="4" targetInArray="value">
155178
<properties xpath="$argument[0]">
156179
<filters>
157180
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
@@ -161,20 +184,20 @@
161184

162185
<!-- Morphed Has Many -->
163186
<!-- target: -->
164-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="1">
187+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="0">
165188
<collection name="cycle/orm:entity-class" argument="0" />
166189
<collection name="cycle/orm:entity-role" argument="0" />
167190
</classConstructor>
168191
<!-- innerKey: -->
169-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="1" targetInArray="value">
192+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="3" targetInArray="value">
170193
<properties xpath="$containingClass">
171194
<filters>
172195
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
173196
</filters>
174197
</properties>
175198
</classConstructor>
176199
<!-- outerKey: -->
177-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="2" targetInArray="value">
200+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasMany" argument="4" targetInArray="value">
178201
<properties xpath="$argument[0]">
179202
<filters>
180203
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
@@ -184,20 +207,20 @@
184207

185208
<!-- Morphed Has One -->
186209
<!-- target: -->
187-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="1">
210+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="0">
188211
<collection name="cycle/orm:entity-class" argument="0" />
189212
<collection name="cycle/orm:entity-role" argument="0" />
190213
</classConstructor>
191214
<!-- innerKey: -->
192-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="1" targetInArray="value">
215+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="3" targetInArray="value">
193216
<properties xpath="$containingClass">
194217
<filters>
195218
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
196219
</filters>
197220
</properties>
198221
</classConstructor>
199222
<!-- outerKey: -->
200-
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="2" targetInArray="value">
223+
<classConstructor class="\Cycle\Annotated\Annotation\Relation\Morphed\MorphedHasOne" argument="4" targetInArray="value">
201224
<properties xpath="$argument[0]">
202225
<filters>
203226
<hasAttribute class="\Cycle\Annotated\Annotation\Column"/>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Annotation\Relation\Morphed;
6+
7+
use Cycle\Annotated\Annotation\Relation\Inverse;
8+
use Cycle\Annotated\Annotation\Relation\Relation;
9+
use Cycle\Annotated\Annotation\Relation\Traits\InverseTrait;
10+
use Doctrine\Common\Annotations\Annotation\Target;
11+
use JetBrains\PhpStorm\ExpectedValues;
12+
use Spiral\Attributes\NamedArgumentConstructor;
13+
14+
/**
15+
* Morphed variation of the refers-to relation. Like {@see BelongsToMorphed} it stores the outer key
16+
* and the target role on the owner, but resolves the outer key in a deferred way, so it can be used
17+
* for self-linked and cyclic morphed references.
18+
*
19+
* @Annotation
20+
* @NamedArgumentConstructor
21+
* @Target("PROPERTY")
22+
*/
23+
#[\Attribute(\Attribute::TARGET_PROPERTY), NamedArgumentConstructor]
24+
class RefersToMorphed extends Relation
25+
{
26+
use InverseTrait;
27+
28+
protected const TYPE = 'refersToMorphed';
29+
30+
/**
31+
* @param non-empty-string $target
32+
* @param bool $cascade Automatically save related data with source entity.
33+
* @param bool $nullable Defines if the relation can be nullable (child can have no parent).
34+
* @param array|non-empty-string|null $innerKey Inner key in source entity. Defaults to `{relationName}_{outerKey}`.
35+
* @param array|non-empty-string|null $outerKey Outer key in the related entity. Defaults to primary key.
36+
* @param non-empty-string|null $morphKey Name of key to store related entity role. Defaults to `{relationName}_role`.
37+
* @param int $morphKeyLength The length of morph key.
38+
* @param bool $indexCreate Create an index on morphKey and innerKey.
39+
* @param non-empty-string $load Relation load approach.
40+
*/
41+
public function __construct(
42+
string $target,
43+
protected bool $cascade = true,
44+
protected bool $nullable = true,
45+
protected array|string|null $innerKey = null,
46+
protected array|string|null $outerKey = null,
47+
protected ?string $morphKey = null,
48+
protected int $morphKeyLength = 32,
49+
protected bool $indexCreate = true,
50+
#[ExpectedValues(values: ['lazy', 'eager'])]
51+
string $load = 'lazy',
52+
?Inverse $inverse = null,
53+
) {
54+
$this->inverse = $inverse;
55+
56+
parent::__construct($target, $load);
57+
}
58+
}
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+
namespace Cycle\Annotated\Tests\Fixtures\RefersToMorphed;
6+
7+
use Cycle\Annotated\Annotation\Column;
8+
use Cycle\Annotated\Annotation\Entity;
9+
use Cycle\Annotated\Annotation\Relation\Morphed\RefersToMorphed;
10+
11+
/**
12+
* @Entity
13+
*/
14+
#[Entity]
15+
class Comment
16+
{
17+
/** @Column(type="primary") */
18+
#[Column(type: 'primary')]
19+
protected $id;
20+
21+
/** @Column(type="string") */
22+
#[Column(type: 'string')]
23+
protected $message;
24+
25+
/** @RefersToMorphed(target="MorphedParentInterface") */
26+
#[RefersToMorphed(target: 'MorphedParentInterface')]
27+
protected $parent;
28+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\RefersToMorphed;
6+
7+
interface MorphedParentInterface {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Fixtures\RefersToMorphed;
6+
7+
use Cycle\Annotated\Annotation\Column;
8+
use Cycle\Annotated\Annotation\Entity;
9+
10+
/**
11+
* @Entity
12+
*/
13+
#[Entity]
14+
class Post implements MorphedParentInterface
15+
{
16+
/** @Column(type="primary") */
17+
#[Column(type: 'primary')]
18+
protected $id;
19+
20+
/** @Column(type="string") */
21+
#[Column(type: 'string')]
22+
protected $title;
23+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Functional\Driver\Common\Relation\Morphed;
6+
7+
use Cycle\Annotated\Entities;
8+
use Cycle\Annotated\Locator\TokenizerEntityLocator;
9+
use Cycle\Annotated\MergeColumns;
10+
use Cycle\Annotated\MergeIndexes;
11+
use Cycle\Annotated\Tests\Fixtures\RefersToMorphed\MorphedParentInterface;
12+
use Cycle\Annotated\Tests\Functional\Driver\Common\BaseTestCase;
13+
use Cycle\ORM\Relation;
14+
use Cycle\ORM\SchemaInterface as Schema;
15+
use Cycle\Schema\Compiler;
16+
use Cycle\Schema\Generator\GenerateRelations;
17+
use Cycle\Schema\Generator\GenerateTypecast;
18+
use Cycle\Schema\Generator\RenderRelations;
19+
use Cycle\Schema\Generator\RenderTables;
20+
use Cycle\Schema\Generator\ResetTables;
21+
use Cycle\Schema\Generator\SyncTables;
22+
use Cycle\Schema\Registry;
23+
use PHPUnit\Framework\Attributes\DataProvider;
24+
use Spiral\Attributes\ReaderInterface;
25+
use Spiral\Tokenizer\Config\TokenizerConfig;
26+
use Spiral\Tokenizer\Tokenizer;
27+
28+
abstract class RefersToMorphedTestCase extends BaseTestCase
29+
{
30+
#[DataProvider('allReadersProvider')]
31+
public function testRelation(ReaderInterface $reader): void
32+
{
33+
$locator = (new Tokenizer(new TokenizerConfig([
34+
'directories' => [__DIR__ . '/../../../../../Fixtures/RefersToMorphed'],
35+
'exclude' => [],
36+
])))->classLocator();
37+
38+
$r = new Registry($this->dbal);
39+
40+
$schema = (new Compiler())->compile($r, [
41+
new Entities(new TokenizerEntityLocator($locator, $reader), $reader),
42+
new ResetTables(),
43+
new MergeColumns($reader),
44+
new GenerateRelations(),
45+
new RenderTables(),
46+
new RenderRelations(),
47+
new MergeIndexes($reader),
48+
new SyncTables(),
49+
new GenerateTypecast(),
50+
]);
51+
52+
$this->assertArrayHasKey('parent', $schema['comment'][Schema::RELATIONS]);
53+
$this->assertSame(
54+
Relation::REFERS_TO_MORPHED,
55+
$schema['comment'][Schema::RELATIONS]['parent'][Relation::TYPE],
56+
);
57+
$this->assertSame(
58+
MorphedParentInterface::class,
59+
$schema['comment'][Schema::RELATIONS]['parent'][Relation::TARGET],
60+
);
61+
62+
// Morphed refers-to stores the outer key and the target role on the source entity.
63+
$this->assertContains('parent_id', $schema['comment'][Schema::COLUMNS]);
64+
$this->assertContains('parent_role', $schema['comment'][Schema::COLUMNS]);
65+
}
66+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Functional\Driver\MySQL\Relation\Morphed;
6+
7+
// phpcs:ignore
8+
use Cycle\Annotated\Tests\Functional\Driver\Common\Relation\Morphed\RefersToMorphedTestCase;
9+
use PHPUnit\Framework\Attributes\Group;
10+
11+
#[Group('driver')]
12+
#[Group('driver-mysql')]
13+
final class RefersToMorphedTest extends RefersToMorphedTestCase
14+
{
15+
public const DRIVER = 'mysql';
16+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Cycle\Annotated\Tests\Functional\Driver\Postgres\Relation\Morphed;
6+
7+
// phpcs:ignore
8+
use Cycle\Annotated\Tests\Functional\Driver\Common\Relation\Morphed\RefersToMorphedTestCase;
9+
use PHPUnit\Framework\Attributes\Group;
10+
11+
#[Group('driver')]
12+
#[Group('driver-postgres')]
13+
final class RefersToMorphedTest extends RefersToMorphedTestCase
14+
{
15+
public const DRIVER = 'postgres';
16+
}

0 commit comments

Comments
 (0)