Passed
Pull Request — master (#44)
by Stone
03:09 queued 23s
created

Model   A

Complexity

Total Complexity 24

Size/Duplication

Total Lines 256
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 91
dl 0
loc 256
rs 10
c 0
b 0
f 0
wmc 24

11 Methods

Rating   Name   Duplication   Size   Complexity  
A query() 0 3 1
A bind() 0 21 6
A execute() 0 6 2
A __construct() 0 4 1
A getResultSet() 0 8 1
A getTable() 0 41 5
A getResultSetLimited() 0 9 1
A getRowBySlug() 0 10 1
A returnArray() 0 9 3
A getRowById() 0 10 1
A getRowByColumn() 0 13 2
1
<?php
2
3
namespace Core;
4
5
use PDO;
6
7
/**
8
 * Class Model here we have all the generic calls to be inherited by the App\Models
9
 * using PDO connections
10
 * @package Core
11
 *
12
 * PHP version 7
13
 */
14
abstract class Model
15
{
16
    /**
17
     * @var null|PDO the database handeler
18
     */
19
    protected $dbh;
20
21
    /**
22
     * @var \PDOStatement the prepared sql statement
23
     */
24
    protected $stmt;
25
26
    /**
27
     * @var Container the dependancy injector
28
     */
29
    private $container;
30
31
    /**
32
     * Model constructor. prepares the database connection
33
     * @param Container $container
34
     */
35
    public function __construct(Container $container)
36
    {
37
        $this->container = $container;
38
        $this->dbh = $this->container->setPdo();
39
    }
40
41
    /*
42
     * generic PDO query constructor
43
     * ---------------------------------------------
44
     */
45
46
    /**
47
     * creating and storing the query
48
     * @param $sql string creating the sql query
49
     */
50
    protected function query($sql): void
51
    {
52
        $this->stmt = $this->dbh->prepare($sql);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dbh->prepare($sql) can also be of type boolean. However, the property $stmt is declared as type PDOStatement. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
Bug introduced by
The method prepare() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

52
        /** @scrutinizer ignore-call */ 
53
        $this->stmt = $this->dbh->prepare($sql);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
53
    }
54
55
    /**
56
     * binding the parameters to the query. Need the stmt to be declared before via query()
57
     * @param $param
58
     * @param $value
59
     * @param  $type
60
     * @throws \Exception error if no sql query to bind to
61
     */
62
    protected function bind($param, $value, $type = null): void
63
    {
64
        if ($this->stmt == null) {
65
            throw new \Exception("No query to bind to");
66
        }
67
        if (is_null($type)) { //need a bind value, so just check it in code. that way we can just call bind(param,value)
68
            switch (true) {
69
                case is_int($value):
70
                    $type = PDO::PARAM_INT;
71
                    break;
72
                case is_bool($value):
73
                    $type = PDO::PARAM_BOOL;
74
                    break;
75
                case is_null($value):
76
                    $type = PDO::PARAM_NULL;
77
                    break;
78
                default:
79
                    $type = PDO::PARAM_STR;
80
            }
81
        }
82
        $this->stmt->bindValue($param, $value, $type);
83
    }
84
85
    /**
86
     * Execute our constructed SQL statement
87
     * @return bool
88
     * @throws \Exception if the statement is empty
89
     */
90
    protected function execute()
91
    {
92
        if ($this->stmt == null) {
93
            throw new \Exception("No statement to execute");
94
        }
95
        return $this->stmt->execute();
96
    }
97
98
    /*
99
     * END generic PDO query constructor
100
     * ---------------------------------------------
101
     */
102
103
    /**
104
     * correlation between the model name and the table name
105
     * 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 !!)
106
     * Also search if the table exists, if not do a check in the views (must be v_$table)
107
     * @param string|null $table the name of the table to get, if none the get the table of the models name
108
     * @return string the table name (with an s)
109
     * @throws \ReflectionException the model doesn't exist, should never happen
110
     * @throws \Exception table or view doesn't exist
111
     * @return string table or view name
112
     */
113
    private function getTable(string $table = null): string
114
    {
115
        //If no table is passed, get the calling model name
116
        if ($table === null) {
117
            $reflect = new \ReflectionClass(get_class($this));
118
            $table = $reflect->getShortName(); //this is to only get the model name, otherwise we get the full namespace
119
            $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
120
            $table = strtolower($table); //the database names are in lowercase
121
        }
122
123
        if(Config::TABLE_PREFIX != '')
1 ignored issue
show
introduced by
The condition Core\Config::TABLE_PREFIX != '' is always false.
Loading history...
124
        {
125
            $table = Config::TABLE_PREFIX.'_'.$table;
126
        }
127
128
        //see if table exists
129
        $sql = "SHOW TABLES LIKE :table";
130
        $stmt = $this->dbh->prepare($sql);
131
        $stmt->bindValue(':table', $table, PDO::PARAM_STR);
132
        $stmt->execute();
133
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
134
135
        if ($exists) {
136
            //the table exists
137
            return $table;
138
        }
139
140
        //if we are here, then table doesn't exist, check for view
141
        $view = 'v_' . $table;
142
        $stmt->bindValue(':table', $view, PDO::PARAM_STR);
143
        $stmt->execute();
144
        $exists = $stmt->rowCount() > 0; //will return 1 if table exists or 0 if non existant
145
146
        if ($exists) {
147
            //the view exists
148
            return $view;
149
        }
150
151
        //neither table or view exists
152
        //throw an error
153
        throw new \Exception("Table or view $table doesn't exist");
154
    }
155
156
    /**
157
     * checks if the result from a PDO query has any data.
158
     * If yes then we return the array
159
     * If debugging is enabled we throw an exception on no results
160
     * or we just return an empty array
161
     * @param mixed $result the PDO result of a query
162
     * @return array the result or empty
163
     * @throws \Exception if debugging is on and no result
164
     */
165
    private function returnArray($result): array
166
    {
167
        if ($result) {
168
            return $result;
169
        }
170
        if (Config::DEV_ENVIRONMENT) {
171
            throw new \Exception("No results in database");
172
        }
173
        return [];
174
    }
175
176
    /**
177
     * gets the entire table or view and returns the array
178
     * @param string $table the table to search in, if empty then get the table based on model name
179
     * @return array the results from database
180
     * @throws \ReflectionException
181
     */
182
    protected function getResultSet($table = ''): array
183
    {
184
        $tableName = $this->getTable($table);
185
        $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.
186
        $this->query($sql);
187
        $this->execute();
188
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
189
        return $this->returnArray($result);
190
    }
191
192
    /**
193
     * gets the entire table or view and returns the array with a limit to the number of rows
194
     * @param string $table the table to search in, if empty then get the table based on model name
195
     * @param string $limit the limit of rows to return
196
     * @return array the results from database
197
     * @throws \ReflectionException
198
     */
199
    protected function getResultSetLimited($limit, $table = ''): array
200
    {
201
        $tableName = $this->getTable($table);
202
        $sql = "SELECT * FROM $tableName LIMIT :limit";
203
        $this->query($sql);
204
        $this->bind(':limit', $limit);
205
        $this->execute();
206
        $result = $this->stmt->fetchAll(); //returns an array or false if no results
207
        return $this->returnArray($result);
208
    }
209
210
    /**
211
     * get's the result of SELECT * FROM table where idtable=$id
212
     * @param int $rowId searched id
213
     * @param string $table the table to search, if blank then we get the table or view based on the model name
214
     * @return array result or empty array
215
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
216
     */
217
    protected function getRowById($rowId, $table = ''): array
218
    {
219
        $tableName = $this->getTable($table);
220
        $idName = 'id' . $tableName;
221
        $sql = "SELECT * FROM $tableName WHERE $idName = :rowId";
222
        $this->query($sql);
223
        $this->bind(':rowId', $rowId);
224
        $this->execute();
225
        $result = $this->stmt->fetch();
226
        return $this->returnArray($result);
227
    }
228
229
    /**
230
     * gets the row from the query SELECT * FROM table WHERE $columnName = $Value
231
     * @param string $columnName the column to search in. Does a regex check for security
232
     * @param string $value the value to search for
233
     * @param string $table the table to search, if blank then we get the table or view based on the model name
234
     * @return array the results or empty
235
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
236
     * @throws \Exception if the column name consists of other characters than lower case, numbers and underscore for security
237
     */
238
    protected function getRowByColumn($columnName, $value, $table = ''): array
239
    {
240
        $tableName = $this->getTable($table);
241
        $columnNameOk = preg_match("/^[a-z0-9_]+$/i", $columnName); //testing if column name only has lower case, numbers and underscore
242
        if (!$columnNameOk) {
243
            throw new \Exception("Syntax error : Column name \"$columnName\" is not legal");
244
        }
245
        $sql = "SELECT * FROM $tableName WHERE $columnName = :value";
246
        $this->query($sql);
247
        $this->bind(':value', $value);
248
        $this->execute();
249
        $result = $this->stmt->fetch();
250
        return $this->returnArray($result);
251
    }
252
253
    /**
254
     * get's the result of SELECT * FROM table where table_slug=$slug
255
     * @param string $slug the slug to look up
256
     * @param string $table the table to search, if blank then we get the table or view based on the model name
257
     * @return array result or empty array
258
     * @throws \ReflectionException (probably not, but will throw an exception if debugging is on and no results)
259
     */
260
    protected function getRowBySlug(string $slug, $table = ''): array
261
    {
262
        $tableName = $this->getTable($table);
263
        $slugName = $tableName.'_slug';
264
        $sql = "SELECT * FROM $tableName WHERE $slugName = :slug";
265
        $this->query($sql);
266
        $this->bind(':slug', $slug);
267
        $this->execute();
268
        $result = $this->stmt->fetch();
269
        return $this->returnArray($result);
270
    }
271
}