Completed
Push — master ( 3c6b88...473a6c )
by Jared
02:29
created

DatabaseDriver::unserialize()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 3
eloc 5
nc 3
nop 2
1
<?php
2
3
/**
4
 * @author Jared King <[email protected]>
5
 *
6
 * @link http://jaredtking.com
7
 *
8
 * @copyright 2015 Jared King
9
 * @license MIT
10
 */
11
namespace Pulsar\Driver;
12
13
use ICanBoogie\Inflector;
14
use Pulsar\Exception\DriverException;
15
use Pulsar\Model;
16
use Pulsar\Query;
17
use PDOException;
18
use PDOStatement;
19
use Pimple\Container;
20
21
class DatabaseDriver implements DriverInterface
22
{
23
    /**
24
     * @var \Pimple\Container
25
     */
26
    private $app;
27
28
    /**
29
     * @param \Pimple\Container $app
30
     */
31
    public function __construct(Container $app = null)
32
    {
33
        $this->app = $app;
34
    }
35
36
    public function createModel(Model $model, array $parameters)
37
    {
38
        $values = $this->serialize($parameters);
39
        $tablename = $this->getTablename($model);
40
41
        try {
42
            return $this->app['db']->insert($values)
43
                ->into($tablename)
44
                ->execute() instanceof PDOStatement;
45
        } catch (PDOException $original) {
46
            $e = new DriverException('An error occurred in the database driver when creating the '.$model::modelName());
47
            $e->setException($original);
48
            throw $e;
49
        }
50
    }
51
52
    public function getCreatedID(Model $model, $propertyName)
53
    {
54
        try {
55
            $id = $this->app['db']->getPDO()->lastInsertId();
56
        } catch (PDOException $original) {
57
            $e = new DriverException('An error occurred in the database driver when getting the ID of the new '.$model::modelName());
58
            $e->setException($original);
59
            throw $e;
60
        }
61
62
        return Model::cast($model::getProperty($propertyName)['type'], $id);
63
    }
64
65
    public function updateModel(Model $model, array $parameters)
66
    {
67
        if (count($parameters) == 0) {
68
            return true;
69
        }
70
71
        $values = $this->serialize($parameters);
72
        $tablename = $this->getTablename($model);
73
74
        try {
75
            return $this->app['db']->update($tablename)
76
                ->values($values)
77
                ->where($model->ids())
78
                ->execute() instanceof PDOStatement;
79
        } catch (PDOException $original) {
80
            $e = new DriverException('An error occurred in the database driver when updating the '.$model::modelName());
81
            $e->setException($original);
82
            throw $e;
83
        }
84
    }
85
86
    public function deleteModel(Model $model)
87
    {
88
        $tablename = $this->getTablename($model);
89
90
        try {
91
            return $this->app['db']->delete($tablename)
92
                ->where($model->ids())
93
                ->execute() instanceof PDOStatement;
94
        } catch (PDOException $original) {
95
            $e = new DriverException('An error occurred in the database driver while deleting the '.$model::modelName());
96
            $e->setException($original);
97
            throw $e;
98
        }
99
    }
100
101
    public function queryModels(Query $query)
102
    {
103
        $model = $query->getModel();
104
        $tablename = $this->getTablename($model);
105
106
        // build a DB query from the model query
107
        $dbQuery = $this->app['db']
108
            ->select($this->prefixSelect('*', $tablename))
109
            ->from($tablename)
110
            ->where($this->prefixWhere($query->getWhere(), $tablename))
111
            ->limit($query->getLimit(), $query->getStart())
112
            ->orderBy($this->prefixSort($query->getSort(), $tablename));
113
114
        // join conditions
115
        foreach ($query->getJoins() as $join) {
116
            list($foreignModel, $column, $foreignKey) = $join;
117
118
            $foreignTablename = $this->getTablename($foreignModel);
119
            $condition = $this->prefixColumn($column, $tablename).'='.$this->prefixColumn($foreignKey, $foreignTablename);
120
121
            $dbQuery->join($foreignTablename, $condition);
122
        }
123
124
        try {
125
            $data = $dbQuery->all();
126
        } catch (PDOException $original) {
127
            $e = new DriverException('An error occurred in the database driver while performing the '.$model::modelName().' query');
128
            $e->setException($original);
129
            throw $e;
130
        }
131
132
        $properties = $model::getProperties();
133
        foreach ($data as &$row) {
134
            foreach ($row as $k => &$value) {
135
                if (isset($properties[$k])) {
136
                    $type = (isset($properties[$k]['type'])) ? $properties[$k]['type'] : Model::TYPE_STRING;
137
                    $value = Model::cast($type, $value);
138
                }
139
            }
140
        }
141
142
        return $data;
143
    }
144
145
    public function totalRecords(Query $query)
146
    {
147
        $model = $query->getModel();
148
        $tablename = $this->getTablename($model);
149
150
        try {
151
            return (int) $this->app['db']->select('count(*)')
152
                ->from($tablename)
153
                ->where($query->getWhere())
154
                ->scalar();
155
        } catch (PDOException $original) {
156
            $e = new DriverException('An error occurred in the database driver while getting the number of '.$model::modelName().' objects');
157
            $e->setException($original);
158
            throw $e;
159
        }
160
    }
161
162
    /**
163
     * Generates the tablename for the model.
164
     *
165
     * @param string|Model $model
166
     *
167
     * @return string
168
     */
169
    public function getTablename($model)
170
    {
171
        $inflector = Inflector::get();
172
173
        return $inflector->camelize($inflector->pluralize($model::modelName()));
174
    }
175
176
    /**
177
     * Marshals a value to storage.
178
     *
179
     * @param mixed $value
180
     *
181
     * @return mixed serialized value
182
     */
183
    public function serializeValue($value)
184
    {
185
        // encode arrays/objects as JSON
186
        if (is_array($value) || is_object($value)) {
187
            return json_encode($value);
188
        }
189
190
        return $value;
191
    }
192
193
    /**
194
     * Serializes an array of values.
195
     *
196
     * @param array $values
197
     *
198
     * @return array
199
     */
200
    private function serialize(array $values)
201
    {
202
        foreach ($values as &$value) {
203
            $value = $this->serializeValue($value);
204
        }
205
206
        return $values;
207
    }
208
209
    /**
210
     * Returns a prefixed select statement.
211
     *
212
     * @param string $columns
213
     * @param string $tablename
214
     *
215
     * @return string
216
     */
217
    private function prefixSelect($columns, $tablename)
218
    {
219
        $prefixed = [];
220
        foreach (explode(',', $columns) as $column) {
221
            $prefixed[] = $this->prefixColumn($column, $tablename);
222
        }
223
224
        return implode(',', $prefixed);
225
    }
226
227
    /**
228
     * Returns a prefixed where statement.
229
     *
230
     * @param string $columns
0 ignored issues
show
Bug introduced by
There is no parameter named $columns. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
231
     * @param string $tablename
232
     *
233
     * @return string
234
     */
235
    private function prefixWhere(array $where, $tablename)
236
    {
237
        $return = [];
238
        foreach ($where as $key => $condition) {
239
            // handles $where[property] = value
240
            if (!is_numeric($key)) {
241
                $return[$this->prefixColumn($key, $tablename)] = $condition;
242
            // handles $where[] = [property, value, '=']
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
243
            } elseif (is_array($condition)) {
244
                $condition[0] = $this->prefixColumn($condition[0], $tablename);
245
                $return[] = $condition;
246
            // handles raw SQL - do nothing
247
            } else {
248
                $return[] = $condition;
249
            }
250
        }
251
252
        return $return;
253
    }
254
255
    /**
256
     * Returns a prefixed sort statement.
257
     *
258
     * @param string $columns
0 ignored issues
show
Bug introduced by
There is no parameter named $columns. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
259
     * @param string $tablename
260
     *
261
     * @return string
262
     */
263
    private function prefixSort(array $sort, $tablename)
264
    {
265
        foreach ($sort as &$condition) {
266
            $condition[0] = $this->prefixColumn($condition[0], $tablename);
267
        }
268
269
        return $sort;
270
    }
271
272
    /**
273
     * Prefix columns with tablename that contains only
274
     * alphanumeric/underscores/*.
275
     *
276
     * @param string $column
277
     * @param string $tablename
278
     *
279
     * @return string prefixed column
280
     */
281
    private function prefixColumn($column, $tablename)
282
    {
283
        if ($column === '*' || preg_match('/^[a-z0-9_]+$/i', $column)) {
284
            return "$tablename.$column";
285
        }
286
287
        return $column;
288
    }
289
}
290