1<?php
2
3namespace Doctrine\DBAL\Schema;
4
5use Doctrine\DBAL\Platforms\AbstractPlatform;
6
7use function array_keys;
8use function array_map;
9use function in_array;
10use function strrpos;
11use function strtolower;
12use function strtoupper;
13use function substr;
14
15/**
16 * An abstraction class for a foreign key constraint.
17 */
18class ForeignKeyConstraint extends AbstractAsset implements Constraint
19{
20    /**
21     * Instance of the referencing table the foreign key constraint is associated with.
22     *
23     * @var Table
24     */
25    protected $_localTable;
26
27    /**
28     * Asset identifier instances of the referencing table column names the foreign key constraint is associated with.
29     * array($columnName => Identifier)
30     *
31     * @var Identifier[]
32     */
33    protected $_localColumnNames;
34
35    /**
36     * Table or asset identifier instance of the referenced table name the foreign key constraint is associated with.
37     *
38     * @var Table|Identifier
39     */
40    protected $_foreignTableName;
41
42    /**
43     * Asset identifier instances of the referenced table column names the foreign key constraint is associated with.
44     * array($columnName => Identifier)
45     *
46     * @var Identifier[]
47     */
48    protected $_foreignColumnNames;
49
50    /**
51     * Options associated with the foreign key constraint.
52     *
53     * @var mixed[]
54     */
55    protected $_options;
56
57    /**
58     * Initializes the foreign key constraint.
59     *
60     * @param string[]     $localColumnNames   Names of the referencing table columns.
61     * @param Table|string $foreignTableName   Referenced table.
62     * @param string[]     $foreignColumnNames Names of the referenced table columns.
63     * @param string|null  $name               Name of the foreign key constraint.
64     * @param mixed[]      $options            Options associated with the foreign key constraint.
65     */
66    public function __construct(
67        array $localColumnNames,
68        $foreignTableName,
69        array $foreignColumnNames,
70        $name = null,
71        array $options = []
72    ) {
73        if ($name !== null) {
74            $this->_setName($name);
75        }
76
77        $this->_localColumnNames = $this->createIdentifierMap($localColumnNames);
78
79        if ($foreignTableName instanceof Table) {
80            $this->_foreignTableName = $foreignTableName;
81        } else {
82            $this->_foreignTableName = new Identifier($foreignTableName);
83        }
84
85        $this->_foreignColumnNames = $this->createIdentifierMap($foreignColumnNames);
86        $this->_options            = $options;
87    }
88
89    /**
90     * @param string[] $names
91     *
92     * @return Identifier[]
93     */
94    private function createIdentifierMap(array $names): array
95    {
96        $identifiers = [];
97
98        foreach ($names as $name) {
99            $identifiers[$name] = new Identifier($name);
100        }
101
102        return $identifiers;
103    }
104
105    /**
106     * Returns the name of the referencing table
107     * the foreign key constraint is associated with.
108     *
109     * @return string
110     */
111    public function getLocalTableName()
112    {
113        return $this->_localTable->getName();
114    }
115
116    /**
117     * Sets the Table instance of the referencing table
118     * the foreign key constraint is associated with.
119     *
120     * @param Table $table Instance of the referencing table.
121     *
122     * @return void
123     */
124    public function setLocalTable(Table $table)
125    {
126        $this->_localTable = $table;
127    }
128
129    /**
130     * @return Table
131     */
132    public function getLocalTable()
133    {
134        return $this->_localTable;
135    }
136
137    /**
138     * Returns the names of the referencing table columns
139     * the foreign key constraint is associated with.
140     *
141     * @return string[]
142     */
143    public function getLocalColumns()
144    {
145        return array_keys($this->_localColumnNames);
146    }
147
148    /**
149     * Returns the quoted representation of the referencing table column names
150     * the foreign key constraint is associated with.
151     *
152     * But only if they were defined with one or the referencing table column name
153     * is a keyword reserved by the platform.
154     * Otherwise the plain unquoted value as inserted is returned.
155     *
156     * @param AbstractPlatform $platform The platform to use for quotation.
157     *
158     * @return string[]
159     */
160    public function getQuotedLocalColumns(AbstractPlatform $platform)
161    {
162        $columns = [];
163
164        foreach ($this->_localColumnNames as $column) {
165            $columns[] = $column->getQuotedName($platform);
166        }
167
168        return $columns;
169    }
170
171    /**
172     * Returns unquoted representation of local table column names for comparison with other FK
173     *
174     * @return string[]
175     */
176    public function getUnquotedLocalColumns()
177    {
178        return array_map([$this, 'trimQuotes'], $this->getLocalColumns());
179    }
180
181    /**
182     * Returns unquoted representation of foreign table column names for comparison with other FK
183     *
184     * @return string[]
185     */
186    public function getUnquotedForeignColumns()
187    {
188        return array_map([$this, 'trimQuotes'], $this->getForeignColumns());
189    }
190
191    /**
192     * {@inheritdoc}
193     *
194     * @see getLocalColumns
195     */
196    public function getColumns()
197    {
198        return $this->getLocalColumns();
199    }
200
201    /**
202     * Returns the quoted representation of the referencing table column names
203     * the foreign key constraint is associated with.
204     *
205     * But only if they were defined with one or the referencing table column name
206     * is a keyword reserved by the platform.
207     * Otherwise the plain unquoted value as inserted is returned.
208     *
209     * @see getQuotedLocalColumns
210     *
211     * @param AbstractPlatform $platform The platform to use for quotation.
212     *
213     * @return string[]
214     */
215    public function getQuotedColumns(AbstractPlatform $platform)
216    {
217        return $this->getQuotedLocalColumns($platform);
218    }
219
220    /**
221     * Returns the name of the referenced table
222     * the foreign key constraint is associated with.
223     *
224     * @return string
225     */
226    public function getForeignTableName()
227    {
228        return $this->_foreignTableName->getName();
229    }
230
231    /**
232     * Returns the non-schema qualified foreign table name.
233     *
234     * @return string
235     */
236    public function getUnqualifiedForeignTableName()
237    {
238        $name     = $this->_foreignTableName->getName();
239        $position = strrpos($name, '.');
240
241        if ($position !== false) {
242            $name = substr($name, $position + 1);
243        }
244
245        return strtolower($name);
246    }
247
248    /**
249     * Returns the quoted representation of the referenced table name
250     * the foreign key constraint is associated with.
251     *
252     * But only if it was defined with one or the referenced table name
253     * is a keyword reserved by the platform.
254     * Otherwise the plain unquoted value as inserted is returned.
255     *
256     * @param AbstractPlatform $platform The platform to use for quotation.
257     *
258     * @return string
259     */
260    public function getQuotedForeignTableName(AbstractPlatform $platform)
261    {
262        return $this->_foreignTableName->getQuotedName($platform);
263    }
264
265    /**
266     * Returns the names of the referenced table columns
267     * the foreign key constraint is associated with.
268     *
269     * @return string[]
270     */
271    public function getForeignColumns()
272    {
273        return array_keys($this->_foreignColumnNames);
274    }
275
276    /**
277     * Returns the quoted representation of the referenced table column names
278     * the foreign key constraint is associated with.
279     *
280     * But only if they were defined with one or the referenced table column name
281     * is a keyword reserved by the platform.
282     * Otherwise the plain unquoted value as inserted is returned.
283     *
284     * @param AbstractPlatform $platform The platform to use for quotation.
285     *
286     * @return string[]
287     */
288    public function getQuotedForeignColumns(AbstractPlatform $platform)
289    {
290        $columns = [];
291
292        foreach ($this->_foreignColumnNames as $column) {
293            $columns[] = $column->getQuotedName($platform);
294        }
295
296        return $columns;
297    }
298
299    /**
300     * Returns whether or not a given option
301     * is associated with the foreign key constraint.
302     *
303     * @param string $name Name of the option to check.
304     *
305     * @return bool
306     */
307    public function hasOption($name)
308    {
309        return isset($this->_options[$name]);
310    }
311
312    /**
313     * Returns an option associated with the foreign key constraint.
314     *
315     * @param string $name Name of the option the foreign key constraint is associated with.
316     *
317     * @return mixed
318     */
319    public function getOption($name)
320    {
321        return $this->_options[$name];
322    }
323
324    /**
325     * Returns the options associated with the foreign key constraint.
326     *
327     * @return mixed[]
328     */
329    public function getOptions()
330    {
331        return $this->_options;
332    }
333
334    /**
335     * Returns the referential action for UPDATE operations
336     * on the referenced table the foreign key constraint is associated with.
337     *
338     * @return string|null
339     */
340    public function onUpdate()
341    {
342        return $this->onEvent('onUpdate');
343    }
344
345    /**
346     * Returns the referential action for DELETE operations
347     * on the referenced table the foreign key constraint is associated with.
348     *
349     * @return string|null
350     */
351    public function onDelete()
352    {
353        return $this->onEvent('onDelete');
354    }
355
356    /**
357     * Returns the referential action for a given database operation
358     * on the referenced table the foreign key constraint is associated with.
359     *
360     * @param string $event Name of the database operation/event to return the referential action for.
361     *
362     * @return string|null
363     */
364    private function onEvent($event)
365    {
366        if (isset($this->_options[$event])) {
367            $onEvent = strtoupper($this->_options[$event]);
368
369            if (! in_array($onEvent, ['NO ACTION', 'RESTRICT'])) {
370                return $onEvent;
371            }
372        }
373
374        return null;
375    }
376
377    /**
378     * Checks whether this foreign key constraint intersects the given index columns.
379     *
380     * Returns `true` if at least one of this foreign key's local columns
381     * matches one of the given index's columns, `false` otherwise.
382     *
383     * @param Index $index The index to be checked against.
384     *
385     * @return bool
386     */
387    public function intersectsIndexColumns(Index $index)
388    {
389        foreach ($index->getColumns() as $indexColumn) {
390            foreach ($this->_localColumnNames as $localColumn) {
391                if (strtolower($indexColumn) === strtolower($localColumn->getName())) {
392                    return true;
393                }
394            }
395        }
396
397        return false;
398    }
399}
400