1<?php
2
3namespace LibreNMS;
4
5abstract class Model
6{
7    protected static $table;
8    protected static $primaryKey = 'id';
9
10    public static function create(array $data)
11    {
12        $instance = new static();
13        $instance->fill($data);
14
15        return $instance;
16    }
17
18    protected function fill(array $data = [])
19    {
20        foreach ($data as $field => $value) {
21            $this->$field = $value;
22        }
23    }
24
25    /**
26     * Save Models and remove invalid Models
27     * This the sensors array should contain all the sensors of a specific class
28     * It may contain sensors from multiple tables and devices, but that isn't the primary use
29     *
30     * @param int $device_id
31     * @param array $models
32     * @param array $unique_fields fields to search for an existing entry
33     * @param array $ignored_update_fields Don't compare these field when updating
34     */
35    final public static function sync($device_id, array $models, $unique_fields = [], $ignored_update_fields = [])
36    {
37        // save and collect valid ids
38        $valid_ids = [];
39        foreach ($models as $model) {
40            /** @var $this $model */
41            if ($model->isValid()) {
42                $valid_ids[] = $model->save($unique_fields, $ignored_update_fields);
43            }
44        }
45
46        // delete invalid sensors
47        self::clean($device_id, $valid_ids);
48    }
49
50    /**
51     * Remove invalid Models.  Passing an empty array will remove all models related to $device_id
52     *
53     * @param int $device_id
54     * @param array $model_ids valid Model ids
55     */
56    protected static function clean($device_id, $model_ids)
57    {
58        $table = static::$table;
59        $key = static::$primaryKey;
60
61        $params = [$device_id];
62        $where = '`device_id`=?';
63
64        if (! empty($model_ids)) {
65            $where .= " AND `$key` NOT IN " . dbGenPlaceholders(count($model_ids));
66            $params = array_merge($params, $model_ids);
67        }
68
69        $rows = dbFetchRows("SELECT * FROM `$table` WHERE $where", $params);
70        foreach ($rows as $row) {
71            static::onDelete(static::create($row));
72        }
73        if (! empty($rows)) {
74            dbDelete($table, $where, $params);
75        }
76    }
77
78    /**
79     * Save this Model to the database.
80     *
81     * @param array $unique_fields fields to search for an existing entry
82     * @param array $ignored_update_fields Don't compare these field when updating
83     * @return int the id of this model in the database
84     */
85    final public function save($unique_fields = [], $ignored_update_fields = [])
86    {
87        $db_model = $this->fetch($unique_fields);
88        $key = static::$primaryKey;
89
90        if ($db_model) {
91            $new_model = $this->toArray(array_merge([$key], $ignored_update_fields));
92            $update = array_diff($new_model, $db_model);
93
94            if (empty($update)) {
95                static::onNoUpdate();
96            } else {
97                dbUpdate($update, static::$table, "`$key`=?", [$this->$key]);
98                static::onUpdate($this);
99            }
100        } else {
101            $new_model = $this->toArray([$key]);
102            $this->$key = dbInsert($new_model, static::$table);
103            if ($this->$key !== null) {
104                static::onCreate($this);
105            }
106        }
107
108        return $this->$key;
109    }
110
111    /**
112     * Fetch the sensor from the database.
113     * If it doesn't exist, returns null.
114     *
115     * @param array $unique_fields fields to search for an existing entry
116     * @return array|null
117     */
118    protected function fetch($unique_fields = [])
119    {
120        $table = static::$table;
121        $key = static::$primaryKey;
122
123        if (isset($this->id)) {
124            return dbFetchRow(
125                "SELECT `$table` FROM ? WHERE `$key`=?",
126                [$this->$key]
127            );
128        }
129
130        $where = [];
131        $params = [];
132        foreach ($unique_fields as $field) {
133            if (isset($this->$field)) {
134                $where[] = " $field=?";
135                $params[] = $this->$field;
136            }
137        }
138
139        if (empty($params)) {
140            return null;
141        }
142
143        $row = dbFetchRow(
144            "SELECT * FROM `$table` WHERE " . implode(' AND', $where),
145            $params
146        );
147
148        $this->$key = $row[$key];
149
150        return $row;
151    }
152
153    /**
154     * Convert this Model to an array with fields that match the database
155     *
156     * @param array $exclude Exclude the listed fields
157     * @return array
158     */
159    abstract public function toArray($exclude = []);
160
161    /**
162     * Returns if this model passes validation and should be saved to the database
163     *
164     * @return bool
165     */
166    abstract public function isValid();
167
168    /**
169     * @param static $model
170     */
171    public static function onDelete($model)
172    {
173        if (\App::runningInConsole()) {
174            echo '-';
175        }
176    }
177
178    /**
179     * @param static $model
180     */
181    public static function onCreate($model)
182    {
183        if (\App::runningInConsole()) {
184            echo '+';
185        }
186    }
187
188    /**
189     * @param static $model
190     */
191    public static function onUpdate($model)
192    {
193        if (\App::runningInConsole()) {
194            echo 'U';
195        }
196    }
197
198    public static function onNoUpdate()
199    {
200        if (\App::runningInConsole()) {
201            echo '.';
202        }
203    }
204}
205