Completed
Branch feature/pre-split (d50ea4)
by Anton
03:42
created

ManyToMorphedSchema::declareTables()   B

Complexity

Conditions 6
Paths 7

Size

Total Lines 78
Code Lines 44

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 44
nc 7
nop 1
dl 0
loc 78
rs 8.4316
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Spiral, Core 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\Helpers\ColumnRenderer;
12
use Spiral\ORM\ORMInterface;
13
use Spiral\ORM\Record;
14
use Spiral\ORM\Schemas\Relations\Traits\ForeignsTrait;
15
use Spiral\ORM\Schemas\Relations\Traits\MorphedTrait;
16
use Spiral\ORM\Schemas\Relations\Traits\TypecastTrait;
17
use Spiral\ORM\Schemas\SchemaBuilder;
18
19
/**
20
 * ManyToMorphed relation declares relation between parent record and set of outer records joined by
21
 * common interface. Relation allow to specify inner key (key in parent record), outer key (key in
22
 * outer records), morph key, pivot table name, names of pivot columns to store inner and outer key
23
 * values and set of additional columns. Relation DOES NOT to specify WHERE statement for outer
24
 * records. However you can specify where conditions for PIVOT table.
25
 *
26
 * You can declare this relation using same syntax as for ManyToMany except your target class
27
 * must be an interface.
28
 *
29
 * Attention, be very careful using morphing relations, you must know what you doing!
30
 * Attention #2, relation like that can not be preloaded!
31
 *
32
 * Example [Tag related to many TaggableInterface], relation name "tagged", relation requested to be
33
 * inversed using name "tags":
34
 * - relation will walk should every record implementing TaggableInterface to collect name and
35
 *   type of outer keys, if outer key is not consistent across records implementing this interface
36
 *   an exception will be raised, let's say that outer key is "id" in every record
37
 * - relation will create pivot table named "tagged_map" (if allowed), where table name generated
38
 *   based on relation name (you can change name)
39
 * - relation will create pivot key named "tag_ud" related to Tag primary key
40
 * - relation will create pivot key named "tagged_id" related to primary key of outer records,
41
 *   singular relation name used to generate key like that
42
 * - relation will create pivot key named "tagged_type" to store role of outer record
43
 * - relation will create unique index on "tag_id", "tagged_id" and "tagged_type" columns if allowed
44
 * - relation will create additional columns in pivot table if any requested
45
 *
46
 * Using in records:
47
 * You can use inversed relation as usual ManyToMany, however in Tag record relation access will be
48
 * little bit more complex - every linked record will create inner ManyToMany relation:
49
 * $tag->tagged->users->count(); //Where "users" is plural form of one outer records
50
 *
51
 * You can defined your own inner relation names by using MORPHED_ALIASES option when defining
52
 * relation.
53
 *
54
 * Attention, relation do not support WHERE statement on outer records.
55
 *
56
 * @see BelongsToMorhedSchema
57
 * @see ManyToManySchema
58
 */
