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

Model::setConnectionResolver()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
cc 1
eloc 2
nc 1
nop 1
crap 1
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;
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 27
    }
39
40 27
    public static function unsetConnectionResolver(): void
41 18
    {
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 11
45
46
    /**
47
     * @deprecated
48
     */
49
    public static function getTable(): string
50 29
    {
51
        if (static::$_table !== null) {
52 29
            return static::$_table;
53
        }
54
55
        return strtolower(str_replace('\\', '_', static::class));
56
    }
57
58 19
    /**
59
     * @throws \Rorm\Exception
60 19
     * @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 5
    /**
68
     * @return static
69 5
     * @deprecated
70 5
     */
71 5
    public static function create(): Model
72
    {
73
        return new static();
74
    }
75
76
    /**
77 2
     * @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 2
    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 24
86
    /**
87 24
     * @return static[]
88
     */
89
    public static function findAll(): array
90
    {
91
        return static::query()->findAll();
92
    }
93
94
    public static function query(): QueryBuilder
95 3
    {
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 3
    }
98 3
99 3
    public static function customQuery(string $query, array $params = []): Query
100 1
    {
101 1
        $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...
Documentation introduced by
static::getDatabase() is of type object<PDO>, but the function expects a object<Rorm\ModelBuilder>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
102 3
        $ormQuery->setQuery($query);
103
        if (!empty($params)) {
104
            $ormQuery->setParams($params);
105
        }
106
        return $ormQuery;
107
    }
108 3
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 3
    {
111 1
        if (is_array(static::$_idColumn)) {
112 1
            $result = [];
113 1
            /** @var string[] $columns */
114 1
            $columns = static::$_idColumn;
115 1
            foreach ($columns as $key) {
116
                $result[$key] = $this->get($key);
117 3
            }
118
            return $result;
119
        } else {
120
            return $this->get(static::$_idColumn);
121
        }
122
    }
123
124 10
    public function hasId(): bool
125
    {
126 10
        if (is_array(static::$_idColumn)) {
127 3
            /** @var string[] $columns */
128 3
            $columns = static::$_idColumn;
129 3
            foreach ($columns as $key) {
130 1
                $value = $this->get($key);
131
                if (empty($value)) {
132 3
                    return false;
133 3
                }
134
            }
135 8
            return true;
136 8
        } else {
137
            $value = $this->get(static::$_idColumn);
138
            return !empty($value);
139
        }
140
    }
141
142
    /**
143
     * @throws QueryException
144
     * @throws \PDOException
145 10
     */
146
    public function save(): bool
147 10
    {
148 1
        $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...
149
        $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...
150
        $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...
151 9
152 9
        $idColumns = (array)static::$_idColumn;
153 9
        $doMerge = $this->hasId();
154
155 9
        // ignore fields
156 9
        $notSetFields = static::$_ignoreColumns;
157 7
158 7
        /**
159 9
         * Different queries are built for each driver
160
         *
161
         * IDEA: probably split into methods (saveMySQL, saveSQLite)
162 9
         */
163
        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...
164
            /**
165
             * MySQL
166
             * Instead of REPLACE INTO we use INSERT INTO ON DUPLICATE KEY UPDATE.
167
             * Because REPLACE INTO does DELETE and INSERT,
168
             * which does not play nice with TRIGGERs and FOREIGN KEY CONSTRAINTS
169 9
             */
170
            $sql = 'INSERT INTO ' . $quotedTable . ' ';
171
172
            $insertData = [];
173
            $updateData = [];
174
175
            foreach ($this->_data as $column => $value) {
176 5
                if (in_array($column, $notSetFields, true)) {
177
                    continue;
178 5
                }
179 5
180
                $quotedColumn = $quoteIdentifier($column);
181 5
                $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...
182 5
183 1
                if ($doMerge && !in_array($column, $idColumns, true)) {
184
                    $updateData[] = $quotedColumn . ' = VALUES(' . $quotedColumn . ')';
185
                }
186 5
            }
187 5
            unset($column, $value, $quotedColumn);
188
189 5
            // insert
190 2
            $sql .=
191 2
                '(' . implode(', ', array_keys($insertData)) . ')' .
192 5
                ' VALUES ' .
193 5
                '(' . implode(', ', $insertData) . ')';
194
195
            if ($doMerge && count($updateData) > 0) {
196
                // update
197 5
                $sql .= ' ON DUPLICATE KEY UPDATE ' . implode(', ', $updateData);
198 5
            }
199 5
200
            // execute (most likely throws PDOException if there is an error)
201 5
            if ($dbh->exec($sql) === false) {
202
                return false; // @codeCoverageIgnore
203 2
            }
204 2
205
            // update generated id
206
            if (static::$_autoId && !$doMerge) {
207 5
                // last insert id
208
                $this->set(static::$_idColumn, $dbh->lastInsertId());
209
            }
210
211
            return true;
212 5
        } else {
213
            /**
214 4
             * SQLite
215 4
             */
216
            if ($doMerge) {
217 5
                $sql = 'INSERT OR REPLACE INTO ' . $quotedTable . ' ';
218
            } else {
219
                $sql = 'INSERT INTO ' . $quotedTable . ' ';
220
            }
221
222 4
            // build (column) VALUES (values)
223 2
            $quotedData = [];
224 2
            foreach ($this->_data as $column => $value) {
225 3
                if (in_array($column, $notSetFields, true)) {
226
                    continue;
227
                }
228
229 4
                $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...
230 4
            }
231 4
            unset($column, $value);
232
233
            $sql .= '(' . implode(', ', array_keys($quotedData)) . ') VALUES (' . implode(', ', $quotedData) . ')';
234
235 4
            // execute (most likely throws PDOException if there is an error)
236 4
            if ($dbh->exec($sql) === false) {
237 4
                return false; // @codeCoverageIgnore
238
            }
239 4
240
            // update generated id
241
            if (static::$_autoId && !$this->hasId()) {
242 4
                // last insert id
243
                $this->set(static::$_idColumn, $dbh->lastInsertId());
244
            }
245
246
            return true;
247 4
        }
248
    }
