1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* components |
4
|
|
|
* |
5
|
|
|
* @author Wolfy-J |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Spiral\ORM\Schemas\Relations; |
9
|
|
|
|
10
|
|
|
use Spiral\ORM\Exceptions\RelationSchemaException; |
11
|
|
|
use Spiral\ORM\ORMInterface; |
12
|
|
|
use Spiral\ORM\Record; |
13
|
|
|
use Spiral\ORM\Schemas\InversableRelationInterface; |
14
|
|
|
use Spiral\ORM\Schemas\Relations\Traits\MorphedTrait; |
15
|
|
|
use Spiral\ORM\Schemas\Relations\Traits\TypecastTrait; |
16
|
|
|
use Spiral\ORM\Schemas\SchemaBuilder; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* BelongsToMorphed are almost identical to BelongsTo except it parent Record defined by role value |
20
|
|
|
* stored in [morph key] and parent key in [inner key]. |
21
|
|
|
* |
22
|
|
|
* You can define BelongsToMorphed relation using syntax for BelongsTo but declaring outer class |
23
|
|
|
* as interface, meaning you should not only declare inversed relation name, but also it's type - |
24
|
|
|
* HAS_ONE or HAS_MANY. |
25
|
|
|
* |
26
|
|
|
* Example: 'parent' => [self::BELONGS_TO_MORPHED => 'Records\CommentableInterface'] |
27
|
|
|
* |
28
|
|
|
* Attention, be very careful using morphing relations, you must know what you doing! |
29
|
|
|
* Attention #2, relation like that can not be preloaded! |
30
|
|
|
* |
31
|
|
|
* Attention #3, inverse morphed relation to use it efficiently (inversed into HAS_ONE relation). |
32
|
|
|
* |
33
|
|
|
* Example, [Comment can belong to any CommentableInterface record], relation name "parent", |
34
|
|
|
* relation requested to be inversed into HAS_MANY "comments": |
35
|
|
|
* - relation will walk should every record implementing CommentableInterface to collect name and |
36
|
|
|
* type of outer keys, if outer key is not consistent across records implementing this interface |
37
|
|
|
* an exception will be raised, let's say that outer key is "id" in every record |
38
|
|
|
* - relation will create inner key "parent_id" in "comments" table (or other table name), nullable |
39
|
|
|
* by default |
40
|
|
|
* - relation will create "parent_type" morph key in "comments" table, nullable by default |
41
|
|
|
* - relation will create complex index index on columns "parent_id" and "parent_type" in |
42
|
|
|
* "comments" table if allowed |
43
|
|
|
* - due relation is inversable every record implementing CommentableInterface will receive |
44
|
|
|
* HAS_MANY relation "comments" pointing to Comment record using record role value |
45
|
|
|
* |
46
|
|
|
* @see BelongsToSchema |
47
|
|
|
*/ |
48
|
|
|
class BelongsToMorphedSchema extends AbstractSchema implements InversableRelationInterface |
49
|
|
|
{ |
50
|
|
|
use MorphedTrait, TypecastTrait; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Relation type. |
54
|
|
|
*/ |
55
|
|
|
const RELATION_TYPE = Record::BELONGS_TO_MORPHED; |
56
|
|
|
|
57
|
|
|
/** |
58
|
|
|
* Size of string column dedicated to store outer role name. Used in polymorphic relations. |
59
|
|
|
* Even simple relations might include morph key (usually such relations created via inversion |
60
|
|
|
* of polymorphic relation). |
61
|
|
|
* |
62
|
|
|
* @see RecordSchema::getRole() |
63
|
|
|
*/ |
64
|
|
|
const MORPH_COLUMN_SIZE = 32; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Options needed in runtime. |
68
|
|
|
*/ |
69
|
|
|
const PACK_OPTIONS = [ |
70
|
|
|
Record::INNER_KEY, |
71
|
|
|
Record::OUTER_KEY, |
72
|
|
|
Record::MORPH_KEY, |
73
|
|
|
Record::NULLABLE |
74
|
|
|
]; |
75
|
|
|
|
76
|
|
|
/** |
77
|
|
|
* {@inheritdoc} |
78
|
|
|
*/ |
79
|
|
|
const OPTIONS_TEMPLATE = [ |
80
|
|
|
//By default morphed relations points to PRIMARY KEY |
81
|
|
|
Record::OUTER_KEY => ORMInterface::R_PRIMARY_KEY, |
82
|
|
|
|
83
|
|
|
//Inner key will be based on singular name of relation and outer key name |
84
|
|
|
Record::INNER_KEY => '{relation:singular}_id', |
85
|
|
|
|
86
|
|
|
//Inner key will be based on singular name of relation and outer key name |
87
|
|
|
Record::MORPH_KEY => '{relation:singular}_type', |
88
|
|
|
|
89
|
|
|
//Relation allowed to create indexes in inner table |
90
|
|
|
Record::CREATE_INDEXES => true, |
91
|
|
|
|
92
|
|
|
//We are going to make all relations nullable by default, so we can add fields to existed |
93
|
|
|
//tables without raising an exceptions |
94
|
|
|
Record::NULLABLE => true, |
95
|
|
|
]; |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* {@inheritdoc} |
99
|
|
|
* |
100
|
|
|
* Relation will be inversed into every associated parent. |
101
|
|
|
*/ |
102
|
|
|
public function inverseDefinition(SchemaBuilder $builder, $inverseTo) |
103
|
|
|
{ |
104
|
|
|
$inversed = []; |
105
|
|
|
foreach ($this->findTargets($builder) as $schema) { |
|
|
|
|
106
|
|
|
|
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
return $inversed; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
/** |
113
|
|
|
* {@inheritdoc} |
114
|
|
|
*/ |
115
|
|
|
public function declareTables(SchemaBuilder $builder): array |
116
|
|
|
{ |
117
|
|
|
$sourceTable = $this->sourceTable($builder); |
118
|
|
|
|
119
|
|
|
if (!interface_exists($target = $this->definition->getTarget())) { |
120
|
|
|
throw new RelationSchemaException("Morphed relations can only be pointed to an interface"); |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$outerKey = $this->findOuter($builder); |
124
|
|
|
if (empty($outerKey)) { |
125
|
|
|
throw new RelationSchemaException("Unable to build morphed relation, no outer record found"); |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
//Make sure all tables has same outer |
129
|
|
|
$this->verifyOuter($builder, $outerKey); |
130
|
|
|
|
131
|
|
|
//Column to be used as inner key |
132
|
|
|
$innerKey = $sourceTable->column($this->option(Record::INNER_KEY)); |
133
|
|
|
|
134
|
|
|
//Syncing types |
135
|
|
|
$innerKey->setType($this->resolveType($outerKey)); |
136
|
|
|
|
137
|
|
|
//If nullable |
138
|
|
|
$innerKey->nullable($this->option(Record::NULLABLE)); |
139
|
|
|
|
140
|
|
|
//Morph key is always string |
141
|
|
|
$morphKey = $sourceTable->column($this->option(Record::MORPH_KEY)); |
142
|
|
|
$morphKey->string(self::MORPH_COLUMN_SIZE); |
143
|
|
|
|
144
|
|
|
//Do we need indexes? |
145
|
|
|
if ($this->option(Record::CREATE_INDEXES)) { |
146
|
|
|
//Compound outer key |
147
|
|
|
$sourceTable->index([$innerKey->getName(), $morphKey->getName()]); |
148
|
|
|
} |
149
|
|
|
|
150
|
|
|
//No constrains to create |
151
|
|
|
return [$sourceTable]; |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
/** |
155
|
|
|
* {@inheritdoc} |
156
|
|
|
*/ |
157
|
|
|
public function packRelation(SchemaBuilder $builder): array |
158
|
|
|
{ |
159
|
|
|
$schema = parent::packRelation($builder); |
160
|
|
|
$schema[ORMInterface::R_SCHEMA][Record::OUTER_KEY] = $this->findOuter($builder)->getName(); |
161
|
|
|
|
162
|
|
|
foreach ($this->findTargets($builder) as $outer) { |
163
|
|
|
//Role => model mapping |
164
|
|
|
$schema[ORMInterface::R_SCHEMA][ORMInterface::R_ROLE_NAME][$outer->getRole()] = $outer->getClass(); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
return $schema; |
168
|
|
|
} |
169
|
|
|
} |
This check looks for
foreach
loops that have no statements or where all statements have been commented out. This may be the result of changes for debugging or the code may simply be obsolete.Consider removing the loop.