59
class ManyToMorphedSchema extends AbstractSchema //implements InversableRelationInterface
60
{
61
    use TypecastTrait, ForeignsTrait, MorphedTrait;
62
63
    /**
64
     * Relation type.
65
     */
66
    const RELATION_TYPE = Record::MANY_TO_MORPHED;
67
68
    /**
69
     * Size of string column dedicated to store outer role name. Used in polymorphic relations.
70
     * Even simple relations might include morph key (usually such relations created via inversion
71
     * of polymorphic relation).
72
     *
73
     * @see RecordSchema::getRole()
74
     */
75
    const MORPH_COLUMN_SIZE = 32;
76
77
    /**
78
     * Options to be packed.
79
     */
80
    const PACK_OPTIONS = [
81
        Record::PIVOT_TABLE,
82
        Record::OUTER_KEY,
83
        Record::INNER_KEY,
84
        Record::THOUGHT_INNER_KEY,
85
        Record::THOUGHT_OUTER_KEY,
86
        Record::RELATION_COLUMNS,
87
        Record::PIVOT_COLUMNS,
88
        Record::WHERE_PIVOT
89
    ];
90
91
    /**
92
     * Default postfix for pivot tables.
93
     */
94
    const PIVOT_POSTFIX = '_map';
95
96
    /**
97
     * {@inheritdoc}
98
     *
99
     * @invisible
100
     */
101
    const OPTIONS_TEMPLATE = [
102
        //Inner key of parent record will be used to fill "THOUGHT_INNER_KEY" in pivot table
103
        Record::INNER_KEY         => '{source:primaryKey}',
104
105
        //We are going to use primary key of outer table to fill "THOUGHT_OUTER_KEY" in pivot table
106
        //This is technically "inner" key of outer record, we will name it "outer key" for simplicity
107
        Record::OUTER_KEY         => ORMInterface::R_PRIMARY_KEY,
108
109
        //Name field where parent record inner key will be stored in pivot table, role + innerKey
110
        //by default
111
        Record::THOUGHT_INNER_KEY => '{source:role}_{option:innerKey}',
112
113
        //Name field where inner key of outer record (outer key) will be stored in pivot table,
114
        //role + outerKey by default
115
        Record::THOUGHT_OUTER_KEY => '{relation:name}_id',
116
117
        //Declares what specific record pivot record linking to
118
        Record::MORPH_KEY         => '{relation:name}_type',
119
120
        //Set constraints (foreign keys) by default, attention only set for source table
121
        Record::CREATE_CONSTRAINT => true,
122
123
        //@link https://en.wikipedia.org/wiki/Foreign_key
124
        Record::CONSTRAINT_ACTION => 'CASCADE',
125
126
        //Relation allowed to create indexes in pivot table
127
        Record::CREATE_INDEXES    => true,
128
129
        //Name of pivot table to be declared, default value is not stated as it will be generated
130
        //based on roles of inner and outer records
131
        Record::PIVOT_TABLE       => '{relation:name}_map',
132
133
        //Relation allowed to create pivot table
134
        Record::CREATE_PIVOT      => true,
135
136
        //Additional set of columns to be added into pivot table, you can use same column definition
137
        //type as you using for your records
138
        Record::PIVOT_COLUMNS     => [],
139
140
        //Set of default values to be used for pivot table
141
        Record::PIVOT_DEFAULTS    => [],
142
143
        //WHERE statement in a form of simplified array definition to be applied to pivot table
144
        //data.
145
        Record::WHERE_PIVOT       => [],
146
    ];
147
148
    /**
149
     * {@inheritdoc}
150
     *
151
     * Note: pivot table will be build from direction of source, please do not attempt to create
152
     * many to many relations between databases without specifying proper database.
153
     */
154
    public function declareTables(SchemaBuilder $builder): array
155
    {
156
        $sourceContext = $this->definition->sourceContext();
157
158
        if (!interface_exists($target = $this->definition->getTarget())) {
159
            throw new RelationSchemaException("Morphed relations can only be pointed to an interface");
160
        }
161
162
        if (!$this->option(Record::CREATE_PIVOT)) {
163
            //No pivot table creation were requested, noting really to do
164
            return [];
165
        }
166
167
        $outerKey = $this->findOuter($builder);
168
        if (empty($outerKey)) {
169
            throw new RelationSchemaException("Unable to build morphed relation, no outer record found");
170
        }
171
172
        //Make sure all tables has same outer
173
        $this->verifyOuter($builder, $outerKey);
174
175
        $pivotTable = $builder->requestTable(
176
            $this->option(Record::PIVOT_TABLE),
177
            $sourceContext->getDatabase(),
178
            false,
179
            true
180
        );
181
182
        /*
183
         * Declare columns in map/pivot table.
184
         */
185
        $thoughtInnerKey = $pivotTable->column($this->option(Record::THOUGHT_INNER_KEY));
186
        $thoughtInnerKey->nullable(false);
187
        $thoughtInnerKey->setType($this->resolveType(
188
            $sourceContext->getColumn($this->option(Record::INNER_KEY))
189
        ));
190
191
        $thoughtOuterKey = $pivotTable->column($this->option(Record::THOUGHT_OUTER_KEY));
192
        $thoughtOuterKey->nullable(false);
193
        $thoughtOuterKey->setType($this->resolveType($outerKey));
194
195
        //Morph key
196
        $thoughtMorphKey = $pivotTable->column($this->option(Record::MORPH_KEY));
197
        $thoughtMorphKey->nullable(false);
198
        $thoughtMorphKey->string(static::MORPH_COLUMN_SIZE);
199
200
        /*
201
         * Declare user columns in pivot table.
202
         */
203
        $rendered = new ColumnRenderer();
204
        $rendered->renderColumns(
205
            $this->option(Record::PIVOT_COLUMNS),
206
            $this->option(Record::PIVOT_DEFAULTS),
207
            $pivotTable
208
        );
209
210
        //Map might only contain unique link between source and target
211
        if ($this->option(Record::CREATE_INDEXES)) {
212
            $pivotTable->index([
213
                $thoughtInnerKey->getName(),
214
                $thoughtOuterKey->getName(),
215
                $thoughtMorphKey->getName()
216
            ])->unique();
217
        }
218
219
        //There is only 1 constrain
220
        if ($this->isConstrained()) {
221
            $this->createForeign(
222
                $pivotTable,
223
                $thoughtInnerKey,
224
                $sourceContext->getColumn($this->option(Record::INNER_KEY)),
225
                $this->option(Record::CONSTRAINT_ACTION),
226
                $this->option(Record::CONSTRAINT_ACTION)
227
            );
228
        }
229
230
        return [$pivotTable];
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function packRelation(SchemaBuilder $builder): array
237
    {
238
        return [];
239
    }
240
}