Completed
Push — master ( f38213...c5a8cb )
by Jared
02:23
created

DatabaseAdapter::prefixColumn()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 8
rs 9.4285
cc 3
eloc 4
nc 2
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\Adapter;
12
13
use Carbon\Carbon;
14
use ICanBoogie\Inflector;
15
use Pulsar\Exception\AdapterException;
16
use Pulsar\Model;
17
use Pulsar\Query;
18
use PDOException;
19
use PDOStatement;
20
use Pimple\Container;
21
22
class DatabaseAdapter implements AdapterInterface
23
{
24
    /**
25
     * @var \Pimple\Container
26
     */
27
    private $app;
28
29
    /**
30
     * @param \Pimple\Container $app
31
     */
32
    public function __construct(Container $app = null)
33
    {
34
        $this->app = $app;
35
    }
36
37
    public function createModel(Model $model, array $parameters)
38
    {
39
        $values = $this->serialize($model, $parameters);
40
        $tablename = $this->getTablename($model);
41
42
        try {
43
            return $this->app['db']->insert($values)
44
                ->into($tablename)
45
                ->execute() instanceof PDOStatement;
46
        } catch (PDOException $original) {
47
            $e = new AdapterException('An error occurred in the database adapter when creating the '.$model::modelName());
48
            $e->setException($original);
49
            throw $e;
50
        }
51
    }
52
53
    public function getCreatedID(Model $model, $propertyName)
54
    {
55
        try {
56
            $id = $this->app['db']->getPDO()->lastInsertId();
57
        } catch (PDOException $original) {
58
            $e = new AdapterException('An error occurred in the database adapter when getting the ID of the new '.$model::modelName());
59
            $e->setException($original);
60
            throw $e;
61
        }
62
63
        return $id;
64
    }
65
66
    public function updateModel(Model $model, array $parameters)
67
    {
68
        if (count($parameters) == 0) {
69
            return true;
70
        }
71
72
        $values = $this->serialize($model, $parameters);
73
        $tablename = $this->getTablename($model);
74
75
        try {
76
            return $this->app['db']->update($tablename)
77
                ->values($values)
78
                ->where($model->ids())
79
                ->execute() instanceof PDOStatement;
80
        } catch (PDOException $original) {
81
            $e = new AdapterException('An error occurred in the database adapter when updating the '.$model::modelName());
82
            $e->setException($original);
83
            throw $e;
84
        }
85
    }
86
87
    public function deleteModel(Model $model)
88
    {
89
        $tablename = $this->getTablename($model);
90
91
        try {
92
            return $this->app['db']->delete($tablename)
93
                ->where($model->ids())
94
                ->execute() instanceof PDOStatement;
95
        } catch (PDOException $original) {
96
            $e = new AdapterException('An error occurred in the database adapter while deleting the '.$model::modelName());
97
            $e->setException($original);
98
            throw $e;
99
        }
100
    }
101
102
    public function queryModels(Query $query)
103
    {
104
        $model = $query->getModel();
105
        $tablename = $this->getTablename($model);
106
107
        // build a DB query from the model query
108
        $dbQuery = $this->app['db']
109
            ->select($this->prefixSelect('*', $tablename))
110
            ->from($tablename)
111
            ->where($this->prefixWhere($query->getWhere(), $tablename))
112
            ->limit($query->getLimit(), $query->getStart())
113
            ->orderBy($this->prefixSort($query->getSort(), $tablename));
114
115
        // join conditions
116
        foreach ($query->getJoins() as $join) {
117
            list($foreignModel, $column, $foreignKey) = $join;
118
119
            $foreignTablename = $this->getTablename($foreignModel);
120
            $condition = $this->prefixColumn($column, $tablename).'='.$this->prefixColumn($foreignKey, $foreignTablename);
121
122
            $dbQuery->join($foreignTablename, $condition);
123
        }
124
125
        try {
126
            $data = $dbQuery->all();
127
        } catch (PDOException $original) {
128
            $e = new AdapterException('An error occurred in the database adapter while performing the '.$model::modelName().' query');
129
            $e->setException($original);
130
            throw $e;
131
        }
132
133
        return $data;
134
    }
135
136
    public function totalRecords(Query $query)
137
    {
138
        $model = $query->getModel();
139
        $tablename = $this->getTablename($model);
140
141
        try {
142
            return (int) $this->app['db']->select('count(*)')
143
                ->from($tablename)
144
                ->where($query->getWhere())
145
                ->scalar();
146
        } catch (PDOException $original) {
147
            $e = new AdapterException('An error occurred in the database adapter while getting the number of '.$model::modelName().' objects');
148
            $e->setException($original);
149
            throw $e;
150
        }
151
    }
152
153
    /**
154
     * Generates the tablename for the model.
155
     *
156
     * @param string|Model $model
157
     *
158
     * @return string
159
     */
160
    public function getTablename($model)
161
    {
162
        $inflector = Inflector::get();
163
164
        return $inflector->camelize($inflector->pluralize($model::modelName()));
165
    }
166
167
    /**
168
     * Marshals a value to storage.
169
     *
170
     * @param mixed $value
171
     * @param Model|string optional model class
172
     * @param string $property optional property name
173
     *
174
     * @return mixed serialized value
175
     */
176
    public function serializeValue($value, $model = null, $property = null)
177
    {
178
        // convert dates back to their string representation
179
        if ($value instanceof Carbon) {
180
            if (!$model) {
181
                $model = 'Pulsar\Model';
182
            }
183
184
            $format = $model::getDateFormat($property);
185
186
            return $value->format($format);
187
        }
188
189
        // encode arrays/objects as JSON
190
        if (is_array($value) || is_object($value)) {
191
            return json_encode($value);
192
        }
193
194
        return $value;
195
    }
196
197
    /**
198
     * Serializes an array of values.
199
     *
200
     * @param Model|string model class
201
     * @param array $values
202
     *
203
     * @return array
204
     */
205
    private function serialize($model, array $values)
206
    {
207
        foreach ($values as $k => &$value) {
208
            $value = $this->serializeValue($value, $model, $k);
209
        }
210
211
        return $values;
212
    }
213
214
    /**
215
     * Returns a prefixed select statement.
216
     *
217
     * @param string $columns
218
     * @param string $tablename
219
     *
220
     * @return string
221
     */
222
    private function prefixSelect($columns, $tablename)
223
    {
224
        $prefixed = [];
225
        foreach (explode(',', $columns) as $column) {
226
            $prefixed[] = $this->prefixColumn($column, $tablename);
227
        }
228
229
        return implode(',', $prefixed);
230
    }
231
232
    /**
233
     * Returns a prefixed where statement.
234
     *
235
     * @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...
236
     * @param string $tablename
237
     *
238
     * @return string
239
     */
240
    private function prefixWhere(array $where, $tablename)
241
    {
242
        $return = [];
243
        foreach ($where as $key => $condition) {
244
            // handles $where[property] = value
245
            if (!is_numeric($key)) {
246
                $return[$this->prefixColumn($key, $tablename)] = $condition;
247
            // 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...
248
            } elseif (is_array($condition)) {
249
                $condition[0] = $this->prefixColumn($condition[0], $tablename);
250
                $return[] = $condition;
251
            // handles raw SQL - do nothing
252
            } else {
253
                $return[] = $condition;
254
            }
255
        }
256
257
        return $return;
258
    }
259
260
    /**
261
     * Returns a prefixed sort statement.
262
     *
263
     * @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...
264
     * @param string $tablename
265
     *
266
     * @return string
267
     */
268
    private function prefixSort(array $sort, $tablename)
269
    {
270
        foreach ($sort as &$condition) {
271
            $condition[0] = $this->prefixColumn($condition[0], $tablename);
272
        }
273
274
        return $sort;
275
    }
276
277
    /**
278
     * Prefix columns with tablename that contains only
279
     * alphanumeric/underscores/*.
280
     *
281
     * @param string $column
282
     * @param string $tablename
283
     *
284
     * @return string prefixed column
285
     */
286
    private function prefixColumn($column, $tablename)
287
    {
288
        if ($column === '*' || preg_match('/^[a-z0-9_]+$/i', $column)) {
289
            return "$tablename.$column";
290
        }
291
292
        return $column;
293
    }
294
}
295