MemoryDatabase::count()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 11.4436

Importance

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