Potato::save()   B
last analyzed

Complexity

Conditions 5
Paths 68

Size

Total Lines 25
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 3
Metric Value
c 4
b 0
f 3
dl 0
loc 25
rs 8.439
cc 5
eloc 18
nc 68
nop 0
1
<?php
2
3
/**
4
 * @package A simple ORM that performs basic CRUD operations
5
 * @author Surajudeen AKANDE <[email protected]>
6
 * @license MIT <https://opensource.org/licenses/MIT>
7
 * @link http://www.github.com/andela-sakande
8
 *
9
 */
10
11
namespace Sirolad;
12
13
use PDO;
14
use PDOException;
15
use Sirolad\DB\DBConnect;
16
use Sirolad\Libraries\Formatter;
17
use Sirolad\Libraries\TableMapper;
18
use Sirolad\Interfaces\PotatoInterface;
19
use Sirolad\Exceptions\EmptyTableException;
20
use Sirolad\Exceptions\RecordNotFoundException;
21
use Sirolad\Exceptions\TableDoesNotExistException;
22
23
/**
24
 * Potato is the main class which is not to be instantiated.
25
 * */
26
class Potato implements PotatoInterface
27
{
28
    /**
29
     * @var array Array for holding properties set with magic method __set()
30
     */
31
    protected $record = [];
32
33
    /**
34
     * Set property dynamically
35
     *
36
     * @param string $field Property set dynamically
37
     * @param string $value Value of property set dynamically
38
     */
39
    public function __set($field, $value)
40
    {
41
        $this->record[$field] = $value;
42
    }
43
44
    /**
45
     * @param string connection to class name
46
     * @return string table name of Called class
47
     */
48
    public function tableName()
49
    {
50
        return TableMapper::getClassName(get_called_class());
51
    }
52
53
    /**
54
     * Provide a read access to protected $record array
55
     *
56
     * @return array $record Array of variables set dynamically with method __set()
57
     */
58
    public function getRecord()
59
    {
60
        return $this->record;
61
    }
62
63
    /**
64
     * @return object Database connection
65
     */
66
    protected function makeDbConn()
67
    {
68
        $getConn = new DBConnect();
69
        return $getConn->getConnection();
70
    }
71
72
    /**
73
     * Get a distinct record from the database
74
     *
75
     * @param int $record Index of record to get
76
     * @return string|object
77
     */
78
    public function find($record)
79
    {
80
        return self::where('id', $record);
81
    }
82
83
    /**
84
     * Get a record in the database
85
     *
86
     * @param string $field Field name to search under
87
     * @param string $value Field value to search for
88
     * @return string|object
89
     */
90
    public function where($field, $value)
91
    {
92
        try {
93
            $dbConnect = self::makeDbConn();
94
            $sql = 'SELECT * FROM ' . self::tableName() . ' WHERE ' . $field . ' = ?';
95
            $query = $dbConnect->prepare($sql);
96
            $query->execute([$value]);
97
            if ($query->rowCount() > 0) {
98
                $found = new static;
99
                $found->dbData = $query->fetch(PDO::FETCH_ASSOC);
0 ignored issues
show
Documentation introduced by
The property dbData does not exist on object<Sirolad\Potato>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
100
101
                return $found;
102
            } else {
103
                throw new RecordNotFoundException;
104
            }
105
        } catch (RecordNotFoundException $e) {
106
            return $e->message();
107
        }
108
        finally {
109
            $dbConnect = null;
0 ignored issues
show
Unused Code introduced by
$dbConnect 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...
110
        }
111
    }
112
113
    /**
114
     * Get all the records in a database table
115
     * @return array|object
116
     * @return exception
117
     */
118
    public function getAll()
119
    {
120
        try {
121
            $dbConn = self::makeDbConn();
122
            $query = $dbConn->prepare('SELECT * FROM ' . self::tableName());
123
            $query->execute();
124
125
            if ($query->rowCount()) {
126
                return json_encode($query->fetchAll(PDO::FETCH_ASSOC), JSON_FORCE_OBJECT);
127
            } else {
128
                throw new EmptyTableException;
129
            }
130
        } catch (PDOException $e) {
131
            return $e->getMessage();
132
        }
133
        finally {
134
            $dbConn = null;
0 ignored issues
show
Unused Code introduced by
$dbConn 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...
135
        }
136
    }
137
138
    /**
139
     * Insert or Update a record in a database table
140
     * @return inte
141
     * @return exception
142
     */
143
    public function save()
144
    {
145
        try {
146
            $dbConn = self::makeDbConn();
147
148
            if (isset($this->record['dbData']) && is_array($this->record['dbData'])) {
149
                $sql = 'UPDATE ' . $this->tableName() . ' SET ' . Formatter::tokenize(implode(',', Formatter::makeAssociativeArray($this->record)), ',') . ' WHERE id=' . $this->record['dbData']['id'];
150
                $query = $dbConn->prepare($sql);
151
                $query->execute();
152
            } else {
153
                $sql = 'INSERT INTO ' . $this->tableName() . ' (' . Formatter::tokenize(implode(',', array_keys($this->record)), ',') . ')' . ' VALUES ' . '(' . Formatter::tokenize(implode(',', Formatter::generateUnnamedPlaceholders($this->record)), ',') . ')';
154
                $query = $dbConn->prepare($sql);
155
                $query->execute(array_values($this->record));
156
            }
157
        } catch (PDOException $e) {
158
            return $e->getMessage();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $e->getMessage(); (string) is incompatible with the return type documented by Sirolad\Potato::save of type Sirolad\inte.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
159
        } catch (RecordNotFoundException $e) {
160
            return $e->message();
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $e->message(); (string) is incompatible with the return type documented by Sirolad\Potato::save of type Sirolad\inte.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

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

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
161
        }
162
        finally {
163
            $dbConn = null;
0 ignored issues
show
Unused Code introduced by
$dbConn 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...
164
        }
165
166
        return $query->rowCount();
0 ignored issues
show
Bug introduced by
The variable $query 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...
167
    }
168
169
    /**
170
     * Delete a record from the database table
171
     * @param int $record Index of record to be deleted
172
     * @return bool|string
173
     * @return exception
174
     */
175
    public function destroy($record)
176
    {
177
        try {
178
            $dbConn = self::makeDbConn();
179
            $query = $dbConn->prepare('DELETE FROM ' . self::tableName($dbConn) . ' WHERE id= ' . $record);
0 ignored issues
show
Unused Code introduced by
The call to Potato::tableName() has too many arguments starting with $dbConn.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
180
            $query->execute();
181
            $check = $query->rowCount();
182
            if ($check) {
183
                return $check;
184
            } else {
185
                throw new RecordNotFoundException;
186
            }
187
        } catch (PDOException $e) {
188
            echo $e->getMessage();
189
        }
190
        finally {
191
            $dbConn = null;
0 ignored issues
show
Unused Code introduced by
$dbConn 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...
192
        }
193
    }
194
}
195