Completed
Push — master ( 6738b5...e02aed )
by Iqbal
02:36
created

Pdo::find()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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