Completed
Pull Request — master (#3)
by Rémy
01:31
created

Model::setConnectionResolver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
ccs 0
cts 4
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * @author Rémy M. Böhler <[email protected]>
4
 */
5
declare(strict_types=1);
6
7
namespace Rorm;
8
9
use Iterator;
10
use JsonSerializable;
11
12
abstract class Model implements Iterator, JsonSerializable
13
{
14
    /** @var string */
15
    public static $_table;
16
17
    /** @var string|array */
18
    public static $_idColumn = 'id';
19
20
    /** @var bool */
21
    public static $_autoId = true;
22
23
    /** @var array */
24
    public static $_ignoreColumns = [];
25
26
    /** @var string */
27
    public static $_connection = Rorm::CONNECTION_DEFAULT;
28
29
    /** @var array */
30
    public $_data = [];
31
32
    /** @var ConnectionResolver */
33
    private static $connectionResolver;
34
35
    public static function setConnectionResolver(ConnectionResolver $resolver): void
36
    {
37
        static::$connectionResolver = $resolver;
0 ignored issues
show
Bug introduced by
Since $connectionResolver is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $connectionResolver to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
38
    }
39
40
    public static function unsetConnectionResolver(): void
41
    {
42
        static::$connectionResolver = null;
0 ignored issues
show
Bug introduced by
Since $connectionResolver is declared private, accessing it with static will lead to errors in possible sub-classes; consider using self, or increasing the visibility of $connectionResolver to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return static::$someVariable;
    }
}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass { }

YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class SomeClass
{
    private static $someVariable;

    public static function getSomeVariable()
    {
        return self::$someVariable; // self works fine with private.
    }
}
Loading history...
43
    }
44
45
46
    /**
47
     * @deprecated
48
     */
49
    public static function getTable(): string
50
    {
51
        if (static::$_table !== null) {
52
            return static::$_table;
53
        }
54
55
        return strtolower(str_replace('\\', '_', static::class));
56
    }
57
58
    /**
59
     * @throws \Rorm\Exception
60
     * @deprecated
61
     */
62
    public static function getDatabase(): \PDO
63
    {
64
        return Rorm::getDatabase(static::$_connection);
0 ignored issues
show
Bug introduced by
The method getDatabase() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
65
    }
66
67
    /**
68
     * @return static
69
     * @deprecated
70
     */
71
    public static function create(): Model
72
    {
73
        return new static();
74
    }
75
76
    /**
77
     * @return static
0 ignored issues
show
Documentation introduced by
Should the return type not be Model|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
78
     */
79
    public static function find($id): ?Model
80
    {
81
        $query = static::query();
82
        call_user_func_array([$query, 'whereId'], func_get_args());
83
        return $query->findOne();
84
    }
85
86
    /**
87
     * @return static[]
88
     */
89
    public static function findAll(): array
90
    {
91
        return static::query()->findAll();
92
    }
93
94
    public static function query(): QueryBuilder
95
    {
96
        return new QueryBuilder(static::getTable(), static::$_idColumn, static::class, static::getDatabase());
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getTable() has been deprecated.

This method has been deprecated.

Loading history...
Deprecated Code introduced by
The method Rorm\Model::getDatabase() has been deprecated.

This method has been deprecated.

Loading history...
97
    }
98
99
    public static function customQuery(string $query, array $params = []): Query
100
    {
101
        $ormQuery = new Query(static::class, static::getDatabase());
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getDatabase() has been deprecated.

This method has been deprecated.

Loading history...
102
        $ormQuery->setQuery($query);
103
        if (!empty($params)) {
104
            $ormQuery->setParams($params);
105
        }
106
        return $ormQuery;
107
    }
108
109
    public function getId()
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
110
    {
111
        if (is_array(static::$_idColumn)) {
112
            $result = [];
113
            /** @var string[] $columns */
114
            $columns = static::$_idColumn;
115
            foreach ($columns as $key) {
116
                $result[$key] = $this->get($key);
117
            }
118
            return $result;
119
        } else {
120
            return $this->get(static::$_idColumn);
121
        }
122
    }
