Completed
Pull Request — master (#24)
by Alexander
02:05
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
    abstract function __construct(array $data = []);
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
27
28
    /**
29
     * @throws ConfigMissing
30
     */
31
    public static function db(): Source
32
    {
33
        $name = isset(static::$connection) ? static::$connection : 'default';
34
        return Connections::get($name);
35
    }
36
37
    private static function pk(): string
38
    {
39
        return isset(static::$pk) ? static::$pk : 'id';
40
    }
41
42
    public function exists(): bool
43
    {
44
        // @TODO set a "read from db" property?
45
        $pk = static::pk();
46
        return isset($this->$pk) && $this->$pk;
47
    }
48
49
    private static function table(): string
50
    {
51
        if (!isset(static::$table)) {
52
            throw new ConfigMissing(
53
                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

53
            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...
54
                ConfigMissing::MISSING_TABLE
55
            );
56
        }
57
        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...
58
    }
59
60
    /**
61
     * @TODO throw exception instead of returning null of insert failed?
62
     * @return null|static
63
     */
64
    public static function get($id, array $conditions = [], array $options = []) //: ?Model
65
    {
66
        if (empty($conditions) === false) {
67
            throw new \InvalidArgumentException("Conditions is not implemented on get");
68
        }
69
        $pk = static::pk();
70
        $conditions[$pk] = $id;
71
        $result = static::db()->one(static::table(), $conditions, $options);
72
        if ($result) {
73
            return new static($result);
74
        }
75
        return null;
76
    }
77
78
    /**
79
     * Find all records matching $conditions, returns a generator
80
     *
81
     * @throws ConfigMissing
82
     */
83
    public static function find(array $conditions = [], array $options = []): \Generator
84
    {
85
        $conditions = self::filterByFields($conditions);
86
        $with = array_key_exists('with', $options) ? (array)$options['with'] : false;
87
        unset($options['with']);
88
        $result = static::db()->find(static::table(), $conditions, $options);
89
        $pk = static::pk();
90
        $gen = function () use ($result, $pk, $with) {
91
            foreach ($result as $row) {
92
                $model = new static($row);
93
                if ($with) {
94
                    $model->with(...$with);
95
                }
96
                $id = $row[$pk];
97
                yield $id => $model;
98
            }
99
        };
100
        return $gen();
101
    }
102
103
    /**
104
     * The implementation of this method will come from the Entity trait.
105
     *
106
     * @param string[] ...$relation_names list of names of relationships
107
     * @return object Instance of class that uses this trait
108
     */
109
    abstract public function with(string ...$relation_names);
110
    abstract public function reset(): void;
111
    abstract public function data(array $data = null): array;
112
113
    /**
114
     * Find all records matching `$conditions`, returns an array with key being the pk value
115
     *
116
     * @throws ConfigMissing
117
     */
118
    public static function findAsArray(array $conditions = [], array $options = []): array
119
    {
120
        $generator = static::find($conditions, $options);
121
        return iterator_to_array($generator);
122
    }
123
124
    public static function fields(): ?array
125
    {
126
        return isset(static::$fields) ? static::$fields : null;
127
    }
128
129
    private static function filterByFields(array $data): array
130
    {
131
        $fields = static::fields();
132
        if (empty($fields) === false) {
133
            $data = array_filter(
134
                $data,
135
                function ($key) use ($fields) {
136
                    return in_array($key, $fields);
137
                },
138
                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

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