Passed
Push — master ( 3bceb6...12b03a )
by Seth
02:10
created

AbstractBinding::database()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/** Binding */
3
4
namespace Battis\SharedLogs\Database;
5
6
use Battis\SharedLogs\Exceptions\BindingException;
7
use Battis\SharedLogs\AbstractObject;
8
use PDO;
9
10
/**
11
 * A binding between and Object and a database table
12
 *
13
 * @author Seth Battis <[email protected]>
14
 * @see Object
15
 *
16
 * TODO Convert database errors into clearer API responses
17
 * TODO Add search capabilities to all()
18
 */
19
abstract class AbstractBinding extends Parameters
20
{
21
    /** @var PDO Database connector */
22
    private $database;
23
24
    /** @var string Name of database table bound to this object*/
25
    private $databaseTable;
26
27
    /** @var string The class of all objects created by this binding */
28
    private $object = AbstractObject::class;
29
30
    /**
31
     * Binding constructor
32
     *
33
     * @param PDO $database Database connector
34
     * @param string $databaseTable Name of the bound database table
35
     * @param string $type Fully-qualified class name of bound object (e.g. `AbstractObject::class` or
36
     *        `"\Battis\SharedLogs\Object"`)
37
     *
38
     * @throws BindingException If `$databases` is not an instance of PDO
39
     */
40
    public function __construct(PDO $database, $databaseTable, $type)
41
    {
42
        $this->initDatabase($database);
43
        $this->initDatabaseTable($databaseTable);
44
        $this->initObject($type);
45
    }
46
47
    /**
48
     * Initialize the database connector
49
     *
50
     * @param PDO $database Database connector
51
     *
52
     * @throws BindingException If `$database` is not an instance of PDO
53
     */
54
    private function initDatabase(PDO $database)
55
    {
56
        if ($database instanceof PDO) {
57
            $this->database = $database;
58
        } else {
59
            throw new BindingException(
60
                'Cannot create binding without datasbase connector',
61
                BindingException::MISSING_DATABASE_CONNECTOR
62
            );
63
        }
64
    }
65
66
    /**
67
     * The database connector
68
     *
69
     * @return PDO
70
     */
71
    protected function database()
72
    {
73
        return $this->database;
74
    }
75
76
    /**
77
     * Initialize the name of the bound database table
78
     *
79
     * @param string $table Name of bound table in database
80
     */
81
    private function initDatabaseTable($table)
82
    {
83
        $table = (string) $table;
84
        if (!empty($table)) {
85
            $this->databaseTable = $table;
86
        }
87
    }
88
89
    /**
90
     * The name of the bound database table
91
     *
92
     * @return string
93
     */
94
    protected function databaseTable()
95
    {
96
        return $this->databaseTable;
97
    }
98
99
    /**
100
     * Initialize the bound object class
101
     *
102
     * @param string $type Fully-qualified class name of bound object (e.g. `Object::class` or
103
     *        `"\Battis\SharedLogs\Object"`)
104
     */
105
    private function initObject($type)
106
    {
107
        $type = (string) $type;
108
        if (!empty($type)) {
109
            $this->object = $type;
110
        }
111
    }
112
113
    /**
114
     * Instantiate the bound object
115
     *
116
     * @param array ...$params
117
     * @return AbstractObject
118
     */
119
    protected function object(...$params)
120
    {
121
        return new $this->object(...$params);
122
    }
123
124
    /**
125
     * Retrieve a single object from the bound database table
126
     *
127
     * @param integer|string $id A valid numeric object ID
128
     * @param array $params (Optional) Associative array of additional request parameters
129
     *
130
     * @uses AbstractBinding::instantiateObject()
131
     *
132
     * @return AbstractObject|null `NULL` if no object is found in database
133
     */
134
    public function get($id, $params = [])
135
    {
136
        if (is_numeric($id)) {
137
            $statement = $this->database()->prepare("
138
            SELECT *
139
                FROM `" . $this->databaseTable . "`
140
                WHERE
141
                  `id` = :id
142
        ");
143
            if ($statement->execute(['id' => $id])) {
144
                return $this->instantiateObject($statement->fetch(), $params);
145
            }
146
        }
147
        return null;
148
    }
149
150
    /**
151
     * Instantiate bound object when retrieved via `get()`
152
     *
153
     * Presumably similar to the instantiation when retrieved via `all()`, but there may be some desire to, for example,
154
     * limit the depth of nested subobjects in the context of a list.
155
     *
156
     * @used-by AbstractBinding::get()
157
     *
158
     * @param array $databaseRow Bound row of database table
159
     * @param array $params Associative array of additional request parameters
160
     *
161
     * @return AbstractObject
162
     *
163
     * @see AbstractBinding::instantiateListedObject()
164
     */
165
    protected function instantiateObject($databaseRow, $params)
166
    {
167
        return $this->object($databaseRow);
168
    }
169
170
    /**
171
     * Retrieve all objects from bound database table
172
     *
173
     * @param array $params (Optional) Associative array of additional request parameters
174
     *
175
     * @uses AbstractBinding::listOrder()
176
     * @uses AbstractBinding::instantiateListedObject()
177
     *
178
     * @return AbstractObject[]
179
     */
180
    public function all($params = [])
181
    {
182
        $statement = $this->database()->query("
183
            SELECT *
184
                FROM `" . $this->databaseTable() . "`
185
            ORDER BY
186
                " . $this->listOrder() . "
187
        ");
188
        $list = [];
189
        while ($row = $statement->fetch()) {
190
            $list[] = $this->instantiateListedObject($row, $params);
191
        }
192
        return $list;
193
    }
194
195
    /**
196
     * Configure ordering of list of bound objects
197
     *
198
     * The default ordering is to sort the list by descending creation date (most recent first)
199
     *
200
     * @used-by AbstractBinding::all()
201
     *
202
     * @return string
203
     */
204
    protected function listOrder()
205
    {
206
        return '`created` DESC';
207
    }
208
209
    /**
210
     * Instantiate bound object when retrieved via `all()`
211
     *
212
     * Presumably similar to the instantiation when retrieved via `get()`, but there may be some desire to, for example,
213
     * limit the depth of nested subobjects in the context of a list.
214
     *
215
     * @used-by AbstractBinding::all()
216
     *
217
     * @param array $databaseRow Associative array of fields from database to initialize object
218
     * @param array $params Associative array of additional request parameters
219
     *
220
     * @return AbstractObject
221
     *
222
     * @see AbstractBinding::instantiateObject()
223
     */
224
    protected function instantiateListedObject($databaseRow, $params)
225
    {
226
        return $this->object($databaseRow);
227
    }
228
229
    /**
230
     * Create a new instance of the bound object and store in database
231
     *
232
     * @param array $params Associative array of fields to initialize in created Object (will potentially be passed on
233
     *        as additional request parameters to `get()`)
234
     *
235
     * @uses AbstractBinding::getCreatedObject()
236
     *
237
     * @return AbstractObject|null `NULL` if the object cannot be created in the database
238
     */
239
    public function create($params)
240
    {
241
        $statement = $this->database()->prepare("
242
            INSERT
243
              INTO `" . $this->databaseTable() . "`
244
              (`" . implode('`, `', array_keys($params)) . "`) VALUES
245
              (:" . implode(', :', array_keys($params)) . ")
246
        ");
247
        if ($statement->execute($params)) {
248
            return $this->getCreatedObject($this->database()->lastInsertId(), $params);
249
        }
250
        return null;
251
    }
252
253
    /**
254
     * Retrieve a recently created object from database
255
     *
256
     * The default implementation of this method simply passes the ID on to `get()` to retrieve and return the recently
257
     * created object. Presumably similar to `getUpdatedObject()` or `getDeletedObject()`.
258
     *
259
     * @used-by AbstractBinding::create()
260
     *
261
     * @param integer|string $id Numeric ID of recently created object in database
262
     * @param array $params Associative array of additional request parameters
263
     *
264
     * @return AbstractObject|null `NULL` if object cannot be retrieved from database
265
     *
266
     * @see AbstractBinding::getUpdatedObject()
267
     * @see AbstractBinding::getDeletedObject()
268
     */
269
    protected function getCreatedObject($id, $params)
270
    {
271
        return $this->get($id, $params);
272
    }
273
274
    /**
275
     * Update an existing object in the database
276
     *
277
     * TODO Disambiguate reasons for errors in updating -- separate "can't be found" from "found, but can't be changed"
278
     *
279
     * @param integer|string $id ID of the object to be updated
280
     * @param array $params Associative array of fields to be updated (will also be potentially passed to `get()` as
281
     *        additional request parameters
282
     *
283
     * @uses AbstractBinding::getUpdatedObject()
284
     *
285
     * @return AbstractObject|null `NULL` if the specified object cannot be found or cannot be updated as requested
286
     */
287
    public function update($id, $params)
288
    {
289
        $assignments = [];
290
        unset($params['id']);
291
        foreach (array_keys($params) as $key) {
292
            $assignments[] = "`$key` = ':$key'";
293
        }
294
        $statement = $this->database()->prepare("
295
            UPDATE `" . $this->databaseTable() . "`
296
                SET
297
                  " . implode(', ', $assignments) . "
298
                WHERE
299
                    `id` = :id
300
        ");
301
        if ($statement->execute(array_merge(['id' => $id], $params))) {
302
            return $this->getUpdatedObject($id, $params);
303
        }
304
        return null;
305
    }
306
307
    /**
308
     * Retrieve a recently updated object from database
309
     *
310
     * The default implementation of this method simply passes the ID on to `get()` to retrieve and return the recently
311
     * updated object. Presumably similar to `getCreatedObject()` and `getDeletedObject()`.
312
     *
313
     * @param integer|string $id
314
     * @param array $params
315
     * @return AbstractObject|null
316
     *
317
     * @see AbstractBinding::getCreatedObject()
318
     * @see AbstractBinding::getDeletedObject()
319
     */
320
    protected function getUpdatedObject($id, $params)
321
    {
322
        return $this->get($id, $params);
323
    }
324
325
    /**
326
     * Delete an object from the bound database
327
     *
328
     * TODO Disambiguate null responses of "can't find it" and "found it, but can't delete it"
329
     *
330
     * @param integer|string $id Numeric ID of the object to be deleted
331
     * @param array $params (Optional) Associative array of additional request parameters
332
     *
333
     * @uses AbstractBinding::getDeletedObject()
334
     *
335
     * @return AbstractObject|null `NULL` if the object is not found or cannot be deleted
336
     */
337 View Code Duplication
    public function delete($id, $params = [])
338
    {
339
        $object = $this->getDeletedObject($id, $params);
340
        if ($object instanceof AbstractObject) {
341
            $statement = $this->database()->prepare("
342
                DELETE FROM `" . $this->databaseTable() . "`
343
                    WHERE
344
                        `id` = :id
345
            ");
346
            if ($statement->execute(['id' => $id])) {
347
                return $object;
348
            }
349
        }
350
        return null;
351
    }
352
353
    /**
354
     * Retrieve a soon-to-be deleted object from database
355
     *
356
     * The default implementation of this method simply passes the ID on to `get()` to retrieve and return the
357
     * soon-to-be deleted object. Presumably similar to `getCreatedObject()` and `getUpdatedObject()`.
358
     *
359
     * @used-by AbstractBinding::delete()
360
     *
361
     * @param integer}string $id Numeric ID of the object to be retrieved
0 ignored issues
show
Documentation introduced by
The doc-type integer}string could not be parsed: Unknown type name "integer}string" at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
362
     * @param array $params Associative array of additional request parameters
363
     *
364
     * @return AbstractObject|null `NULL` if the object specified cannot be retrieved
365
     *
366
     * @see AbstractBinding::getCreatedObject()
367
     * @see AbstractBinding::getUpdatedObject()
368
     */
369
    protected function getDeletedObject($id, $params)
370
    {
371
        return $this->get($id, $params);
372
    }
373
}
374