Completed
Push — master ( b92126...853c09 )
by Christopher
37:17 queued 22:19
created

Potato::getOne()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 34
Code Lines 22

Duplication

Lines 17
Ratio 50 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
c 3
b 1
f 0
dl 17
loc 34
rs 6.7273
cc 7
eloc 22
nc 7
nop 1
1
<?php
2
3
namespace Ganga\Potato;
4
5
use ReflectionClass;
6
7
/**
8
 * Class that defines Potato ORM
9
 * Will be extended by Model Classes
10
 */
11
class Potato
12
{
13
    protected $tableName;
14
    protected static $id = null;
15
    protected static $table;
16
17
    /**
18
     * Constructor
19
     * TableName is set when the class is extended
20
     */
21
    public function __construct()
22
    {
23
        /**
24
         * Tests if the Model has defined a table name
25
         * if not it assigns it to the name of the class
26
         */
27
        if (!$this->tableName)
28
        {
29
            $ref = new ReflectionClass($this);
30
            $tableName = strtolower($ref->getShortName()).'s';
31
        }
32
33
        // Test if table exists else throw an exception
34
        if (!self::tableExists($tableName)) {
35
            throw new PotatoException("Table $tableName does not exist.");
36
        } else {
37
            $this->tableName = $tableName;
0 ignored issues
show
Bug introduced by
The variable $tableName does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
38
            self::$table = $tableName;
39
        }
40
    }
41
42
    /**
43
     * Checks if a given table exists in the database
44
     * @param  string $tableName name of the table to be checked
45
     * @return bool           true if table is found, false otherwise
46
     */
47
    public static function tableExists($tableName)
48
    {
49
        switch (Connection::$dbType) {
50
            case 'sqlite':
51
                $query = "SELECT name FROM sqlite_master WHERE type='table' AND name='$tableName';";
52
                $exists = Connection::db()->querySingle($query, true);
53
                break;
54
            case 'mysql':
55
                $query = "Select COUNT(*) FROm information_schema.TABLES WHERE TABLE_NAME = '$tableName';"; 
56
                if (!$exists = Connection::db()->query($query)) {
57
                    echo 'error'.Connection::db()->error;
58
                } else {
59
                    $exists = $exists->num_rows;
60
                }
61
                break;
62
        }
63
        
64
65
        if (empty($exists) || $exists == 0) {
66
            return false;
67
        } else {
68
            return true;
69
        }
70
    }
71
72
    /**
73
     * Get the name of the current table
74
     * @return String table name
75
     */
76
    public function getTableName()
77
    {
78
        return $this->tableName;
79
    }
80
81
    /**
82
     * Getting table names from a class
83
     * This is important when no instance of the class is defined
84
     * Like User::getAll()
85
     * @return string table name
86
     */
87
    public static function getTableNameFromClass()
88
    {
89
        $instance = new static();
90
        $ref = new ReflectionClass($instance);
91
        $name = $ref->getShortName();
92
        $table = strtolower($name) . 's';
93
94
        if (!self::tableExists($table)) {
95
            throw new PotatoException("Table $table does not exist.");
96
        }
97
98
        return $table;
99
    }
100
101
    /**
102
     * Get all records from the record table
103
     * @return array associative array of the records received from the database
104
     */
105
    public static function getAll()
106
    {
107
        $table = self::getTableNameFromClass();
108
109
        $query = "SELECT * FROM $table";
110
111
        $res = [];
112
        
113 View Code Duplication
        switch (Connection::$dbType) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
114
            case 'sqlite':
115
                $results = Connection::db()->query($query);
116
                while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
117
                    array_push($res, $row);
118
                }
119
                break;
120
            case 'mysql':
121
                if ($results = Connection::db()->query($query)) {
122
                    while($row = $results->fetch_assoc()){
123
                        array_push($res, $row);
124
                    }
125
                } else {
126
                    die('There was an error running the query [' . Connection::db()->error . ']');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getAll() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
127
                }
128
                break;
129
        }
130
131
        return $res;
132
    }
133
134
    /**
135
     * Get one record from the table based on the id provided
136
     * @param  integer $id id of the record to be retrieved
137
     * @return array     associative array of the record retrieved
138
     */
139
    public static function getOne($id)
