Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.85% covered (warning)
84.85%
56 / 66
28.57% covered (danger)
28.57%
2 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeleteMapper
84.85% covered (warning)
84.85%
56 / 66
28.57% covered (danger)
28.57%
2 / 7
34.34
0.00% covered (danger)
0.00%
0 / 1
 delete
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 executeDelete
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 deleteModel
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 deleteSingleRelation
83.33% covered (warning)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
5.12
 deleteHasMany
75.00% covered (warning)
75.00%
15 / 20
0.00% covered (danger)
0.00%
0 / 1
12.89
 deleteRelationTable
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
7.02
1<?php
2/**
3 * Jingga
4 *
5 * PHP Version 8.1
6 *
7 * @package   phpOMS\DataStorage\Database\Mapper
8 * @copyright Dennis Eichhorn
9 * @license   OMS License 2.0
10 * @version   1.0.0
11 * @link      https://jingga.app
12 */
13declare(strict_types=1);
14
15namespace phpOMS\DataStorage\Database\Mapper;
16
17use phpOMS\DataStorage\Database\Query\Builder;
18
19/**
20 * Delete mapper (DELETE).
21 *
22 * @package phpOMS\DataStorage\Database\Mapper
23 * @license OMS License 2.0
24 * @link    https://jingga.app
25 * @since   1.0.0
26 */
27final class DeleteMapper extends DataMapperAbstract
28{
29    /**
30     * Get delete mapper
31     *
32     * @return self
33     *
34     * @since 1.0.0
35     */
36    public function delete() : self
37    {
38        $this->type = MapperType::DELETE;
39
40        return $this;
41    }
42
43    /**
44     * Execute mapper
45     *
46     * @param mixed ...$options Options to pass to the selete mapper
47     *
48     * @return mixed
49     *
50     * @since 1.0.0
51     */
52    public function execute(mixed ...$options) : mixed
53    {
54        switch($this->type) {
55            case MapperType::DELETE:
56                /** @var object[] ...$options */
57                return $this->executeDelete(...$options);
58            default:
59                return null;
60        }
61    }
62
63    /**
64     * Execute mapper
65     *
66     * @param object $obj Object to delete
67     *
68     * @return mixed
69     *
70     * @since 1.0.0
71     */
72    public function executeDelete(object $obj) : mixed
73    {
74        $refClass = new \ReflectionClass($obj);
75        $objId    = $this->mapper::getObjectId($obj);
76
77        if (empty($objId)) {
78            return null;
79        }
80
81        $this->deleteSingleRelation($obj, $refClass, $this->mapper::BELONGS_TO);
82        $this->deleteHasMany($refClass, $obj, $objId);
83        $this->deleteModel($objId);
84        $this->deleteSingleRelation($obj, $refClass, $this->mapper::OWNS_ONE);
85
86        return $objId;
87    }
88
89    /**
90     * Delete model
91     *
92     * @param mixed $objId Object id to delete
93     *
94     * @return void
95     *
96     * @since 1.0.0
97     */
98    private function deleteModel(mixed $objId) : void
99    {
100        $query = new Builder($this->db);
101        $query->delete()
102            ->from($this->mapper::TABLE)
103            ->where($this->mapper::TABLE . '.' . $this->mapper::PRIMARYFIELD, '=', $objId);
104
105        $sth = $this->db->con->prepare($query->toSql());
106        if ($sth !== false) {
107            $sth->execute();
108        }
109    }
110
111    /**
112     * Delete ownsOne, belongsTo relations
113     *
114     * @param object           $obj      Object to delete
115     * @param \ReflectionClass $refClass Reflection of object to delete
116     * @param array            $relation Relation data (e.g. ::BELONGS_TO, ::OWNS_ONE)
117     *
118     * @return void
119     *
120     * @since 1.0.0
121     */
122    private function deleteSingleRelation(object $obj, \ReflectionClass $refClass, array $relation) : void
123    {
124        if (empty($relation)) {
125            return;
126        }
127
128        foreach ($relation as $member => $relData) {
129            if (!isset($this->with[$member])) {
130                continue;
131            }
132
133            /** @var class-string<DataMapperFactory> $mapper */
134            $mapper = $relData['mapper'];
135
136            /** @var self $relMapper */
137            $relMapper        = $this->createRelationMapper($mapper::delete(db: $this->db), $member);
138            $relMapper->depth = $this->depth + 1;
139
140            $refProp = $refClass->getProperty($member);
141            if (!$refProp->isPublic()) {
142                $relMapper->execute($refProp->getValue($obj));
143            } else {
144                $relMapper->execute($obj->{$member});
145            }
146        }
147    }
148
149    /**
150     * Delete hasMany
151     *
152     * @param \ReflectionClass $refClass Reflection of object to delete
153     * @param object           $obj      Object to delete
154     * @param mixed            $objId    Object id to delete
155     *
156     * @return void
157     *
158     * @since 1.0.0
159     */
160    private function deleteHasMany(\ReflectionClass $refClass, object $obj, mixed $objId) : void
161    {
162        if (empty($this->mapper::HAS_MANY)) {
163            return;
164        }
165
166        foreach ($this->mapper::HAS_MANY as $member => $rel) {
167            // always
168            if (!isset($this->with[$member]) && !isset($rel['external'])) {
169                continue;
170            }
171
172            $objIds  = [];
173            $refProp = $refClass->getProperty($member);
174            $values  = $refProp->isPublic() ? $obj->{$member} : $refProp->getValue($obj);
175
176            if (!\is_array($values)) {
177                // conditionals
178                continue;
179            }
180
181            /** @var class-string<DataMapperFactory> $mapper */
182            $mapper = $this->mapper::HAS_MANY[$member]['mapper'];
183
184            foreach ($values as $key => $value) {
185                if (!\is_object($value)) {
186                    // Is scalar => already in database
187                    $objIds[$key] = $value;
188
189                    continue;
190                }
191
192                $objIds[$key] = $mapper::getObjectId($value);
193            }
194
195            // delete relation tables
196            if (isset($rel['external'])) {
197                $this->deleteRelationTable($member, $objIds, $objId);
198            } else {
199                // only delete related obj if it is NOT in a relation table
200                // if it is not in a relation table it must be directly related
201                // this means it CAN ONLY be related to this object and not others
202                foreach ($objIds as $id) {
203                    $mapper::delete(db: $this->db)->execute($id);
204                }
205            }
206        }
207    }
208
209    /**
210     * Delete has many relations if the relation is handled in a relation table
211     *
212     * @param string $member Property which contains the has many models
213     * @param array  $objIds Objects which are related to the parent object
214     * @param mixed  $objId  Parent object id
215     *
216     * @return void
217     *
218     * @since 1.0.0
219     */
220    public function deleteRelationTable(string $member, array $objIds = null, mixed $objId) : void
221    {
222        if ((empty($objIds) && $objIds !== null)
223            || $this->mapper::HAS_MANY[$member]['table'] === $this->mapper::TABLE
224            || $this->mapper::HAS_MANY[$member]['table'] === $this->mapper::HAS_MANY[$member]['mapper']::TABLE
225        ) {
226            return;
227        }
228
229        $relQuery = new Builder($this->db);
230        $relQuery->delete()
231            ->from($this->mapper::HAS_MANY[$member]['table'])
232            ->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['self'], '=', $objId);
233
234        if ($objIds !== null) {
235            $relQuery->where($this->mapper::HAS_MANY[$member]['table'] . '.' . $this->mapper::HAS_MANY[$member]['external'], 'in', $objIds);
236        }
237
238        $sth = $this->db->con->prepare($relQuery->toSql());
239        if ($sth !== false) {
240            $sth->execute();
241        }
242    }
243}