Passed
Pull Request — dev (#47)
by Stone
04:57 queued 02:32
created

Model::fetch()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
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
        return $this->stmt->fetchAll();
106
    }
107
108
    /**
109
     * returns a single line from the executed query
110
     * @return mixed
111
     */
112
    protected function fetch(){
113
        return $this->stmt->fetch();
114
    }
115
116
    /*
117
     * END generic PDO query constructor
118
     * ---------------------------------------------
119
     */
120
121
    /**
122
     * correlation between the model name and the table name
123
     * 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 !!)
124
     * Also search if the table exists, if not do a check in the views (must be v_$table)
125
     * @param string|null $table the name of the table to get, if none the get the table of the models name
126
     * @return string the table name (with an s)
127
     * @throws \ReflectionException the model doesn't exist, should never happen
128
     * @throws \Exception table or view doesn't exist
129
     * @return string table or view name
130
     */
131
    protected function getTable(String $table = null): String
132
    {
133
        //If no table is passed, get the calling model name
134
        if ($table === null) {
135
            $reflect = new \ReflectionClass(get_class($this));
136
            $table = $reflect->getShortName(); //this is to only get the model name, otherwise we get the full namespace
137
            //since our models all end with Model, we should remove it.
138
            $table = $this->removeFromEnd($table, 'Model');
139
            $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
140
            $table = strtolower($table); //the database names are in lowercase
141
        }
142
143
        $table = $this->getTablePrefix($table);
144
145
        //see if table exists
146
        $sql = "SHOW TABLES LIKE :table";
147
        $stmt = $this->dbh->prepare($sql);
148
        $stmt->bindValue(':table', $table, PDO::PARAM_STR);
149
        $stmt->execute();
150
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
151
152
        if ($exists) {
153
            //the table exists
154
            return $table;
155
        }
156
157
        //if we are here, then table doesn't exist, check for view
158
        $view = 'v_' . $table;
159
        $stmt->bindValue(':table', $view, PDO::PARAM_STR);
160
        $stmt->execute();
161
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
162
163
        if ($exists) {
164
            //the view exists
165
            return $view;
166
        }
167
168
        //neither table or view exists
169
        //throw an error
170
        throw new \Exception("Table or view $table doesn't exist");
171
    }
172
173
    /**
174
     * This function adds the table prefix if set and returns the name
175
     * Use this if we are sure of the table name. Avoids the DB calls
176
     * @param $table string the table name
177
     * @return string
178
     */
179
    protected function getTablePrefix($table){
180
        if(Config::TABLE_PREFIX != '')
181
        {
182
            $table = Config::TABLE_PREFIX.'_'.$table;
183
        }
184
        return $table;
185
    }
186
187
    /**
188
     * checks if the result from a PDO query has any data.
189
     * If yes then we return the array
190
     * If debugging is enabled we throw an exception on no results
191
     * or we just return an empty array
192
     * @param mixed $result the PDO result of a query
193
     * @return array the result or empty
194
     * @throws \Exception if debugging is on and no result
195
     */
196
    private function returnArray($result): array
197
    {
198
        if ($result) {
199
            return $result;
200
        }
201
        if (Config::DEV_ENVIRONMENT) {
202
            throw new \Exception("No results in database");
203
        }
204
        return [];
205
    }
206
207
    /**
208
     * gets the entire table or view and returns the array
209
     * @param string $table the table to search in, if empty then get the table based on model name
210
     * @return array the results from database
211
     * @throws \ReflectionException
212
     */
213
    protected function getResultSet($table = null): array
214
    {
215
        $tableName = $this->getTable($table);
216
        $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.
217
        $this->query($sql);
218
        $this->execute();
219
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
220
        return $this->returnArray($result);
221
    }
222
223
    /**
224
     * gets the entire table or view and returns the array with a limit to the number of rows
225
     * @param string $table the table to search in, if empty then get the table based on model name
226
     * @param string $limit the limit of rows to return
227
     * @return array the results from database
228
     * @throws \ReflectionException
229
     */
230
    protected function getResultSetLimited($limit, $table = null): array
231
    {
232
        $tableName = $this->getTable($table);
233
        $sql = "SELECT * FROM $tableName LIMIT :limit";
234
        $this->query($sql);
235
        $this->bind(':limit', $limit);
236
        $this->execute();
237
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
238
        return $this->returnArray($result);
239
    }
240
241
    /**
242
     * get's the result of SELECT * FROM table where idtable=$id
243
     * @param int $rowId searched id
244
     * @param string $table the table to search, if blank then we get the table or view based on the model name
245
     * @return array result or empty array
246
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
247
     */
248
    protected function getRowById($rowId, $table = null): array
249
    {
250
        $tableName = $this->getTable($table);
251
        $idName = 'id' . $tableName;
252
        $sql = "SELECT * FROM $tableName WHERE $idName = :rowId";
253
        $this->query($sql);
254
        $this->bind(':rowId', $rowId);
255
        $this->execute();
256
        $result = $this->stmt->fetch();
257
        return $this->returnArray($result);
258
    }
259
260
    /**
261
     * gets the row from the query SELECT * FROM table WHERE $columnName = $Value
262
     * @param string $columnName the column to search in. Does a regex check for security
263
     * @param string $value the value to search for
264
     * @param string $table the table to search, if blank then we get the table or view based on the model name
265
     * @return array the results or empty
266
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
267
     * @throws \Exception if the column name consists of other characters than lower case, numbers and underscore for security
268
     */
269
    protected function getRowByColumn(String $columnName, $value, $table = null): array
270
    {
271
        $tableName = $this->getTable($table);
272
        $columnNameOk = preg_match("/^[a-z0-9_]+$/i", $columnName); //testing if column name only has lower case, numbers and underscore
273
        if (!$columnNameOk) {
274
            throw new \Exception("Syntax error : Column name \"$columnName\" is not legal");
275
        }
276
        $sql = "SELECT * FROM $tableName WHERE $columnName = :value";
277
        $this->query($sql);
278
        $this->bind(':value', $value);
279
        $this->execute();
280
        $result = $this->stmt->fetch();
281
        return $this->returnArray($result);
282
    }
283
284
    /**
285
     * get's the result of SELECT * FROM table where table_slug=$slug
286
     * @param string $slug the slug to look up
287
     * @param string $table the table to search, if blank then we get the table or view based on the model name
288
     * @return array result or empty array
289
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
290
     */
291
    protected function getRowBySlug(String $slug, $table = null): array
292
    {
293
        $tableName = $this->getTable($table);
294
        $slugName = $tableName.'_slug';
295
        $sql = "SELECT * FROM $tableName WHERE $slugName = :slug";
296
        $this->query($sql);
297
        $this->bind(':slug', $slug);
298
        $this->execute();
299
        $result = $this->stmt->fetch();
300
        return $this->returnArray($result);
301
    }
302
}