123
124
    public function hasId(): bool
125
    {
126
        if (is_array(static::$_idColumn)) {
127
            /** @var string[] $columns */
128
            $columns = static::$_idColumn;
129
            foreach ($columns as $key) {
130
                $value = $this->get($key);
131
                if (empty($value)) {
132
                    return false;
133
                }
134
            }
135
            return true;
136
        } else {
137
            $value = $this->get(static::$_idColumn);
138
            return !empty($value);
139
        }
140
    }
141
142
    /**
143
     * @throws QueryException
144
     * @throws \PDOException
145
     */
146
    public function save(): bool
147
    {
148
        if (empty($this->_data)) {
149
            throw new QueryException('can not save empty data!');
150
        }
151
152
        $dbh = static::getDatabase();
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getDatabase() has been deprecated.

This method has been deprecated.

Loading history...
153
        $quoteIdentifier = Rorm::getIdentifierQuoter($dbh);
0 ignored issues
show
Bug introduced by
The method getIdentifierQuoter() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
154
        $quotedTable = $quoteIdentifier(static::getTable());
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getTable() has been deprecated.

This method has been deprecated.

Loading history...
155
156
        $idColumns = (array)static::$_idColumn;
157
        $doMerge = $this->hasId();
158
159
        // ignore fields
160
        $notSetFields = static::$_ignoreColumns;
161
162
        /**
163
         * Different queries are built for each driver
164
         *
165
         * IDEA: probably split into methods (saveMySQL, saveSQLite)
166
         */
167
        if (Rorm::isMySQL($dbh)) {
0 ignored issues
show
Bug introduced by
The method isMySQL() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
168
            /**
169
             * MySQL
170
             * Instead of REPLACE INTO we use INSERT INTO ON DUPLICATE KEY UPDATE.
171
             * Because REPLACE INTO does DELETE and INSERT,
172
             * which does not play nice with TRIGGERs and FOREIGN KEY CONSTRAINTS
173
             */
174
            $sql = 'INSERT INTO ' . $quotedTable . ' ';
175
176
            $insertData = [];
177
            $updateData = [];
178
179
            foreach ($this->_data as $column => $value) {
180
                if (in_array($column, $notSetFields, true)) {
181
                    continue;
182
                }
183
184
                $quotedColumn = $quoteIdentifier($column);
185
                $insertData[$quotedColumn] = Rorm::quote($dbh, $value);
0 ignored issues
show
Bug introduced by
The method quote() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
186
187
                if ($doMerge && !in_array($column, $idColumns, true)) {
188
                    $updateData[] = $quotedColumn . ' = VALUES(' . $quotedColumn . ')';
189
                }
190
            }
191
            unset($column, $value, $quotedColumn);
192
193
            // insert
194
            $sql .=
195
                '(' . implode(', ', array_keys($insertData)) . ')' .
196
                ' VALUES ' .
197
                '(' . implode(', ', $insertData) . ')';
198
199
            if ($doMerge && count($updateData) > 0) {
200
                // update
201
                $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updateData);
202
            }
203
204
            // execute (most likely throws PDOException if there is an error)
205
            if ($dbh->exec($sql) === false) {
206
                return false; // @codeCoverageIgnore
207
            }
208
209
            // update generated id
210
            if (static::$_autoId && !$doMerge) {
211
                // last insert id
212
                $this->set(static::$_idColumn, $dbh->lastInsertId());
213
            }
214
215
            return true;
216
        } else {
217
            /**
218
             * SQLite
219
             */
220
            if ($doMerge) {
221
                $sql = 'INSERT OR REPLACE INTO ' . $quotedTable . ' ';
222
            } else {
223
                $sql = 'INSERT INTO ' . $quotedTable . ' ';
224
            }
225
226
            // build (column) VALUES (values)
227
            $quotedData = [];
228
            foreach ($this->_data as $column => $value) {
229
                if (in_array($column, $notSetFields, true)) {
230
                    continue;
231
                }
232
233
                $quotedData[$quoteIdentifier($column)] = Rorm::quote($dbh, $value);
0 ignored issues
show
Bug introduced by
The method quote() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
234
            }
235
            unset($column, $value);
236
237
            $sql .= '(' . implode(', ', array_keys($quotedData)) . ') VALUES (' . implode(', ', $quotedData) . ')';
238
239
            // execute (most likely throws PDOException if there is an error)
240
            if ($dbh->exec($sql) === false) {
241
                return false; // @codeCoverageIgnore
242
            }
243
244
            // update generated id
245
            if (static::$_autoId && !$this->hasId()) {
246
                // last insert id
247
                $this->set(static::$_idColumn, $dbh->lastInsertId());
248
            }
249
250
            return true;
251
        }
