Passed
Push — RefactoringPosts ( 4f49c0...c0b061 )
by Stone
02:54
created

Model::count()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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