Completed
Branch feature/pre-split (24875e)
by Anton
03:28
created

BelongsToMorphedSchema::inverseDefinition()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 2
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
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) {
0 ignored issues
show
Unused Code introduced by
This foreach statement is empty and can be removed.

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.

Loading history...
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
}