252
    }
253
254
    public function delete(): bool
255
    {
256
        $dbh = static::getDatabase();
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getDatabase() has been deprecated.

This method has been deprecated.

Loading history...
257
        $quoteIdentifier = Rorm::getIdentifierQuoter($dbh);
0 ignored issues
show
Bug introduced by
The method getIdentifierQuoter() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
258
259
        $idColumns = (array)static::$_idColumn;
260
261
        $where = [];
262
        foreach ($idColumns as $columnName) {
263
            $where[] = $quoteIdentifier($columnName) . ' = ' . Rorm::quote($dbh, $this->$columnName);
0 ignored issues
show
Bug introduced by
The method quote() does not seem to exist on object<Rorm\Rorm>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
264
        }
265
266
        $sql = 'DELETE FROM ' . $quoteIdentifier(static::getTable()) . ' WHERE ' . implode(' AND ', $where);
0 ignored issues
show
Deprecated Code introduced by
The method Rorm\Model::getTable() has been deprecated.

This method has been deprecated.

Loading history...
267
268
        return $dbh->exec($sql) > 0;
269
    }
270
271
    // data access
272
    public function getData(): array
273
    {
274
        return $this->_data;
275
    }
276
277
    public function setData(array $data): void
278
    {
279
        $this->_data = $data;
280
    }
281
282
    public function get(string $name)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
283
    {
284
        if (array_key_exists($name, $this->_data)) {
285
            return $this->_data[$name];
286
        }
287
        return null;
288
    }
289
290
    public function set(string $name, $value): Model
291
    {
292
        $this->_data[$name] = $value;
293
        return $this;
294
    }
295
296
    public function has(string $name): bool
297
    {
298
        return isset($this->_data[$name]);
299
    }
300
301
    /**
302
     * Remove data from the model
303
     */
304
    public function remove(string $name): void
305
    {
306
        $this->_data[$name] = null;
307
    }
308
309
    public function __get(string $name)
0 ignored issues
show
Documentation introduced by
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
310
    {
311
        return $this->get($name);
312
    }
313
314
    public function __set(string $name, $value): void
315
    {
316
        $this->set($name, $value);
317
    }
318
319
    public function __isset(string $name): bool
320
    {
321
        return $this->has($name);
322
    }
323
324
    public function __unset(string $name): void
325
    {
326
        $this->remove($name);
327
    }
328
329
    public function copyDataFrom($object, array $except = []): void
330
    {
331
        foreach ($object as $key => $value) {
332
            if (!in_array($key, $except, true)) {
333
                $this->set($key, $value);
334
            }
335
        }
336
    }
337
338
    // Iterator
339
    public function rewind(): void
340
    {
341
        reset($this->_data);
342
    }
343
344
    public function current()
345
    {
346
        return current($this->_data);
347
    }
348
349
    public function key()
350
    {
351
        return key($this->_data);
352
    }
353
354
    public function next(): void
355
    {
356
        next($this->_data);
357
    }
358
359
    public function valid(): bool
360
    {
361
        return key($this->_data) !== null;
362
    }
363
364
    // JsonSerializable
365
    public function jsonSerialize()
366
    {
367
        return $this->_data;
368
    }
369
}
370