Completed
Pull Request — master (#24)
by Alexander
01:53
created

Model::save()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 20
nc 4
nop 2
dl 0
loc 27
rs 8.5806
c 0
b 0
f 0
1
<?php
2
3
namespace alkemann\h2l\traits;
4
5
use alkemann\h2l\Connections;
6
use alkemann\h2l\exceptions;
7
use alkemann\h2l\exceptions\ConfigMissing;
8
use alkemann\h2l\interfaces\Source;
9
10
/**
11
 * Class Model
12
 *
13
 * Use this for prototyping only, use a real ORM for production stud
14
 *
15
 * Depends on \alkemann\h2l\Entity trait
16
 *
17
 * @property string $pk
18
 * @property string $connection
19
 * @property array $fields
20
 * @property array $table
21
 * @property array $data
22
 * @package alkemann\h2l
23
 */
24
trait Model
25
{
26
    /**
27
     * @throws ConfigMissing
28
     */
29
    public static function db(): Source
30
    {
31
        $name = isset(static::$connection) ? static::$connection : 'default';
32
        return Connections::get($name);
33
    }
34
35
    private static function pk(): string
36
    {
37
        return isset(static::$pk) ? static::$pk : 'id';
38
    }
39
40
    public function exists(): bool
41
    {
42
        // @TODO set a "read from db" property?
43
        $pk = static::pk();
44
        return isset($this->$pk) && $this->$pk;
45
    }
46
47
    private static function table(): string
48
    {
49
        if (!isset(static::$table)) {
50
            throw new ConfigMissing(
51
                get_called_class() . ' is missing static::$table',
0 ignored issues
show
Unused Code introduced by
The call to alkemann\h2l\exceptions\...gMissing::__construct() has too many arguments starting with get_called_class() . ' is missing static::$table'. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

51
            throw /** @scrutinizer ignore-call */ new ConfigMissing(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
52
                ConfigMissing::MISSING_TABLE
53
            );
54
        }
55
        return static::$table;
0 ignored issues
show
Bug Best Practice introduced by
The expression return static::table returns the type array which is incompatible with the type-hinted return string.
Loading history...
56
    }
57
58
    /**
59
     * @TODO throw exception instead of returning null of insert failed?
60
     * @return null|static
61
     */
62
    public static function get($id, array $conditions = [], array $options = []) //: ?Model
63
    {
64
        if (empty($conditions) === false) {
65
            throw new \InvalidArgumentException("Conditions is not implemented on get");
66
        }
67
        $pk = static::pk();
68
        $conditions[$pk] = $id;
69
        $result = static::db()->one(static::table(), $conditions, $options);
70
        if ($result) {
71
            return new static($result);
0 ignored issues
show
Unused Code introduced by
The call to alkemann\h2l\traits\Model::__construct() has too many arguments starting with $result. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

71
            return /** @scrutinizer ignore-call */ new static($result);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
72
        }
73
        return null;
74
    }
75
76
    /**
77
     * Find all records matching $conditions, returns a generator
78
     *
79
     * @throws ConfigMissing
80
     */
81
    public static function find(array $conditions = [], array $options = []): \Generator
82
    {
83
        $conditions = self::filterByFields($conditions);
84
        $with = array_key_exists('with', $options) ? (array)$options['with'] : false;
85
        unset($options['with']);
86
        $result = static::db()->find(static::table(), $conditions, $options);
87
        $pk = static::pk();
88
        $gen = function () use ($result, $pk, $with) {
89
            foreach ($result as $row) {
90
                $model = new static($row);
0 ignored issues
show
Unused Code introduced by
The call to alkemann\h2l\traits\Model::__construct() has too many arguments starting with $row. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

90
                $model = /** @scrutinizer ignore-call */ new static($row);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
91
                if ($with) {
92
                    $model->with(...$with);
93
                }
94
                $id = $row[$pk];
95
                yield $id => $model;
96
            }
97
        };
98
        return $gen();
99
    }
100
101
    /**
102
     * The implementation of this method will come from the Entity trait.
103
     *
104
     * @param string[] ...$relation_names list of names of relationships
105
     * @return object Instance of class that uses this trait
106
     */
107
    abstract public function with(string ...$relation_names);
108
    abstract public function reset(): void;
109
    abstract public function data(array $data = null): array;
110
111
    /**
112
     * Find all records matching `$conditions`, returns an array with key being the pk value
113
     *
114
     * @throws ConfigMissing
115
     */
116
    public static function findAsArray(array $conditions = [], array $options = []): array
117
    {
118
        $generator = static::find($conditions, $options);
119
        return iterator_to_array($generator);
120
    }
121
122
    public static function fields(): ?array
123
    {
124
        return isset(static::$fields) ? static::$fields : null;
125
    }
126
127
    private static function filterByFields(array $data): array
128
    {
129
        $fields = static::fields();
130
        if (empty($fields) === false) {
131
            $data = array_filter(
132
                $data,
133
                function ($key) use ($fields) {
134
                    return in_array($key, $fields);
135
                },
136
                ARRAY_FILTER_USE_KEY
0 ignored issues
show
Bug introduced by
alkemann\h2l\traits\ARRAY_FILTER_USE_KEY of type integer is incompatible with the type integer expected by parameter $flag of array_filter(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

136
                /** @scrutinizer ignore-type */ ARRAY_FILTER_USE_KEY
Loading history...
137
            );
138
        }
139
        return $data;
140
    }
141
142
    /**
143
     * @throws ConfigMissing
144
     */
145
    public function save(array $data = [], array $options = []): bool
146
    {
147
        $pk = static::pk();
148
        $db = static::db();
149
        $table = static::table();
150
151
        if ($this->exists()) {
152
            $id = $this->$pk;
153
            $data = self::filterByFields($data);
154
            unset($data[$pk]);
155
            $rows = $db->update($table, [$pk => $id], $data, $options);
156
            if (!$rows) {
157
                return false;
158
            }
159
        } else {
160
            $data += $this->data;
161
            $data = self::filterByFields($data);
162
            $id = $db->insert($table, $data, $options);
163
            if (!$id) {
164
                return false;
165
            }
166
        }
167
168
        $result = $db->one($table, [$pk => $id]);
169
        $this->reset();
170
        $this->data($result);
171
        return true;
172
    }
173
174
    /**
175
     * @throws exceptions\ConfigMissing
176
     */
177
    public function delete(array $options = []): bool
178
    {
179
        $pk = static::pk();
180
        return static::db()->delete(static::table(), [$pk => $this->$pk], $options);
181
    }
182
}
183