Model::copy()   B
last analyzed

Complexity

Conditions 5
Paths 6

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 8.7624
c 0
b 0
f 0
cc 5
eloc 12
nc 6
nop 4
1
<?php
2
namespace Bedd\Doctrine\Dbal;
3
4
use \Doctrine\DBAL\Schema;
5
use \Doctrine\DBAL\Query\QueryBuilder;
6
7
/**
8
 * Model
9
 */
10
class Model
11
{
12
    /**
13
     * The DBAL connection
14
     *
15
     * @var \Doctrine\DBAL\Connection
16
     */
17
    protected $conn = null;
18
19
    /**
20
     * Shortcut for the return of getSchemaManager()
21
     *
22
     * @var \Doctrine\DBAL\Schema\AbstractSchemaManager
23
     */
24
    protected $sm = null;
25
26
    /**
27
     * Table-Name
28
     *
29
     * @var string
30
     */
31
    protected $table_name = '';
32
33
    /**
34
     * Quoted Table-Name
35
     *
36
     * @var string
37
     */
38
    protected $quoted_table_name = '';
39
40
    /**
41
     * Array with the Columns
42
     *
43
     * @var \Doctrine\DBAL\Schema\Column[]
44
     */
45
    protected $columns = [];
46
47
    /**
48
     * Shortcut with Column-Types
49
     *
50
     * @var array
51
     */
52
    protected $column_types = [];
53
54
    /**
55
     * Caching variable if the table has an auto increment column
56
     *
57
     * @var bool
58
     */
59
    protected $has_autoincrement = null;
60
61
    /**
62
     * Original return type for the read-method
63
     *
64
     * @var string
65
     */
66
    const READ_RETURN_ORIGINAL = 'original';
67
68
    /**
69
     * Simple return type for the read-method
70
     *
71
     * @var string
72
     */
73
    const READ_RETURN_SIMPLE = 'simple';
74
75
    /**
76
     * Complex return type for the read-method
77
     *
78
     * @var string
79
     */
80
    const READ_RETURN_COMPLEX = 'complex';
81
82
    /**
83
     * constructor
84
     *
85
     * @param string $table_name
86
     * @param Connection $conn
87
     */
88
    public function __construct($table_name, \Doctrine\DBAL\Connection $conn)
89
    {
90
        $this->conn = $conn;
91
        $this->table_name = $table_name;
92
        $this->quoted_table_name = $this->conn->quoteIdentifier($this->table_name);
93
        $this->sm = $this->conn->getSchemaManager();
94
        if (!$this->sm->tablesExist([$table_name])) {
95
            throw Schema\SchemaException::tableDoesNotExist($table_name);
96
        }
97
        foreach ($this->sm->listTableColumns($this->table_name) as $colum) {
98
            $this->columns[$colum->getName()] = $colum;
99
            $this->column_types[$colum->getName()] = $colum->getType()->getName();
100
        }
101
    }
102
103
    /**
104
     * Tells of the table has an auto increment column
105
     *
106
     * @return boolean
107
     */
108
    public function hasAutoIncrement()
109
    {
110
        if ($this->has_autoincrement === null) {
111
            $this->has_autoincrement = false;
112
            foreach ($this->columns as $column) {
113
                if ($column->getAutoincrement()) {
114
                    $this->has_autoincrement = true;
115
                    break;
116
                }
117
            }
118
        }
119
        return $this->has_autoincrement;
120
    }
121
122
    /**
123
     * Returns the Column with $column_name
124
     *
125
     * @param string $column_name
126
     *
127
     * @return \Doctrine\DBAL\Schema\Column or null if the Column doesn't exist
128
     */
129
    protected function getColumn($column_name)
130
    {
131
        return isset($this->columns[$column_name]) ? $this->columns[$column_name] : null;
132
    }
133
134
    /**
135
     * Creates a new datarow
136
     *
137
     * @param array $data
138
     *
139
     * @return boolean|string false on error, true on success or an string with the last insert id if the table has an auto increment column
140
     */
141
    public function create(array $data)
142
    {
143
        $return = false;
0 ignored issues
show
Unused Code introduced by
$return is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
144
        $qb = $this->conn->createQueryBuilder()->insert($this->quoted_table_name);
145 View Code Duplication
        foreach ($data as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
146
            if ($this->getColumn($column) === null) {
147
                throw Schema\SchemaException::columnDoesNotExist($column, $this->table_name);
148
            }
149
            $qb->setValue($this->conn->quoteIdentifier($column), $qb->createNamedParameter($value));
150
        }
151
        $return = (bool) $qb->execute();
152
        if ($this->hasAutoIncrement()) {
153
            $return = $this->conn->lastInsertId();
154
        }
155
        return $return;
156
    }
157
158
    /**
159
     * read $columns from $table_name
160
     *
161
     * @param array $columns
162
     * @param array $filters
163
     * @param array $limit
164
     * @param array $order_by
165
     * @param string $return_type
166
     *
167
     * @return array
168
     */
169
    public function read(array $columns = [], array $filters = [], array $limit = [], array $order_by = [], $return_type = self::READ_RETURN_COMPLEX)
170
    {
171
        $return = [];
0 ignored issues
show
Unused Code introduced by
$return is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
172
        $alias_mapping = [];
173
        $qb = $this->conn->createQueryBuilder();
174
        if (empty($columns)) {
175
            $columns = $this->getColumnNames();
176
        }
177
        foreach ($columns as $column) {
178
            $column_name = null;
179
            $alias = null;
180
            $expr = [];
181
            if (is_string($column)) {
182
                $column_name = $alias = $column;
183 View Code Duplication
                if (strpos($alias, ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
                    $alias = explode(':', $alias);
185
                    $alias = array_pop($alias);
186
                }
187
            } elseif (is_array($column)) {
188
                list($column_name, $alias) = $column;
189
            }
190 View Code Duplication
            if (strpos($column_name, ':') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
191
                $expr = explode(':', $column_name);
192
                $column_name = array_pop($expr);
193
            }
194
            $alias_mapping[$alias] = $column_name;
195
            if ($this->getColumn($column_name) === null) {
196
                throw Schema\SchemaException::columnDoesNotExist($column_name, $this->table_name);
197
            }
198
            $column_expr = $this->conn->quoteIdentifier($column_name);
199
            $expr = array_reverse($expr);
200
            foreach ($expr as $ex) {
201
                if (in_array($ex, ['max', 'min', 'avg', 'count', 'sum', 'lower', 'upper'])) {
202
                    $column_expr = call_user_func([$this->conn->getDatabasePlatform(), 'get'.ucfirst($ex).'Expression'], $column_expr);
203
                } else {
204
                    throw QueryBuilderException::expressionTypeDoesNotExist($ex);
205
                }
206
            }
207
            if ($column_name !== $alias || !empty($expr)) {
208
                $column_expr .= ' AS '.$this->conn->quoteIdentifier($alias);
209
            }
210
            $qb->addSelect($column_expr);
211
        }
212
        $qb->from($this->quoted_table_name);
213
        $this
214
            ->buildWhere($qb, $filters)
215
            ->buildOrderBy($qb, $order_by)
216
            ->buildLimit($qb, $limit)
217
        ;
218
        $res = $qb->execute();
219
        $return = $res->fetchAll(\PDO::FETCH_ASSOC);
220
        if (in_array($return_type, [self::READ_RETURN_SIMPLE, self::READ_RETURN_COMPLEX])) {
221
            foreach ($return as $index => $row) {
222
                foreach ($row as $column => $value) {
223
                    if ($return_type === self::READ_RETURN_SIMPLE && !$this->isSimpleType($alias_mapping[$column])) {
224
                        continue;
225
                    }
226
                    $return[$index][$column] = $this->conn->convertToPHPValue($value, $this->column_types[$alias_mapping[$column]]);
227
                }
228
            }
229
        }
230
        return $return;
231
    }
232
233
    /**
234
     * Update table rows
235
     *
236
     * @param array $data
237
     * @param array $filters
238
     *
239
     * @return integer The number of rows
240
     */
241
    public function update(array $data, array $filters = [])
242
    {
243
        $qb = $this->conn->createQueryBuilder()->update($this->quoted_table_name);
244 View Code Duplication
        foreach ($data as $column => $value) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
245
            if ($this->getColumn($column) === null) {
246
                throw Schema\SchemaException::columnDoesNotExist($column, $this->table_name);
247
            }
248
            $qb->set($this->conn->quoteIdentifier($column), $qb->createNamedParameter($value));
249
        }
250
        $this->buildWhere($qb, $filters);
251
        return $qb->execute();
252
    }
253
254
    /**
255
     * Delete rows
256
     *
257
     * @param array $filters
258
     *
259
     * @return integer The number of rows
260
     */
261
    public function delete(array $filters = [])
262
    {
263
        $qb = $this->conn->createQueryBuilder()->delete($this->quoted_table_name);
264
        $this->buildWhere($qb, $filters);
265
        return $qb->execute();
266
    }
267
268
    /**
269
     * copy some records and maybe change some values
270
     *
271
     * @param array $filters
272
     * @param array $limit
273
     * @param array $order_by
274
     * @param array $changes
275
     *
276
     * @return array the result of each $this->create()
277
     */
278
    public function copy(array $filters = [], array $limit = [], array $order_by = [], array $changes = [])
279
    {
280
        $return = [];
281
        //read
282
        $data = $this->read($this->getColumnNames(), $filters, $limit, $order_by, self::READ_RETURN_SIMPLE);
283
        foreach ($data as $row) {
284
            foreach ($row as $column_name => $value) {
285
                $column = $this->getColumn($column_name);
286
                //changes
287
                if (isset($changes[$column_name])) {
288
                    $row[$column_name] = $changes[$column_name];
289
                }
290
                //remove autoincrements
291
                if ($column->getAutoincrement()) {
292
                    unset($row[$column_name]);
293
                }
294
            }
295
            $return[] = $this->create($row);
296
        }
297
        return $return;
298
    }
299
300
    /**
301
     * Builds the WHERE clause from $filter
302
     *
303
     * @param QueryBuilder $qb
304
     * @param array $filters should be an array with arrays wich contains 3 datas
305
     * [
306
     *    ['column_name', 'expr_type', 'value'],
307
     *    [],
308
     *    ...
309
     * ]
310
     * expr_types: 'eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'like', 'in', 'notIn', 'notLike'
311
     *
312
     * @return self
313
     */
314
    protected function buildWhere(QueryBuilder $qb, array $filters = [])
315
    {
316
        if (!empty($filters)) {
317
            $expr = $qb->expr()->andX();
318
            foreach ($filters as $f) {
319
                $column = $f[0];
320
                $expr_type = $f[1];
321
                $value = isset($f[2]) ? $f[2] : null;
322
                $type = \PDO::PARAM_STR;
323
                if ($this->getColumn($column) === null) {
324
                    throw Schema\SchemaException::columnDoesNotExist($column, $this->table_name);
325
                }
326
                if (!in_array($expr_type, $this->getExpressionTypes())) {
327
                    throw QueryBuilderException::expressionTypeDoesNotExist($expr_type);
328
                }
329
                if (in_array($expr_type, ['in', 'notIn']) && is_array($value)) {
330
                    switch ($this->column_types[$column]) {
331
                        case 'integer':
332
                            $type = \Doctrine\DBAL\Connection::PARAM_INT_ARRAY;
333
                            break;
334
                        case 'string':
335
                            $type = \Doctrine\DBAL\Connection::PARAM_STR_ARRAY;
336
                            break;
337
                    }
338
                }
339
                $expr->add($qb->expr()->$expr_type($this->conn->quoteIdentifier($column), $qb->createNamedParameter($value, $type)));
340
            }
341
            $qb->where($expr);
342
        }
343
        return $this;
344
    }
345
346
    /**
347
     * Build up dynamilcy the LIMIT part
348
     *
349
     * @param QueryBuilder $qb
350
     * @param array $limit
351
     *
352
     * @return self
353
     */
354
    protected function buildLimit(QueryBuilder $qb, array $limit = [])
355
    {
356
        switch (count($limit)) {
357
            case 2:
358
                $qb->setFirstResult((int) $limit[0]);
359
                $qb->setMaxResults((int) $limit[1]);
360
                break;
361
            case 1:
362
                $qb->setMaxResults((int) $limit[0]);
363
                break;
364
        }
365
        return $this;
366
    }
367
368
    /**
369
     * Builds the ORDER BY part
370
     *
371
     * @param QueryBuilder $qb
372
     * @param array $order_by
373
     *
374
     * @return self
375
     */
376
    protected function buildOrderBy(QueryBuilder $qb, array $order_by = [])
377
    {
378
        foreach ($order_by as $order) {
379
            $column = null;
380
            $direction = 'ASC';
381
            if (is_string($order)) {
382
                $column = $order;
383
            } elseif (is_array($order) && count($order) === 2) {
384
                list($column, $direction) = $order;
385
            }
386
            if ($column === null || $this->getColumn($column) === null) {
387
                throw Schema\SchemaException::columnDoesNotExist($column, $this->table_name);
388
            }
389
            if (!in_array($direction, ['ASC', 'DESC'])) {
390
                throw QueryBuilderException::orderByDirectionDoesNotExist($direction);
391
            }
392
            $qb->addOrderBy($this->conn->quoteIdentifier($column), $direction);
393
        }
394
        return $this;
395
    }
396
397
    /**
398
     * Returns the table-Name
399
     *
400
     * @return string
401
     */
402
    public function getTableName()
403
    {
404
        return $this->table_name;
405
    }
406
407
    /**
408
     * List all possible expression types
409
     *
410
     * @return string[]
411
     */
412
    public function getExpressionTypes()
413
    {
414
        return ['eq', 'neq', 'lt', 'lte', 'gt', 'gte', 'like', 'in', 'notIn', 'notLike', 'isNull', 'isNotNull'];
415
    }
416
417
    /**
418
     * List all possible column names
419
     * @return string[]
420
     */
421
    public function getColumnNames()
422
    {
423
        return array_keys($this->columns);
424
    }
425
426
    /**
427
     * return true if the column type of $column_name is a simple type like string, integer, ...
428
     * @param string $column_name
429
     * @return boolean
430
     */
431
    public function isSimpleType($column_name)
432
    {
433
        return in_array($this->column_types[$column_name], ['string', 'integer']);
434
    }
435
}
436