Completed
Push — master ( 458558...445d82 )
by Iqbal
02:24
created

Pdo::resolveValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 12
rs 9.4285
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
/*
3
 * This file is part of the Borobudur-Cqrs package.
4
 *
5
 * (c) Hexacodelabs <http://hexacodelabs.com>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Borobudur\Cqrs\ReadModel\Storage\Pdo;
12
13
use Borobudur\Collection\Collection;
14
use Borobudur\Cqrs\Exception\InvalidArgumentException;
15
use Borobudur\Cqrs\ReadModel\ReadModelInterface;
16
use Borobudur\Cqrs\ReadModel\Storage\Finder\Expression\CompositeExpressionInterface;
17
use Borobudur\Cqrs\ReadModel\Storage\Pdo\Exception\PdoException;
18
use Borobudur\Cqrs\ReadModel\Storage\Pdo\Expression\PdoCompositeExpression;
19
use Borobudur\Cqrs\ReadModel\Storage\StorageInterface;
20
use Borobudur\Cqrs\Serializer\DataTypeCasterTrait;
21
use Borobudur\Serialization\StringInterface;
22
use Borobudur\Serialization\ValuableInterface;
23
use PDO as PhpPdo;
24
use PDOStatement;
25
26
/**
27
 * @author      Iqbal Maulana <[email protected]>
28
 * @created     8/18/15
29
 */
