Passed
Push — master ( 499888...edf93f )
by Ronan
03:17 queued 11s
created

QueryBuilder::generateCallback()   B

Complexity

Conditions 7
Paths 1

Size

Total Lines 50
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 7
eloc 36
c 5
b 0
f 0
nc 1
nop 0
dl 0
loc 50
ccs 0
cts 48
cp 0
crap 56
rs 8.4106
1
<?php
2
3
namespace Ronanchilvers\Orm;
4
5
use ClanCats\Hydrahon\Builder;
6
use ClanCats\Hydrahon\Query\Sql\FetchableInterface;
7
use ClanCats\Hydrahon\Query\Sql\Insert;
8
use ClanCats\Hydrahon\Query\Sql\Select;
9
use ClanCats\Hydrahon\Query\Sql\Update;
10
use Closure;
11
use Exception;
12
use PDO;
13
use Ronanchilvers\Orm\Model;
14
use Ronanchilvers\Orm\Model\Hydrator;
15
use RuntimeException;
16
17
/**
18
 * Class to build model queries and return model instances
19
 *
20
 * @author Ronan Chilvers <[email protected]>
21
 */
22
class QueryBuilder
23
{
24
    /**
25
     * @var \PDO
26
     */
27
    protected $connection;
28
29
    /**
30
     * @var string
31
     */
32
    protected $modelClass;
33
34
    /**
35
     * Class constructor
36
     *
37
     * @param PDO $connection
38
     * @param string $model
39
     * @author Ronan Chilvers <[email protected]>
40
     */
41
    public function __construct(
42
        PDO $connection,
43
        $modelClass
44
    ) {
45
        $this->connection = $connection;
46
        $this->modelClass = $modelClass;
47
    }
48
49
    /**
50
     * Get the connection object
51
     *
52
     * @return PDO
53
     * @author Ronan Chilvers <[email protected]>
54
     */
55
    public function getConnection()
56
    {
57
        return $this->connection;
58
    }
59
60
    /**
61
     * Get all records
62
     *
63
     * @return array
64
     * @author Ronan Chilvers <[email protected]>
65
     */
66
    public function all($page = null, $perPage = 10)
67
    {
68
        $select = $this->select();
69
        if (is_int($page)) {
70
            $select->page($page, $perPage);
71
        }
72
73
        return $select->execute();
74
    }
75
76
    /**
77
     * Get the first record in the table
78
     *
79
     * @return \Ronanchilvers\Orm\Model|null
80
     * @author Ronan Chilvers <[email protected]>
81
     */
82
    public function first()
83
    {
84
        $modelClass = $this->modelClass;
85
        return $this
86
            ->select()
87
            ->first($modelClass::primaryKey());
88
    }
89
90
    /**
91
     * Get a single record by id
92
     *
93
     * @param mixed $id
94
     * @return \Ronanchilvers\Orm\Model|null
95
     * @author Ronan Chilvers <[email protected]>
96
     */
97
    public function one($id)
98
    {
99
        $modelClass = $this->modelClass;
100
101
        return $this
102
            ->select()
103
            ->where($modelClass::primaryKey(), $id)
104
            ->one();
105
    }
106
107
    /**
108
     * Create a select object
109
     *
110
     * @return \ClanCats\Hydrahon\Query\Sql\Select
111
     * @author Ronan Chilvers <[email protected]>
112
     */
113
    public function select()
114
    {
115
        $builder = $this->newBuilder();
116
        $modelClass = $this->modelClass;
117
        $select = $builder->select();
118
        $select
119
            ->table($modelClass::table());
120
121
        return $select;
122
    }
123
124
    /**
125
     * Query using a raw SQL statement
126
     *
127
     * @param string $sql
128
     * @param array $params
129
     * @param int $page
130
     * @param int $perPage
131
     * @return mixed
132
     * @author Ronan Chilvers <[email protected]>
133
     */
134
    public function query($sql, $params = [], $page = null, $perPage = 20)
135
    {
136
        if (!is_null($page)) {
137
            $page = (int) $page;
138
            if ($page < 1) {
139
                $page = 1;
140
            }
141
            $offset = $perPage * ($page - 1);
142
            $limit  = $perPage;
143
144
            $sql .= " LIMIT {$limit} OFFSET {$offset}";
145
        }
146
        $callback = $this->generateCallback();
147
148
        return $callback(
149
            null,
150
            $sql,
151
            $params
152
        );
153
    }
154
155
    /**
156
     * Create an insert query
157
     *
158
     * @return \ClanCats\Hydrahon\Query\Sql\Insert
159
     * @author Ronan Chilvers <[email protected]>
160
     */
161
    public function insert()
162
    {
163
        $builder = $this->newBuilder();
164
        $modelClass = $this->modelClass;
165
166
        return $builder
167
            ->table($modelClass::table())
168
            ->insert();
169
    }
170
171
    /**
172
     * Create an update query
173
     *
174
     * @return \ClanCats\Hydrahon\Query\Sql\Update
175
     * @author Ronan Chilvers <[email protected]>
176
     */
177
    public function update()
178
    {
179
        $builder = $this->newBuilder();
180
        $modelClass = $this->modelClass;
181
182
        return $builder
183
            ->table($modelClass::table())
184
            ->update();
185
    }
186
187
    /**
188
     * Get a delete query
189
     *
190
     * @return \ClanCats\Hydrahon\Query\Sql\Delete
191
     * @author Ronan Chilvers <[email protected]>
192
     */
193
    public function delete()
194
    {
195
        $modelClass = $this->modelClass;
196
197
        return $this
198
            ->newBuilder()
199
            ->table($modelClass::table())
200
            ->delete();
201
    }
202
203
    /**
204
     * Create a hydrahon query builder
205
     *
206
     * @return \ClanCats\Hydrahon\Builder
207
     * @author Ronan Chilvers <[email protected]>
208
     */
209
    protected function newBuilder()
210
    {
211
        // @todo Don't hardcode mysql
212
        return new \ClanCats\Hydrahon\Builder(
213
            $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME),
214
            $this->generateCallback()
215
        );
216
    }