249 3
250 3
    public function delete(): bool
251
    {
252 4
        $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...
253
        $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...
254
255
        $idColumns = (array)static::$_idColumn;
256
257
        $where = [];
258
        foreach ($idColumns as $columnName) {
259 4
            $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...
260
        }
261 4
262 4
        $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...
263
264 4
        return $dbh->exec($sql) > 0;
265 4
    }
266 2
267 2
    // data access
268
    public function getData(): array
269 4
    {
270 4
        return $this->_data;
271 4
    }
272 4
273
    public function setData(array $data): void
274 4
    {
275
        $this->_data = $data;
276 4
    }
277
278
    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...
279
    {
280
        if (array_key_exists($name, $this->_data)) {
281
            return $this->_data[$name];
282
        }
283 2
        return null;
284
    }
285 2
286
    public function set(string $name, $value): Model
287
    {
288
        $this->_data[$name] = $value;
289
        return $this;
290
    }
291 15
292
    public function has(string $name): bool
293 15
    {
294 15
        return isset($this->_data[$name]);
295
    }
296
297
    /**
298
     * Remove data from the model
299
     */
300 15
    public function remove(string $name): void
301
    {
302 15
        $this->_data[$name] = null;
303 13
    }
304
305 9
    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...
306
    {
307
        return $this->get($name);
308
    }
309
310
    public function __set(string $name, $value): void
311
    {
312
        $this->set($name, $value);
313 15
    }
314
315 15
    public function __isset(string $name): bool
316 15
    {
317
        return $this->has($name);
318
    }
319
320
    public function __unset(string $name): void
321
    {
322
        $this->remove($name);
323 2
    }
324
325 2
    public function copyDataFrom($object, array $except = []): void
326
    {
327
        foreach ($object as $key => $value) {
328
            if (!in_array($key, $except, true)) {
329
                $this->set($key, $value);
330
            }
331
        }
332
    }
333 2
334
    // Iterator
335 2
    public function rewind(): void
336 2
    {
337
        reset($this->_data);
338
    }
339
340
    public function current()
341
    {
342 12
        return current($this->_data);
343
    }
344 12
345
    public function key()
346
    {
347
        return key($this->_data);
348
    }
349
350
    public function next(): void
351 10
    {
352
        next($this->_data);
353 10
    }
354 10
355
    public function valid(): bool
356
    {
357
        return key($this->_data) !== null;
358
    }
359
360 2
    // JsonSerializable
361
    public function jsonSerialize()
362 2
    {
363
        return $this->_data;
364
    }
365
}
366