140
    {
141
        $table = self::getTableNameFromClass();
142
143
        $query = "SELECT * FROM $table WHERE id = $id";
144
145
        $results = Connection::db()->query($query);
0 ignored issues
show
Unused Code introduced by
$results is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
146
147
        $res = [];
148
149 View Code Duplication
       switch (Connection::$dbType) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
150
            case 'sqlite':
151
                $results = Connection::db()->query($query);
152
                while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
153
                    array_push($res, $row);
154
                }
155
                break;
156
            case 'mysql':
157
                if ($results = Connection::db()->query($query)) {
158
                    while($row = $results->fetch_assoc()){
159
                        array_push($res, $row);
160
                    }
161
                } else {
162
                    die('There was an error running the query [' . Connection::db()->error . ']');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getOne() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
163
                }
164
                break;
165
        }
166
167
        if (!empty($res)) {
168
            return $res;
169
        } else {
170
            throw new PotatoException('There is no user with id '. $id);
171
        }
172
    }
173
174
    /**
175
     * Get table column names
176
     * @return array array containing the table column names
177
     */
178
    public function getColumnNames()
179
    {
180
        $columns = [];
181
182
        switch (Connection::$dbType) {
183
            case 'sqlite':
184
                $query = Connection::db()->query("PRAGMA table_info($this->tableName);");
185
                while ($row = $query->fetchArray(SQLITE3_ASSOC)) {
186
                    array_push($columns, $row['name']);
187
                }
188
                break;
189
            case 'mysql':
190
                $query = "SELECT `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA`='".Connection::$dbName."' AND `TABLE_NAME`='$this->tableName';";
191
                if ($results = Connection::db()->query($query)) {
192
                    while($row = $results->fetch_assoc()){
193
                        array_push($columns, $row['COLUMN_NAME']);
194
                    }
195
                } else {
196
                    die('There was an error running the query [' . Connection::db()->error . ']');
0 ignored issues
show
Coding Style Compatibility introduced by
The method getColumnNames() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
197
                }
198
                break;
199
        }
200
        return $columns;
201
    }
202
203
    /**
204
     * Save either a new or existing record
205
     * @return integer 1 is successful, 0 if false
206
     */
207
    public function save()
208
    {
209
        $columnNames = $this->getColumnNames();
210
        $availableColumnNames = [];
211
        $availableColumnValues = [];
212
213
        foreach ($columnNames as $columnName)
0 ignored issues
show
Bug introduced by
The expression $columnNames of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
214
        {
215
            if (isset($this->{$columnName})) {
216
                array_push($availableColumnNames, $columnName);
217
                array_push($availableColumnValues, $this->{$columnName});
218
            }
219
        }
220
221
        if (!self::$id) {
222
            // new record so we insert
223
            $query = "INSERT INTO $this->tableName (".implode(", ", $availableColumnNames).") VALUES('".implode("', '", $availableColumnValues)."');";
224
        } else {
225
            // existing record, se we update
226
            $query = "UPDATE $this->tableName SET ";
227
228
            for ($i = 0; $i < count($availableColumnNames); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
229
                $prop = $availableColumnNames[$i];
230
                $query .= " $prop = '". $this->{$prop}."'";
231
232
                if ($i != count($availableColumnNames) - 1) {
233
                    $query .= ",";
234
                }
235
            }
236
237
            $query .= " WHERE id = ".self::$id;
238
        }
239
240
        $result = Connection::db()->query($query);
241
242
        if (!$result) {
243
            return 0;
244
        } else {
245
            return 1;
246
        }
247
    }
248
249
    /**
250
     * Find a record by id and change static id param
251
     * @param  int $id id of the record to be found
252
     * @return ClassInstance     an instance of the TableClass so that properties can be assigned automatically
253
     */
254
    public static function find($id)
255
    {
256
        self::getOne($id);
257
        self::$id = $id;
258
        return new static();
259
    }
260
261
    /**
262
     * Delete a record by id from the table in context
263
     * @param  id $id the id of the record to be deleted
264
     * @return [type]     [description]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" 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...
265
     */
266
    public static function destroy($id)
267
    {
268
        $table = self::getTableNameFromClass();
269
270
        self::getOne($id);
271
272
        $query = "DELETE FROM $table WHERE id = ".$id;
273
274
        $result = Connection::db()->query($query);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
275
    }
276
}
277