Passed
Push — master ( 7e2ddb...73a986 )
by Michael
07:32
created

MemoryDatabase::lastIdCreated()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 1
1
<?php
2
namespace DAL;
3
4
use \DAL\Exceptions\DatabaseException;
5
6
/**
7
 * A database that stores all of its data in the memory.
8
 * Still a work in progress.
9
 */
10
class MemoryDatabase extends AbstractDatabase
11
{
12
13
    public $tables = [];
14
    private $lastIdCreated;
15
    private $lastAutoId = 0;
16
    private $primaryKey;
17
18
    /**
19
     * @param string     $primaryKey
20
     * @param array|null $rawData
21
     */
22 13
    public function __construct($primaryKey = 'id', array $rawData = null)
23
    {
24 13
        $this->primaryKey = $primaryKey;
25 13
        if ($rawData !== null) {
26 13
            $this->tables = $rawData;
27 13
        }
28 13
    }
29
30 4
    public function dumpRawData()
31
    {
32 4
        return $this->tables;
33
    }
34
35
    /**
36
     * @return mixed
37
     */
38 2
    public function lastIdCreated()
39
    {
40 2
        return $this->lastIdCreated;
41
    }
42
43 1
    private function createTableIfNotExists($table)
44
    {
45 1
        if (!isset($this->tables[$table])) {
46
            $this->tables[$table] = [];
47
        }
48 1
    }
49
50
    /**
51
     * @param string $table   The table that will be accessed and written.
52
     * @param array  $fields  A list of fields and values to be used when creating a new record.
53
     * @param array  $options The list of options that will help with creating the records.
54
     * @return void
55
     */
56 1
    public function create($table, array $fields, array $options = [])
57
    {
58 1
        $this->createTableIfNotExists($table);
59 1
        $primaryKey = $this->primaryKey;
60 1
        if (isset($fields[$primaryKey])) {
61
            // We will use the primary key already given.
62
            $id = $fields[$primaryKey];
63
        } else {
64
            // We have to make our own primary key.
65 1
            $id = $this->lastAutoId;
66
            do {
67 1
                $id++;
68 1
            } while (isset($this->tables[$table][$id]));
69 1
            $this->lastAutoId    = $id;
70 1
            $fields[$primaryKey] = $id;
71
        }
72 1
        $this->lastIdCreated       = $id;
73 1
        $this->tables[$table][$id] = $fields;
74 1
    }
75
76
    /**
77
     * If the condition matches the data in the record, then the return value is true. Otherwise,
78
     * the return value is false.
79
     *
80
     * @return bool Returns true if the condition matches the data in the record.
81
     */
82 6
    private function isRecordMatchingCondition($primaryKeyValue, $record, Condition $condition = null)
83
    {
84 6
        if ($condition === null) {
85 6
            return true;
86
        }
87
        $operator = $condition->getOperator();
88
        $field    = $condition->getLeft();
89
        $value    = $condition->getRight();
90 View Code Duplication
        if ($operator === 'and') {
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...
91
            return $this->isRecordMatchingCondition($primaryKeyValue, $record, $field)
92
                && $this->isRecordMatchingCondition($primaryKeyValue, $record, $value);
93
        }
94 View Code Duplication
        if ($operator === 'or') {
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...
95
            return $this->isRecordMatchingCondition($primaryKeyValue, $record, $field)
96
                || $this->isRecordMatchingCondition($primaryKeyValue, $record, $value);
97
        }
98
        if ($field === $this->primaryKey) {
99
            $actualValue = $primaryKeyValue;
100
        } elseif (isset($record[$field])) {
101
            $actualValue = $record[$field];
102
        } else {
103
            $actualValue = null;
104
        }
105
        if ($operator === '=') {
106
            // A weak comparison is done intentionally, as some SQL databases
107
            // use weak comparisons.
108
            return $actualValue == $value;
109
        }
110
        if ($operator === '!=') {
111
            return $actualValue != $value;
112
        }
113
        if ($operator === '>') {
114
            return $actualValue > $value;
115
        }
116
        if ($operator === '<') {
117
            return $actualValue < $value;
118
        }
119
        if ($operator === '>=') {
120
            return $actualValue >= $value;
121
        }
122
        if ($operator === '<=') {
123
            return $actualValue <= $value;
124
        }
125
        if ($operator === 'regex') {
126
            $result = preg_match($value, $actualValue);
127
            if ($result === false) {
128
                $message = 'The regular expression provided is invalid! Expression: ' . $value;
129
                throw new DatabaseException($message);
130
            }
131
            return $result === 1;
132
        }
133
        $message = 'Invalid operation! You must use one of the provided static methods!';
134
        throw new DatabaseException($message);
135
    }
136
137
    /**
138
     * @param string         $table    The table that will be accessed and written.
139
     * @param array          $fields   A list of fields and values to be used when updating a record.
140
     * @param Condition|null $criteria The criteria that will filter the data.
141
     * @param array          $options  The list of options that will help with updating the records.
142
     * @return void
143
     */
144 1
    public function update($table, array $fields, Condition $criteria = null, array $options = [])
145
    {
146 1
        if (isset($fields[$this->primaryKey])) {
147
            throw new DatabaseException(
148
                'Error when updating a record! You may NOT change the primary key of a record!'
149
            );
150
        }
151
152
        // The code below is included for increased efficiency.
153
        // It will check to see if a primary key is being compared.
154
        if ($criteria != null
155 1
            && $criteria->getOperator() === '='
156 1
            && $criteria->getLeft() === $this->primaryKey
157 1
        ) {
158
            $primaryKeyValue = $criteria->getRight();
159
            if (isset($this->tables[$table][$primaryKeyValue])) {
160
                $record = $this->tables[$table][$primaryKeyValue];
0 ignored issues
show
Unused Code introduced by
$record 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...
161
                foreach ($fields as $key => $value) {
162
                    $this->tables[$table][$key] = $value;
163
                }
164
            }
165
            return;
166
        }
167
168 1
        $records = [];
169 1
        foreach ($this->tables[$table] as $primaryKeyValue => $record) {
170 1
            if ($this->isRecordMatchingCondition($primaryKeyValue, $record, $criteria)) {
171 1
                foreach ($fields as $key => $value) {
172
                    $this->tables[$table][$key] = $value;
173 1
                }
174 1
                $records[$primaryKeyValue] = $record;
175 1
            }
176 1
        }
177 1
    }
178
179
    /**
180
     * @param string         $table    The table that will be accessed and written.
181
     * @param Condition|null $criteria The criteria that will filter the data.
182
     * @param array          $options  The list of options that will help with deleting the records.
183
     * @return void
184
     */
185 1
    public function delete($table, Condition $criteria = null, array $options = [])
186
    {
187 1
        if (!isset($this->tables[$table])) {
188
            return;
189
        }
190
191 1
        if ($criteria === null) {
192 1
            $this->tables[$table] = [];
193 1
            return;
194
        }
195
196
        // The code below is included for increased efficiency.
197
        // It will check to see if a primary key is being compared.
198
        if ($criteria->getOperator() === '=' && $criteria->getLeft() === $this->primaryKey) {
199
            $primaryKeyValue = $criteria->getRight();
200
            if (isset($this->tables[$table][$primaryKeyValue])) {
201
                unset($this->tables[$table][$primaryKeyValue]);
202
            }
203
            return;
204
        }
205
206
        foreach ($this->tables[$table] as $primaryKeyValue => $record) {
207
            if ($this->isRecordMatchingCondition($primaryKeyValue, $record, $criteria)) {
208
                unset($this->tables[$table][$primaryKeyValue]);
209
            }
210
        }
211
        return;
212
    }
213
214
    /**
215
     * Returns all records that match the criteria.
216
     *
217
     * @param string         $table The table that will be accessed and written.
218
     * @param Condition|null $criteria The criteria that will filter the records.
219
     * @param array          $options The list of options that will help with finding the records.
220
     * @return array[] Multiple records from the table that match the criteria.
221
     */
222 6
    public function findAll($table, Condition $criteria = null, array $options = [])
223
    {
224 6
        $limit = null;
225 6
        if (isset($options['limit'])) {
226 4
            $limit = $options['limit'];
227 4
        }
228
229 6
        if (($limit !== null && $limit < 1) || !isset($this->tables[$table])) {
230 1
            return [];
231
        }
232
233
        // The code below is included for increased efficiency.
234
        // It will check to see if a primary key is being compared.
235 5
        if ($criteria !== null && $criteria->getOperator() === '=' && $criteria->getLeft() === $this->primaryKey) {
236
            $primaryKeyValue = $criteria->getRight();
237
            if (isset($this->tables[$table][$primaryKeyValue])) {
238
                return [$primaryKeyValue => $this->tables[$table][$primaryKeyValue]];
239
            }
240
            return [];
241
        }
242
243 5
        $count   = 0;
244 5
        $records = [];
245 5
        foreach ($this->tables[$table] as $primaryKeyValue => $record) {
246 5
            if ($limit !== null && $count >= $limit) {
247
                // We have reached our limit!
248 3
                break;
249
            }
250 5
            if ($this->isRecordMatchingCondition($primaryKeyValue, $record, $criteria)) {
251 5
                $records[$primaryKeyValue] = $record;
252 5
                $count++;
253 5
            }
254 5
        }
255 5
        return $records;
256
    }
257
258
    /**
259
     * Returns the number of records that match the criteria.
260
     *
261
     * If the crtieria is null, then the return value is the total number of
262
     * records in the table.
263
     *
264
     * @param string         $table The table that will be accessed and written.
265
     * @param Condition|null $criteria The criteria that will filter the records.
266
     * @param array          $options The list of options that help with counting the records.
267
     * @return int The number of records that match the criteria.
268
     */
269 1
    public function count($table, Condition $criteria = null, array $options = [])
270
    {
271 1
        if (!isset($this->tables[$table])) {
272
            return 0;
273
        }
274
275 1
        if ($criteria === null) {
276 1
            return count($this->tables[$table]);
277
        }
278
279
        $count = 0;
280
        foreach ($this->tables[$table] as $primaryKeyValue => $record) {
281
            if ($this->isRecordMatchingCondition($primaryKeyValue, $record, $criteria)) {
282
                $count++;
283
            }
284
        }
285
        return $count;
286
    }
287
288
    /**
289
     * Returns true if there exists a record that matches the criteria.
290
     *
291
     * If the criteria is null and the table has data, then the return value is true. There are two
292
     * reasons for this result.
293
     *  - The table has stuff.
294
     *  - Running `$db->has('table_name')` is similar to saying,
295
          "does the database have table_name?"
296
     *
297
     * @param string         $table The table that will be accessed and written.
298
     * @param Condition|null $criteria The criteria that will filter the records.
299
     * @param array          $options The list of options that will help with finding the records.
300
     * @return boolean A boolean value indicating if any record matches the criteria.
301
     */
302 1
    public function has($table, Condition $criteria = null, array $options = [])
303
    {
304 1
        if (!isset($this->tables[$table])) {
305
            return false;
306
        }
307
308 1
        if ($criteria === null) {
309 1
            return !empty($this->tables[$table]);
310
        }
311
312
        foreach ($this->tables[$table] as $primaryKeyValue => $record) {
313
            if ($this->isRecordMatchingCondition($primaryKeyValue, $record, $criteria)) {
314
                return true;
315
            }
316
        }
317
        return false;
318
    }
319
}
320