Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
ForeignKey
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
8 / 8
14
100.00% covered (success)
100.00%
1 / 1
 references
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 renderReferences
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 onDelete
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderOnDelete
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 onUpdate
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 renderOnUpdate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 makeReferenceOption
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 renderTypeAttributes
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php declare(strict_types=1);
2/*
3 * This file is part of Aplus Framework Database Library.
4 *
5 * (c) Natan Felles <natanfelles@gmail.com>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10namespace Framework\Database\Definition\Table\Indexes\Keys;
11
12use InvalidArgumentException;
13use LogicException;
14
15/**
16 * Class ForeignKey.
17 *
18 * @see https://mariadb.com/kb/en/foreign-keys/
19 *
20 * @package database
21 */
22final class ForeignKey extends ConstraintKey
23{
24    /**
25     * The change is allowed and propagates on the child table.
26     * For example, if a parent row is deleted, the child row is also deleted;
27     * if a parent row's ID changes, the child row's ID will also change.
28     *
29     * @var string
30     */
31    public const OPT_CASCADE = 'CASCADE';
32    /**
33     * Synonym for RESTRICT.
34     *
35     * @see ForeignKey::OPT_RESTRICT
36     *
37     * @var string
38     */
39    public const OPT_NO_ACTION = 'NO ACTION';
40    /**
41     * The change on the parent table is prevented.
42     * The statement terminates with a 1451 error (SQLSTATE '2300').
43     * This is the default behavior for both ON DELETE and ON UPDATE.
44     *
45     * @var string
46     */
47    public const OPT_RESTRICT = 'RESTRICT';
48    /**
49     * The change is allowed, and the child row's foreign key columns are set
50     * to NULL.
51     *
52     * @var string
53     */
54    public const OPT_SET_NULL = 'SET NULL';
55    protected string $type = 'FOREIGN KEY';
56    protected ?string $referenceTable = null;
57    /**
58     * @var array<string>
59     */
60    protected array $referenceColumns = [];
61    protected ?string $onDelete = null;
62    protected ?string $onUpdate = null;
63
64    /**
65     * @param string $table
66     * @param string $column
67     * @param string ...$columns
68     *
69     * @return static
70     */
71    public function references(string $table, string $column, string ...$columns) : static
72    {
73        $this->referenceTable = $table;
74        $this->referenceColumns = $columns ? \array_merge([$column], $columns) : [$column];
75        return $this;
76    }
77
78    protected function renderReferences() : string
79    {
80        if ($this->referenceTable === null) {
81            throw new LogicException('REFERENCES clause was not set');
82        }
83        $table = $this->database->protectIdentifier($this->referenceTable);
84        $columns = [];
85        foreach ($this->referenceColumns as $column) {
86            $columns[] = $this->database->protectIdentifier($column);
87        }
88        $columns = \implode(', ', $columns);
89        return " REFERENCES {$table} ({$columns})";
90    }
91
92    /**
93     * @param string $option
94     *
95     * @return static
96     */
97    public function onDelete(string $option) : static
98    {
99        $this->onDelete = $option;
100        return $this;
101    }
102
103    protected function renderOnDelete() : ?string
104    {
105        if ($this->onDelete === null) {
106            return null;
107        }
108        $reference = $this->makeReferenceOption($this->onDelete);
109        return " ON DELETE {$reference}";
110    }
111
112    /**
113     * @param string $option
114     *
115     * @return static
116     */
117    public function onUpdate(string $option) : static
118    {
119        $this->onUpdate = $option;
120        return $this;
121    }
122
123    protected function renderOnUpdate() : ?string
124    {
125        if ($this->onUpdate === null) {
126            return null;
127        }
128        $reference = $this->makeReferenceOption($this->onUpdate);
129        return " ON UPDATE {$reference}";
130    }
131
132    private function makeReferenceOption(string $option) : string
133    {
134        $result = \strtoupper($option);
135        if (\in_array($result, ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'], true)) {
136            return $result;
137        }
138        throw new InvalidArgumentException("Invalid reference option: {$option}");
139    }
140
141    protected function renderTypeAttributes() : ?string
142    {
143        return $this->renderReferences() . $this->renderOnDelete() . $this->renderOnUpdate();
144    }
145}