30
class Pdo implements StorageInterface
31
{
32
    use DataTypeCasterTrait;
33
34
    /**
35
     * @var PhpPdo
36
     */
37
    protected $conn;
38
39
    /**
40
     * @var PdoConfig
41
     */
42
    protected $config;
43
44
    /**
45
     * @var array
46
     */
47
    protected $quoteMaps = array('mysql' => '`', 'pgsql' => '"');
48
49
    /**
50
     * @var string
51
     */
52
    protected $quote = '`';
53
54
    /**
55
     * Constructor.
56
     *
57
     * @param PdoConfig $config
58
     */
59
    public function __construct(PdoConfig $config)
60
    {
61
        try {
62
            $this->conn = new PhpPdo($config->getDsn(), $config->getUser(), $config->getPass());
63
            $this->conn->setAttribute(PhpPdo::ATTR_ERRMODE, PhpPdo::ERRMODE_EXCEPTION);
64
        } catch (\PDOException $e) {
65
            throw new PdoException($e);
66
        }
67
68
        $this->config = $config;
69
70
        if (isset($this->quoteMaps[$config->getEngine()])) {
71
            $this->quote = $this->quoteMaps[$config->getEngine()];
72
        }
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    public function save(ReadModelInterface $model, $table)
79
    {
80
        $class = get_class($model);
81
        $id = $this->resolveValue($model->getId());
82
        if (null !== $prev = $this->findById($id, $table, $class)) {
83
            $values = $this->normalize(array_merge($prev->serialize(), $model->serialize()));
84
            $conditions = $this->normalize(array('id' => $id));
85
            unset($values['id']);
86
            $this->exec($this->buildUpdateQuery($values, $conditions, $table), array_merge($values, $conditions));
87
88
            return;
89
        }
90
91
        $values = $this->normalize($model->serialize());
92
        $values['id'] = $id;
93
        $this->exec($this->buildInsertQuery($values, $table), $values);
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    public function remove($id, $table)
100
    {
101
        $values = array('id' => $this->resolveValue($id));
102
        $this->exec(sprintf('DELETE FROM %s WHERE id = :id', $this->quote($table)), $values);
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function findById($id, $table, $class)
109
    {
110
        $finder = $this->finder($table, $class);
111
112
        return $finder->where($finder->expr()->equal('id', $this->resolveValue($id)))->first();
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function finder($table, $class)
119
    {
120
        return new PdoFinder($this, $table, $class, $this->config->getParser(), $this->quote);
121
    }
122
123
    /**
124
     * Execute query without statement.
125
     *
126
     * @param string $sql
127
     * @param array  $values
128
     */
129
    public function exec($sql, array $values = array())
130
    {
131
        if (!empty($values)) {
132
            $normalized = array();
133
            foreach ($values as $index => $value) {
134
                $normalized[':' . $index] = $value;
135
            }
136
137
            $values = $normalized;
138
        }
139
140
        try {
141
            $conn = $this->conn->prepare($sql);
142
            $conn->execute($values);
143
        } catch (\PDOException $e) {
144
            throw new PdoException($e);
145
        }
146
    }
147
148
    /**
149
     * Execute query with statement.
150
     *
151
     * @param string $sql
152
     * @param int    $mode
153
     *
154
     * @return PDOStatement
155
     */
156
    public function query($sql, $mode = PhpPdo::FETCH_ASSOC)
157
    {
158
        try {
159
            return $this->conn->query($sql, $mode);
160
        } catch (\PDOException $e) {
161
            throw new PdoException($e);
162
        }
163
    }
164
165
    /**
166
     * Compute fields to expressions.
167
     *
168
     * @param array     $fields
169
     * @param PdoFinder $finder
170
     *
171
     * @return PdoCompositeExpression
172
     */
173
    protected function computeFieldsExpression(array $fields, PdoFinder $finder)
174
    {
175
        $expressions = array();
176
        foreach ($fields as $name => $value) {
177
            $expressions[] = $finder->expr()->equal($name, $value);
178
        }
179
180
        return new PdoCompositeExpression(CompositeExpressionInterface::LOGICAL_AND, $expressions);
181
    }
182
183
    /**
184
     * Normalize value.
185
     *
186
     * @param array $values
187
     *
188
     * @return array
189
     */
190
    protected function normalize(array $values)
191
    {
192
        foreach ($values as $name => $value) {
193
            if (is_array($value)) {
194
                throw new InvalidArgumentException(sprintf(
195
                    'Cannot parse value with named "%s", data should be flat array.',
196
                    $name
197
                ));
198
            }
199
200
            if (is_bool($value)) {
201
                $value = true === $value ? 'true' : 'false';
202
            }
203
204
            $values[$name] = self::castType($value);
205
        }
206
207
        return $values;
208
    }
209
210
    /**
211
     * Build query update.
212
     *
213
     * @param array  $values
214
     * @param array  $conditions
215
     * @param string $table
216
     *
217
     * @return string
218
     */
219
    protected function buildUpdateQuery(array $values, array $conditions, $table)
220
    {
221
        $sets = $this->buildDataSets($values);
222
        $wheres = $this->buildDataSets($conditions);
223
224
        return
225
            'UPDATE ' . $this->quote($table) .
226
            ' SET ' . implode(', ', $sets) .
227
            ' WHERE ' . implode(' AND ', $wheres);
228
    }
229
230
    /**
231
     * Build insert query.
232
     *
233
     * @param array  $values
234
     * @param string $table
235
     *
236
     * @return string
237
     */
238
    public function buildInsertQuery(array $values, $table)
239
    {
240
        $keys = array_map(array($this, 'quote'), array_keys($values));
241
        $values = array_keys($values);
242
243
        return
244
            'INSERT INTO ' . $this->quote($table) .
245
            ' (' . implode(', ', $keys) . ') VALUES (:' . implode(', :', $values) . ')';
246
    }
247
248
    /**
249
     * Build data sets.
250
     *
251
     * @param array $parts
252
     *
253
     * @return array
254
     */
255
    protected function buildDataSets(array $parts)
256
    {
257
        $sets = array();
258
        foreach ($parts as $name => $value) {
259
            $sets[] = $this->quote($name) . '=:' . $name;
260
        }
261
262
        return $sets;
263
    }
264
265
    /**
266
     * Quote field.
267
     *
268
     * @param string $field
269
     *
270
     * @return string
271
     */
272
    protected function quote($field)
273
    {
274
        return $this->quote.$field.$this->quote;
275
    }
276
277
    /**
278
     * @param mixed $value
279
     *
280
     * @return mixed|null|string
281
     */
282
    protected function resolveValue($value)
283
    {
284
        if ($value instanceof ValuableInterface) {
285
            return $value->getValue();
286
        }
287
288
        if ($value instanceof StringInterface) {
289
            return (string) $value;
290
        }
291
292
        return $value;
293
    }
294
}
295