Completed
Pull Request — master (#24)
by Alexander
01:59
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 public function __construct(array $data = []);
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