Passed
Pull Request — Showing-Posts (#54)
by Stone
04:55 queued 02:28
created

Model::isSlugUnique()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 3
dl 0
loc 13
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Core;
4
5
use Core\Traits\StringFunctions;
6
use Exception;
7
use PDO;
8
9
/**
10
 * Class Model here we have all the generic calls to be inherited by the App\Models
11
 * using PDO connections
12
 * @package Core
13
 *
14
 * PHP version 7
15
 */
16
abstract class Model
17
{
18
    use StringFunctions;
19
    /**
20
     * @var PDO the database handeler
21
     */
22
    protected $dbh;
23
24
    /**
25
     * @var \PDOStatement|boolean the prepared sql statement
26
     */
27
    protected $stmt;
28
29
    /**
30
     * @var Container the dependancy injector
31
     */
32
    private $container;
33
34
    /**
35
     * Model constructor. prepares the database connection
36
     * @param Container $container
37
     */
38
    public function __construct(Container $container)
39
    {
40
        $this->container = $container;
41
        $this->dbh = $this->container->setPdo();
42
    }
43
44
    /*
45
     * generic PDO query constructor
46
     * ---------------------------------------------
47
     */
48
49
    /**
50
     * creating and storing the query
51
     * @param $sql string creating the sql query
52
     */
53
    protected function query($sql): void
54
    {
55
        $this->stmt = $this->dbh->prepare($sql);
56
    }
57
58
    /**
59
     * binding the parameters to the query. Need the stmt to be declared before via query()
60
     * @param $param
61
     * @param $value
62
     * @param  $type
63
     * @throws Exception error if no sql query to bind to
64
     */
65
    protected function bind($param, $value, $type = null): void
66
    {
67
        if ($this->stmt == null) {
68
            throw new Exception("No query to bind to");
69
        }
70
        if (is_null($type)) { //need a bind value, so just check it in code. that way we can just call bind(param,value)
71
            switch (true) {
72
                case is_int($value):
73
                    $type = PDO::PARAM_INT;
74
                    break;
75
                case is_bool($value):
76
                    $type = PDO::PARAM_BOOL;
77
                    break;
78
                case is_null($value):
79
                    $type = PDO::PARAM_NULL;
80
                    break;
81
                default:
82
                    $type = PDO::PARAM_STR;
83
            }
84
        }
85
        $this->stmt->bindValue($param, $value, $type);
86
    }
87
88
    /**
89
     * Execute our constructed SQL statement
90
     * @return bool
91
     * @throws Exception if the statement is empty
92
     */
93
    protected function execute()
94
    {
95
        if ($this->stmt == null) {
96
            throw new Exception("No statement to execute");
97
        }
98
        return $this->stmt->execute();
99
    }
100
101
    /**
102
     * fetches the result from an executed query
103
     * @return array
104
     */
105
    protected function fetchAll()
106
    {
107
        return $this->stmt->fetchAll();
108
    }
109
110
    /**
111
     * returns a single line from the executed query
112
     * @return mixed
113
     */
114
    protected function fetch()
115
    {
116
        return $this->stmt->fetch();
117
    }
118
119
    /*
120
     * END generic PDO query constructor
121
     * ---------------------------------------------
122
     */
123
124
    /**
125
     * correlation between the model name and the table name
126
     * if we don't have a table name, get the table that has the same name as the model will be returned (else, we do nothing !!)
127
     * Also search if the table exists, if not do a check in the views (must be v_$table)
128
     * @param string|null $table the name of the table to get, if none the get the table of the models name
129
     * @return string the table name (with an s)
130
     * @throws \ReflectionException the model doesn't exist, should never happen
131
     * @throws Exception table or view doesn't exist
132
     * @return string table or view name
133
     */
134
    protected function getTable(String $table = null): String
135
    {
136
        //If no table is passed, get the calling model name
137
        if ($table === null) {
138
            $reflect = new \ReflectionClass(get_class($this));
139
            $table = $reflect->getShortName(); //this is to only get the model name, otherwise we get the full namespace
140
            //since our models all end with Model, we should remove it.
141
            $table = $this->removeFromEnd($table, 'Model');
142
            $table = $table . 's'; //adding the s since the table should be plural. Might be some special case where the plural isn't just with an s
143
            $table = strtolower($table); //the database names are in lowercase
144
        }
145
146
        //Check if we have already passed the prefix
147
        if (!$this->startsWith($table, Config::TABLE_PREFIX)) {
148
            $table = $this->getTablePrefix($table);
149
        }
150
151
152
        //see if table exists
153
        $sql = "SHOW TABLES LIKE :table";
154
        $stmt = $this->dbh->prepare($sql);
155
        $stmt->bindValue(':table', $table, PDO::PARAM_STR);
156
        $stmt->execute();
157
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
158
159
        if ($exists) {
160
            //the table exists
161
            return $table;
162
        }
163
164
        //if we are here, then table doesn't exist, check for view
165
        $view = 'v_' . $table;
166
        $stmt->bindValue(':table', $view, PDO::PARAM_STR);
167
        $stmt->execute();
168
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
169
170
        if ($exists) {
171
            //the view exists
172
            return $view;
173
        }
174
175
        //neither table or view exists
176
        //throw an error
177
        throw new Exception("Table or view $table doesn't exist");
178
    }
179
180
    /**
181
     * This function adds the table prefix if set and returns the name
182
     * Use this if we are sure of the table name. Avoids the DB calls
183
     * @param $table string the table name
184
     * @return string
185
     */
186
    protected function getTablePrefix($table)
187
    {
188
        if (Config::TABLE_PREFIX != '') {
189
            $table = Config::TABLE_PREFIX . '_' . $table;
190
        }
191
        return $table;
192
    }
193
194
    /**
195
     * checks if the result from a PDO query has any data.
196
     * If yes then we return the array
197
     * If debugging is enabled we throw an exception on no results
198
     * or we just return an empty array
199
     * @param mixed $result the PDO result of a query
200
     * @return array the result or empty
201
     * @throws Exception if debugging is on and no result
202
     */
203
    private function returnArray($result)
204
    {
205
        if ($result) {
206
            return $result;
207
        }
208
        if (Config::DEV_ENVIRONMENT) {
209
            throw new Exception("No results in database");
210
        }
211
        return [];
212
    }
213
214
    /**
215
     * gets the entire table or view and returns the array
216
     * @param string $table the table to search in, if empty then get the table based on model name
217
     * @return array the results from database
218
     * @throws \ReflectionException
219
     */
220
    protected function getResultSet($table = null): array
221
    {
222
        $tableName = $this->getTable($table);
223
        $sql = "SELECT * FROM $tableName"; //can not pass table name as :parameter. since we already have tested if the table exists, this var should be safe.
224
        $this->query($sql);
225
        $this->execute();
226
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
227
        return $this->returnArray($result);
228
    }
229
230
    /**
231
     * gets the entire table or view and returns the array with a limit to the number of rows
232
     * @param string $table the table to search in, if empty then get the table based on model name
233
     * @param string $limit the limit of rows to return
234
     * @return array the results from database
235
     * @throws \ReflectionException
236
     */
237
    protected function getResultSetLimited($limit, $table = null): array
238
    {
239
        $tableName = $this->getTable($table);
240
        $sql = "SELECT * FROM $tableName LIMIT :limit";
241
        $this->query($sql);
242
        $this->bind(':limit', $limit);
243
        $this->execute();
244
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
245
        return $this->returnArray($result);
246
    }
247
248
    /**
249
     * get's the result of SELECT * FROM table where idtable=$id
250
     * @param int $rowId searched id
251
     * @param string $table the table to search, if blank then we get the table or view based on the model name
252
     * @return array result or empty array
253
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
254
     */
255
    protected function getRowById($rowId, $table = null)
256
    {
257
        $tableName = $this->getTable($table);
258
        $idName = 'id' . $tableName;
259
        $sql = "SELECT * FROM $tableName WHERE $idName = :rowId";
260
        $this->query($sql);
261
        $this->bind(':rowId', $rowId);
262
        $this->execute();
263
        $result = $this->stmt->fetch();
264
        return $this->returnArray($result);
265
    }
266
267
    /**
268
     * gets the row from the query SELECT * FROM table WHERE $columnName = $Value
269
     * @param string $columnName the column to search in. Does a regex check for security
270
     * @param string $value the value to search for
271
     * @param string $table the table to search, if blank then we get the table or view based on the model name
272
     * @return array the results or empty
273
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
274
     * @throws Exception if the column name consists of other characters than lower case, numbers and underscore for security
275
     */
276
    protected function getRowByColumn(String $columnName, $value, $table = null): array
277
    {
278
        $tableName = $this->getTable($table);
279
        $columnNameOk = preg_match("/^[a-z0-9_]+$/i",
280
            $columnName); //testing if column name only has lower case, numbers and underscore
281
        if (!$columnNameOk) {
282
            throw new Exception("Syntax error : Column name \"$columnName\" is not legal");
283
        }
284
        $sql = "SELECT * FROM $tableName WHERE $columnName = :value";
285
        $this->query($sql);
286
        $this->bind(':value', $value);
287
        $this->execute();
288
        $result = $this->stmt->fetch();
289
        return $this->returnArray($result);
290
    }
291
292
    /**
293
     * count the number of rows in table
294
     * @param string $table
295
     * @return mixed
296
     * @throws Exception
297
     */
298
    protected function count(string $table = null)
299
    {
300
        $table = $this->getTable($table);
301
        $sql = "SELECT COUNT(*) FROM $table";
302
        $this->query($sql);
303
        $this->execute();
304
        return $this->stmt->fetchColumn();
305
    }
306
307
    /**
308
     * get list with offset and limit from table
309
     * @param int $offset
310
     * @param int $limit
311
     * @param string|null $table
312
     * @return array
313
     * @throws \ReflectionException
314
     */
315
    protected function list(int $offset = 0, int $limit = Constant::POSTS_PER_PAGE, string $table = null)
316
    {
317
        $table = $this->getTable($table);
318
        $sql = "
319
            SELECT * FROM $table
320
            LIMIT :limit OFFSET :offset
321
        ";
322
        $this->query($sql);
323
        $this->bind(":limit", $limit);
324
        $this->bind(":offset", $offset);
325
        $this->execute();
326
        return $this->fetchAll();
327
    }
328
329
    /**
330
     * is the slug unique, used when updating the slug
331
     * @param string $slug the slug to search for
332
     * @param string $table the table to search in
333
     * @param string $slugColumn the name of the slug column
334
     * @return bool
335
     * @throws Exception
336
     */
337
    protected function isSlugUnique(string $slug, string $slugColumn, string $table = null): bool
338
    {
339
        if (!$this->isAlphaNum($slugColumn)) {
340
            throw new Exception("Invalid Column name");
341
        }
342
343
        $table = $this->getTable($table);
344
345
        $sql = "SELECT * FROM $table WHERE $slugColumn = :slug";
346
        $this->query($sql);
347
        $this->bind(':slug', $slug);
348
        $this->execute();
349
        return !$this->stmt->rowCount() > 0;
350
    }
351
352
    /**
353
     * get the ID of the row from the slug
354
     * @param string $slug the slug to search
355
     * @param string $table the table to search in
356
     * @param string $slugColumn the slug column name
357
     * @param string $idColumn the id column name
358
     * @return int the id of the row
359
     * @throws Exception
360
     */
361
    protected function getIdFromSlug(string $slug, string $idColumn, string $slugColumn, string $table = null): int
362
    {
363
        if (!$this->isAllAlphaNum([$idColumn, $slugColumn])) {
364
            throw new Exception("Invalid Column name");
365
        }
366
367
        $table = $this->getTable($table);
368
369
        $sql = "SELECT $idColumn FROM $table WHERE $slugColumn = :slug";
370
        $this->query($sql);
371
        $this->bind(":slug", $slug);
372
        $this->execute();
373
        if (!$this->stmt->rowCount() > 0) {
374
            return 0;
375
        }
376
        return $this->stmt->fetchColumn();
377
378
    }
379
380
    /**
381
     * get the slug from an Id
382
     * @param int $searchId
383
     * @param string $idColumn
384
     * @param string $slugColumn
385
     * @param string $table
386
     * @return string
387
     * @throws \ReflectionException
388
     */
389
    protected function getSlugFromId(
390
        int $searchId,
391
        string $idColumn,
392
        string $slugColumn,
393
        string $table = null
394
    ): string {
395
396
        if (!$this->isAllAlphaNum([$idColumn, $slugColumn])) {
397
            throw new Exception("Invalid Column name");
398
        }
399
        $table = $this->getTable($table);
400
401
        $sql = "SELECT $slugColumn FROM $table WHERE $idColumn = :searchId";
402
        $this->query($sql);
403
        $this->bind(":searchId", $searchId);
404
        $this->execute();
405
        if (!$this->stmt->rowCount() > 0) {
406
            return 0;
407
        }
408
        return $this->stmt->fetchColumn();
409
    }
410
411
    /**
412
     * get's the result of SELECT * FROM table where table_slug=$slug
413
     * @param string $slug the slug to look up
414
     * @param string $slugColumn the name of the slug column
415
     * @param string $table the table to search, if blank then we get the table or view based on the model name
416
     * @return array result or empty array
417
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
418
     */
419
    protected function getRowBySlug(String $slug, string $slugColumn, $table = null): array
420
    {
421
        if (!$this->isAlphaNum($slugColumn)) {
422
            throw new Exception("Invalid Column name");
423
        }
424
425
        $table = $this->getTable($table);
426
427
        $sql = "SELECT * FROM $table WHERE $slugColumn = :slug";
428
        $this->query($sql);
429
        $this->bind(':slug', $slug);
430
        $this->execute();
431
        $result = $this->stmt->fetch();
432
        return $this->returnArray($result);
433
    }
434
}