Completed
Branch feature/pre-split (57df1d)
by Anton
10:42
created

AbstractReference::references()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\Database\Schemas\Prototypes;
8
9
use Spiral\Database\Entities\Driver;
10
use Spiral\Database\Exceptions\SchemaException;
11
use Spiral\Database\Schemas\ReferenceInterface;
12
13
/**
14
 * Abstract foreign schema with read (see ReferenceInterface) and write abilities. Must be
15
 * implemented by driver to support DBMS specific syntax and creation rules.
16
 */
17
abstract class AbstractReference extends AbstractElement implements ReferenceInterface
18
{
19
    /**
20
     * Parent table isolation prefix.
21
     *
22
     * @var string
23
     */
24
    protected $tablePrefix = '';
25
26
    /**
27
     * Local column name (key name).
28
     *
29
     * @var string
30
     */
31
    protected $column = '';
32
33
    /**
34
     * Referenced table name (including prefix).
35
     *
36
     * @var string
37
     */
38
    protected $foreignTable = '';
39
40
    /**
41
     * Linked foreign key name (foreign column).
42
     *
43
     * @var string
44
     */
45
    protected $foreignKey = '';
46
47
    /**
48
     * Action on foreign column value deletion.
49
     *
50
     * @var string
51
     */
52
    protected $deleteRule = self::NO_ACTION;
53
54
    /**
55
     * Action on foreign column value update.
56
     *
57
     * @var string
58
     */
59
    protected $updateRule = self::NO_ACTION;
60
61
    /**
62
     * {@inheritdoc}
63
     *
64
     * @param string $name
65
     */
66
    public function setName(string $name): AbstractElement
67
    {
68
        if (!empty($this->name)) {
69
            throw new SchemaException('Changing reference name is not allowed');
70
        }
71
72
        return parent::setName($name);
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function getName(): string
79
    {
80
        if (empty($this->name)) {
81
            //Generating name on a fly
82
            $this->setName($this->generateName());
83
        }
84
85
        return parent::getName();
86
    }
87
88
    /**
89
     * {@inheritdoc}
90
     */
91
    public function getColumn(): string
92
    {
93
        return $this->column;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function getForeignTable(): string
100
    {
101
        return $this->foreignTable;
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function getForeignKey(): string
108
    {
109
        return $this->foreignKey;
110
    }
111
112
    /**
113
     * {@inheritdoc}
114
     */
115
    public function getDeleteRule(): string
116
    {
117
        return $this->deleteRule;
118
    }
119
120
    /**
121
     * {@inheritdoc}
122
     */
123
    public function getUpdateRule(): string
124
    {
125
        return $this->updateRule;
126
    }
127
128
    /**
129
     * Set local column name foreign key relates to. Make sure column type is the same as foreign
130
     * column one.
131
     *
132
     * @param string $column
133
     *
134
     * @return self
135
     */
136
    public function column(string $column): AbstractReference
137
    {
138
        $this->column = $column;
139
140
        return $this;
141
    }
142
143
    /**
144
     * Set foreign table name and key local column must reference to. Make sure local and foreign
145
     * column types are identical.
146
     *
147
     * @param string $table       Foreign table name with or without database prefix (see 3rd
148
     *                            argument).
149
     * @param string $column      Foreign key name (id by default).
150
     * @param bool   $forcePrefix When true foreign table will get same prefix as table being
151
     *                            modified.
152
     *
153
     * @return self
154
     */
155
    public function references(
156
        string $table,
157
        string $column = 'id',
158
        bool $forcePrefix = true
159
    ): AbstractReference {
160
        $this->foreignTable = ($forcePrefix ? $this->tablePrefix : '') . $table;
161
        $this->foreignKey = $column;
162
163
        return $this;
164
    }
165
166
    /**
167
     * Set foreign key delete behaviour.
168
     *
169
     * @param string $rule Possible values: NO ACTION, CASCADE, etc (driver specific).
170
     *
171
     * @return self
172
     */
173
    public function onDelete(string $rule = self::NO_ACTION): AbstractReference
174
    {
175
        $this->deleteRule = strtoupper($rule);
176
177
        return $this;
178
    }
179
180
    /**
181
     * Set foreign key update behaviour.
182
     *
183
     * @param string $rule Possible values: NO ACTION, CASCADE, etc (driver specific).
184
     *
185
     * @return self
186
     */
187
    public function onUpdate(string $rule = self::NO_ACTION): AbstractReference
188
    {
189
        $this->updateRule = strtoupper($rule);
190
191
        return $this;
192
    }
193
194
    /**
195
     * Foreign key creation syntax.
196
     *
197
     * @param Driver $driver
198
     *
199
     * @return string
200
     */
201
    public function sqlStatement(Driver $driver): string
202
    {
203
        $statement = [];
204
205
        $statement[] = 'CONSTRAINT';
206
        $statement[] = $driver->identifier($this->name);
207
        $statement[] = 'FOREIGN KEY';
208
        $statement[] = '(' . $driver->identifier($this->column) . ')';
209
210
        $statement[] = 'REFERENCES ' . $driver->identifier($this->foreignTable);
211
        $statement[] = '(' . $driver->identifier($this->foreignKey) . ')';
212
213
        $statement[] = "ON DELETE {$this->deleteRule}";
214
        $statement[] = "ON UPDATE {$this->updateRule}";
215
216
        return implode(' ', $statement);
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function compare(ReferenceInterface $initial): bool
223
    {
224
        return $this == clone $initial;
225
    }
226
227
    /**
228
     * Generate unique foreign key name.
229
     *
230
     * @return string
231
     */
232
    protected function generateName(): string
233
    {
234
        $name = $this->table . '_foreign_' . $this->column . '_' . uniqid();
235
236
        if (strlen($name) > 64) {
237
            //Many dbs has limitations on identifier length
238
            $name = md5($name);
239
        }
240
241
        return $name;
242
    }
243
}