Completed
Push — master ( 72aa74...afd9e7 )
by Todd
02:20
created

Model::getDb()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 3
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2017 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Db;
9
10
use Garden\Schema\Schema;
11
12
class Model {
13
    use Utils\FetchModeTrait { setFetchMode as private; }
14
15
    const DEFAULT_LIMIT = 30;
16
17
    /**
18
     * @var string The name of the table.
19
     */
20
    private $name;
21
22
    /**
23
     * @var Db
24
     */
25
    private $db;
26
27
    /**
28
     * @var array
29
     */
30
    private $primaryKey;
31
32
    /**
33
     * @var Schema
34
     */
35
    private $schema;
36
37
    /**
38
     * @var int
39
     */
40
    private $defaultLimit = Model::DEFAULT_LIMIT;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
41
42
    /**
43
     * @var string[]
44
     */
45
    private $defaultOrder = [];
46
47 16
    public function __construct($name, Db $db, $rowType = null) {
48 16
        $this->name = $name;
49 16
        $this->db = $db;
50
51 16
        $fetchMode = $rowType !== null ? $rowType : $db->getFetchMode();
52 16
        if (!empty($fetchMode)) {
53 16
            $this->setFetchMode(...(array)$fetchMode);
54
        }
55 16
    }
56
57
    /**
58
     * Get the name.
59
     *
60
     * @return string Returns the name.
61
     */
62 1
    public function getName() {
63 1
        return $this->name;
64
    }
65
66
    /**
67
     * Get the primaryKey.
68
     *
69
     * @return array Returns the primaryKey.
70
     */
71 1
    public function getPrimaryKey() {
72 1
        if ($this->primaryKey === null) {
73 1
            $schema = $this->getSchema();
74
75 1
            $pk = [];
76 1
            foreach ($schema->getSchemaArray()['properties'] as $column => $property) {
77 1
                if (!empty($property['primary'])) {
78 1
                    $pk[] = $column;
79
                }
80
            }
81 1
            $this->primaryKey = $pk;
82
        }
83 1
        return $this->primaryKey;
84
    }
85
86
    /**
87
     * Set the primaryKey.
88
     *
89
     * @param string ...$primaryKey The names of the columns in the primary key.
90
     * @return $this
91
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $primaryKey not be string[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
92
    protected function setPrimaryKey(...$primaryKey) {
93
        $this->primaryKey = $primaryKey;
94
        return $this;
95
    }
96
97
    /**
98
     * Get the db.
99
     *
100
     * @return Db Returns the db.
101
     */
102 1
    public function getDb() {
103 1
        return $this->db;
104
    }
105
106
    /**
107
     * Set the db.
108
     *
109
     * @param Db $db
110
     * @return $this
111
     */
112
    public function setDb($db) {
113
        $this->db = $db;
114
        return $this;
115
    }
116
117
    /**
118
     * Map primary key values to the primary key name.
119
     *
120
     * @param mixed $id An ID value or an array of ID values. If an array is passed and the model has a mult-column
121
     * primary key then all of the values must be in order.
122
     * @return array Returns an associative array mapping column names to values.
123
     */
124 1
    protected function mapID($id) {
125 1
        $idArray = (array)$id;
126
127 1
        $result = [];
128 1
        foreach ($this->getPrimaryKey() as $i => $column) {
129 1
            if (isset($idArray[$i])) {
130 1
                $result[$column] = $idArray[$i];
131
            } elseif (isset($idArray[$column])) {
132
                $result[$column] = $idArray[$column];
133
            } else {
134 1
                $result[$column] = null;
135
            }
136
        }
137
138 1
        return $result;
139
    }
140
141
    /**
142
     * Gets the row schema for this model.
143
     *
144
     * @return Schema Returns a schema.
145
     */
146 1
    final public function getSchema() {
147 1
        if ($this->schema === null) {
148 1
            $this->schema = $this->fetchSchema();
149
        }
150 1
        return $this->schema;
151
    }
152
153
    /**
154
     * Fetch the row schema from the database meta info.
155
     *
156
     * This method works fine as-is, but can also be overridden to provide more specific schema information for the model.
157
     * This method is called only once for the object and then is cached in a property so you don't need to implement
158
     * caching of your own.
159
     *
160
     * If you are going to override this method we recommend you still call the parent method and add its result to your schema.
161
     * Here is an example:
162
     *
163
     * ```php
164
     * protected function fetchSchema() {
165
     *     $schema = Schema::parse([
166
     *         'body:s', // make the column required even if it isn't in the db.
167
     *         'attributes:o?' // accept an object instead of string
168
     *     ]);
169
     *
170
     *     $dbSchema = parent::fetchSchema();
171
     *     $schema->add($dbSchema, true);
172
     *
173
     *     return $schema;
174
     * }
175
     * ```
176
     *
177
     * @return Schema Returns the row schema.
178
     */
179 1
    protected function fetchSchema() {
180 1
        $columns = $this->getDb()->fetchColumnDefs($this->name);
181 1
        if ($columns === null) {
182
            throw new \InvalidArgumentException("Cannot fetch schema foor {$this->name}.");
183
        }
184
185
        $schema = [
186 1
            'type' => 'object',
187 1
            'dbtype' => 'table',
188 1
            'properties' => $columns
189
        ];
190
191 1
        $required = $this->requiredFields($columns);
192 1
        if (!empty($required)) {
193 1
            $schema['required'] = $required;
194
        }
195
196 1
        return new Schema($schema);
197
    }
198
199
    /**
200
     * Figure out the schema required fields from a list of columns.
201
     *
202
     * A column is required if it meets all of the following criteria.
203
     *
204
     * - The column does not have an auto increment.
205
     * - The column does not have a default value.
206
     * - The column does not allow null.
207
     *
208
     * @param array $columns An array of column schemas.
209
     */
210 1
    private function requiredFields(array $columns) {
211 1
        $required = [];
212
213 1
        foreach ($columns as $name => $column) {
214 1
            if (empty($column['autoIncrement']) && !isset($column['default']) && empty($column['allowNull'])) {
215 1
                $required[] = $name;
216
            }
217
        }
218
219 1
        return $required;
220
    }
221
222
    /**
223
     * Query the model.
224
     *
225
     * @param array $where A where clause to filter the data.
226
     * @return DatasetInterface
227
     */
228 16
    public function get(array $where) {
229
        $options = [
230 16
            Db::OPTION_FETCH_MODE => $this->getFetchArgs(),
231 16
            'rowCallback' => [$this, 'unserialize']
232
        ];
233
234 16
        $qry = new TableQuery($this->name, $where, $this->db, $options);
235 16
        $qry->setLimit($this->getDefaultLimit())
236 16
            ->setOrder(...$this->getDefaultOrder());
237
238 16
        return $qry;
239
    }
240
241
    /**
242
     * Query the database directly.
243
     *
244
     * @param array $where A where clause to filter the data.
245
     * @param array $options Options to pass to the database. See {@link Db::get()}.
246
     * @return \PDOStatement Returns a statement from the query.
247
     */
248 11
    public function query(array $where, array $options = []) {
249
        $options += [
250 11
            'order' => $this->getDefaultOrder(),
251 11
            'limit' => $this->getDefaultLimit(),
252 11
            Db::OPTION_FETCH_MODE => $this->getFetchArgs()
253
        ];
254
255 11
        $stmt = $this->db->get($this->name, $where, $options);
256 11
        return $stmt;
257
    }
258
259
    /**
260
     * @param mixed $id A primary key value for the model.
261
     * @return mixed|null
262
     */
263 1
    public function getID($id) {
264 1
        $r = $this->get($this->mapID($id));
265 1
        return $r->firstRow();
266
    }
267
268 1
    public function insert($row, array $options = []) {
269 1
        $valid = $this->validate($row, false);
270 1
        $serialized = $this->serialize($valid);
271
272 1
        $r = $this->db->insert($this->name, $serialized, $options);
273 1
        return $r;
274
    }
275
276
    public function update(array $set, array $where, array $options = []) {
277
        $valid = $this->validate($set, true);
278
        $serialized = $this->serialize($valid);
279
280
        $r = $this->db->update($this->name, $serialized, $where, $options);
281
        return $r;
282
    }
283
284
    public function updateID($id, $set) {
285
        $r = $this->update($set, $this->mapID($id));
286
        return $r;
287
    }
288
289
    /**
290
     * Validate a row of data.
291
     *
292
     * @param array|\ArrayAccess $row The row to validate.
293
     * @param bool $sparse Whether or not the validation should be sparse (during update).
294
     * @return array Returns valid data.
295
     */
296 1
    public function validate($row, $sparse = false) {
297 1
        $schema = $this->getSchema();
298 1
        $valid = $schema->validate($row, $sparse);
299
300 1
        return $valid;
301
    }
302
303
    /**
304
     * Serialize a row of data into a format that can be native to the database.
305
     *
306
     * This method should always take an array of data, even if your model is meant to use objects of some sort. This is
307
     * possible because the row that gets passed into this method is the output of {@link validate()}.
308
     *
309
     * @param array $row The row to serialize.
310
     * @return array Returns a row of serialized data.
311
     */
312 1
    public function serialize(array $row) {
313 1
        return $row;
314
    }
315
316
    /**
317
     * Unserialize a row from the database and make it ready for use by the user of this model.
318
     *
319
     * The base model doesn't do anything in this method which is intentional for speed.
320
     *
321
     * @param mixed $row
322
     * @return mixed
323
     */
324 15
    public function unserialize($row) {
325 15
        return $row;
326
    }
327
328
    /**
329
     * Get the defaultLimit.
330
     *
331
     * @return int Returns the defaultLimit.
332
     */
333 16
    public function getDefaultLimit() {
334 16
        return $this->defaultLimit;
335
    }
336
337
    /**
338
     * Set the defaultLimit.
339
     *
340
     * @param int $defaultLimit
341
     * @return $this
342
     */
343 1
    public function setDefaultLimit($defaultLimit) {
344 1
        $this->defaultLimit = $defaultLimit;
345 1
        return $this;
346
    }
347
348
    /**
349
     * Get the default sort order.
350
     *
351
     * The default sort order will be passed to all queries, but can be overridden in the {@link DatasetInterface}.
352
     *
353
     * @return string[] Returns an array of column names, optionally prefixed with "-" to denote descending order.
354
     */
355 16
    public function getDefaultOrder() {
356 16
        return $this->defaultOrder;
357
    }
358
359
    /**
360
     * Set the default sort order.
361
     *
362
     * The default sort order will be passed to all queries, but can be overridden in the {@link DatasetInterface}.
363
     *
364
     * @param string ...$columns The column names to sort by, optionally prefixed with "-" to denote descending order.
365
     * @return $this
366
     */
0 ignored issues
show
Documentation introduced by
Should the type for parameter $columns not be string[]?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
367 1
    public function setDefaultOrder(...$columns) {
368 1
        $this->defaultOrder = $columns;
369 1
        return $this;
370
    }
371
}
372