217
218
    /**
219
     * Generate a PDO callback
220
     *
221
     * @return \Closure
222
     * @author Ronan Chilvers <[email protected]>
223
     */
224
    protected function generateCallback()
225
    {
226
        return function($query, $sql, $params) {
227
            $sql = trim($sql);
228
            Orm::getEmitter()->emit('query.init', [
229
                $sql,
230
                $params
231
            ]);
232
            $stmt = $this->connection->prepare(
233
                $sql
234
            );
235
            $params = array_map(function($value) {
236
                if ($value instanceof Model) {
237
                    $value = $value->id;
238
                }
239
                return $value;
240
            }, $params);
241
            Orm::getEmitter()->emit('query.prepare', [
242
                $stmt,
243
                $sql,
244
                $params,
245
            ]);
246
            $result = $stmt->execute($params);
247
            if (false === $result) {
248
                Orm::getEmitter()->emit('query.fail', [
249
                    $stmt,
250
                    $sql,
251
                    $params
252
                ]);
253
                throw new RuntimeException(
254
                    implode(' : ', $stmt->errorInfo())
255
                );
256
            }
257
            if ('select' !== strtolower(substr($sql, 0, 6))) {
258
                return $result;
259
            }
260
            if (false !== strpos($sql, 'count(') || false !== strpos($sql, 'sum(')) {
261
                return $stmt->fetchAll(PDO::FETCH_ASSOC);
262
            }
263
264
            $class = $this->modelClass;
265
            $result = [];
266
            $hydrator = new Hydrator();
267
            while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
268
                $model = new $class();
269
                $hydrator->hydrate($row, $model);
270
                $result[] = $model;
271
            }
272
273
            return $result;
274
        };
275
    }
276
}
277