Passed
Push — develop ( fa47fb...7b664a )
by nguereza
01:45
created

EntityQuery::getConnection()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * Platine ORM
5
 *
6
 * Platine ORM provides a flexible and powerful ORM implementing a data-mapper pattern.
7
 *
8
 * This content is released under the MIT License (MIT)
9
 *
10
 * Copyright (c) 2020 Platine ORM
11
 *
12
 * Permission is hereby granted, free of charge, to any person obtaining a copy
13
 * of this software and associated documentation files (the "Software"), to deal
14
 * in the Software without restriction, including without limitation the rights
15
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
 * copies of the Software, and to permit persons to whom the Software is
17
 * furnished to do so, subject to the following conditions:
18
 *
19
 * The above copyright notice and this permission notice shall be included in all
20
 * copies or substantial portions of the Software.
21
 *
22
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
 * SOFTWARE.
29
 */
30
31
/**
32
 *  @file EntityQuery.php
33
 *
34
 *  The EntityQuery class
35
 *
36
 *  @package    Platine\Orm\Query
37
 *  @author Platine Developers Team
38
 *  @copyright  Copyright (c) 2020
39
 *  @license    http://opensource.org/licenses/MIT  MIT License
40
 *  @link   http://www.iacademy.cf
41
 *  @version 1.0.0
42
 *  @filesource
43
 */
44
45
declare(strict_types=1);
46
47
namespace Platine\Orm\Query;
48
49
use Closure;
50
use Platine\Database\Connection;
51
use Platine\Database\Query\ColumnExpression;
52
use Platine\Database\Query\Delete;
53
use Platine\Database\Query\Expression;
54
use Platine\Database\Query\HavingStatement;
55
use Platine\Database\Query\QueryStatement;
56
use Platine\Database\Query\Update;
57
use Platine\Database\ResultSet;
58
use Platine\Orm\Entity;
59
use Platine\Orm\EntityManager;
60
use Platine\Orm\Mapper\EntityMapper;
61
use Platine\Orm\Relation\RelationLoader;
62
63
/**
64
 * @class EntityQuery
65
 * @package Platine\Orm\Query
66
 */
67
class EntityQuery extends Query
68
{
69
    /**
70
     *
71
     * @var EntityManager
72
     */
73
    protected EntityManager $manager;
74
75
    /**
76
     *
77
     * @var EntityMapper
78
     */
79
    protected EntityMapper $mapper;
80
81
    /**
82
     * Whether the query state is locked
83
     * @var bool
84
     */
85
    protected bool $locked = false;
86
87
    /**
88
     * Create new instance
89
     * @param EntityManager $manager
90
     * @param EntityMapper $mapper
91
     * @param QueryStatement|null $queryStatement
92
     */
93
    public function __construct(
94
        EntityManager $manager,
95
        EntityMapper $mapper,
96
        ?QueryStatement $queryStatement = null
97
    ) {
98
        parent::__construct($queryStatement);
99
        $this->manager = $manager;
100
        $this->mapper = $mapper;
101
    }
102
103
    /**
104
     * Return the connection instance
105
     * @return Connection
106
     */
107
    public function getConnection(): Connection
108
    {
109
        return $this->manager->getConnection();
110
    }
111
112
    /**
113
     * Apply an filter(s) to the query
114
     * @param string|array<int, string>|array<string, mixed> $names
115
     * @return $this
116
     */
117
    public function filter($names): self
118
    {
119
        if (!is_array($names)) {
120
            $names = [$names];
121
        }
122
123
        $query = new Query($this->queryStatement);
124
        $filters = $this->mapper->getFilters();
125
126
        foreach ($names as $name => $args) {
127
            if (is_int($name)) {
128
                $name = $args;
129
                $args = null;
130
            }
131
132
            if (isset($filters[$name])) {
133
                $callback = $filters[$name];
134
                ($callback)($query, $args);
135
            }
136
        }
137
138
        return $this;
139
    }
140
141
    /**
142
     * Return one entity record
143
     * @param array<int, string> $columns
144
     * @param bool $primaryColumn
145
     * @return Entity|null
146
     */
147
    public function get(array $columns = [], bool $primaryColumn = true): ?Entity
148
    {
149
        $result = $this->query($columns, $primaryColumn)
150
                       ->fetchAssoc()
151
                       ->get();
152
153
        if ($result === false) {
154
            return null;
155
        }
156
157
        $class = $this->mapper->getEntityClass();
158
159
        return new $class(
160
            $this->manager,
161
            $this->mapper,
162
            $result,
163
            [],
164
            $this->isReadOnly(),
165
            false
166
        );
167
    }
168
169
    /**
170
     * Return the list of entities
171
     * @param array<int, string> $columns
172
     * @param bool $primaryColumn
173
     * @return array<int, Entity>
174
     */
175
    public function all(array $columns = [], bool $primaryColumn = true): array
176
    {
177
        $results = $this->query($columns, $primaryColumn)
178
                        ->fetchAssoc()
179
                        ->all();
180
181
        $entities = [];
182
183
        if (is_array($results)) {
184
            $class = $this->mapper->getEntityClass();
185
            $isReadOnly = $this->isReadOnly();
186
            $loaders = $this->getRelationLoaders($results);
187
188
            foreach ($results as $result) {
189
                $entities[] = new $class(
190
                    $this->manager,
191
                    $this->mapper,
192
                    $result,
193
                    $loaders,
194
                    $isReadOnly,
195
                    false
196
                );
197
            }
198
        }
199
200
        return $entities;
201
    }
202
203
    /**
204
     * Delete entity record
205
     * @param bool $force whether to bypass soft delete
206
     * @param array<int, string> $tables
207
     * @return int the affected rows
208
     */
209
    public function delete(bool $force = false, array $tables = []): int
210
    {
211
        return (int) $this->transaction(function (Connection $connection) use ($tables, $force) {
212
            if (!$force && $this->mapper->hasSoftDelete()) {
213
                return (new Update($connection, $this->mapper->getTable(), $this->queryStatement))
214
                        ->set([
215
                            $this->mapper->getSoftDeleteColumn() => date($this->manager->getDateFormat())
216
                        ]);
217
            }
218
            return (new Delete($connection, $this->mapper->getTable(), $this->queryStatement))->delete($tables);
219
        });
220
    }
221
222
    /**
223
     * Update entity record
224
     * @param array<int, string> $columns
225
     * @return int
226
     */
227
    public function update(array $columns = []): int
228
    {
229
        return (int) $this->transaction(function (Connection $connection) use ($columns) {
230
            if ($this->mapper->hasTimestamp()) {
231
                list(, $updatedAtColumn) = $this->mapper->getTimestampColumns();
232
                $columns[$updatedAtColumn] = date($this->manager->getDateFormat());
233
            }
234
            return (new Update($connection, $this->mapper->getTable(), $this->queryStatement))
235
                    ->set($columns);
236
        });
237
    }
238
239
    /**
240
     * Update by incremented an column
241
     * @param string|array<string, mixed> $column
242
     * @param mixed $value
243
     * @return int
244
     */
245
    public function increment($column, $value = 1): int
246
    {
247
        return (int) $this->transaction(function (Connection $connection) use ($column, $value) {
248
            if ($this->mapper->hasTimestamp()) {
249
                list(, $updatedAtColumn) = $this->mapper->getTimestampColumns();
250
                $this->queryStatement->addUpdateColumns([
251
                    $updatedAtColumn => date($this->manager->getDateFormat())
252
                ]);
253
            }
254
            return (new Update($connection, $this->mapper->getTable(), $this->queryStatement))
255
                    ->increment($column, $value);
256
        });
257
    }
258
259
    /**
260
     * Update by decremented an column
261
     * @param string|array<string, mixed> $column
262
     * @param mixed $value
263
     * @return int
264
     */
265
    public function decrement($column, $value = 1): int
266
    {
267
        return (int) $this->transaction(function (Connection $connection) use ($column, $value) {
268
            if ($this->mapper->hasTimestamp()) {
269
                list(, $updatedAtColumn) = $this->mapper->getTimestampColumns();
270
                $this->queryStatement->addUpdateColumns([
271
                    $updatedAtColumn => date($this->manager->getDateFormat())
272
                ]);
273
            }
274
            return (new Update($connection, $this->mapper->getTable(), $this->queryStatement))
275
                    ->decrement($column, $value);
276
        });
277
    }
278
279
    /**
280
     * Find entity record using primary key value
281
     * @param mixed $id
282
     *
283
     * @return Entity|null
284
     */
285
    public function find($id): ?Entity
286
    {
287
        if (is_array($id)) {
288
            foreach ($id as $pkColumn => $pkValue) {
289
                $this->where($pkColumn)->is($pkValue);
290
            }
291
        } else {
292
            $columns = $this->mapper->getPrimaryKey()->columns();
293
            $this->where($columns[0])->is($id);
294
        }
295
296
        return $this->get();
297
    }
298
299
/**
300
     * Find entities record using primary key values
301
     * @param mixed ...$ids
302
     *
303
     * @return array<int, Entity>
304
     */
305
    public function findAll(...$ids): array
306
    {
307
        if (is_array($ids[0])) {
308
            $keys = array_keys($ids[0]);
309
            $values = [];
310
311
            foreach ($ids as $pkValue) {
312
                foreach ($keys as $pkColumn) {
313
                    $values[$pkColumn][] = $pkValue[$pkColumn];
314
                }
315
            }
316
317
            foreach ($values as $pkColumn => $pkValues) {
318
                $this->where($pkColumn)->in($pkValues);
319
            }
320
        } else {
321
            $columns = $this->mapper->getPrimaryKey()->columns();
322
            $this->where($columns[0])->in($ids);
323
        }
324
325
        return $this->all();
326
    }
327
328
    /**
329
     *
330
     * @param string|Expression|Closure $column
331
     * @return mixed
332
     */
333
    public function column($column)
334
    {
335
        (new ColumnExpression($this->queryStatement))->column($column);
336
337
        return $this->executeAggregate();
338
    }
339
340
    /**
341
     *
342
     * @param string|Expression|Closure $column
343
     * @param bool $distinct
344
     * @return mixed
345
     */
346
    public function count($column = '*', bool $distinct = true)
347
    {
348
        (new ColumnExpression($this->queryStatement))->count($column, null, $distinct);
349
350
        return $this->executeAggregate();
351
    }
352
353
    /**
354
     *
355
     * @param string|Expression|Closure $column
356
     * @param bool $distinct
357
     * @return mixed
358
     */
359
    public function avg($column, bool $distinct = true)
360
    {
361
        (new ColumnExpression($this->queryStatement))->avg($column, null, $distinct);
362
363
        return $this->executeAggregate();
364
    }
365
366
    /**
367
     *
368
     * @param string|Expression|Closure $column
369
     * @param bool $distinct
370
     * @return mixed
371
     */
372
    public function sum($column, bool $distinct = true)
373
    {
374
        (new ColumnExpression($this->queryStatement))->sum($column, null, $distinct);
375
376
        return $this->executeAggregate();
377
    }
378
379
    /**
380
     *
381
     * @param string|Expression|Closure $column
382
     * @param bool $distinct
383
     * @return mixed
384
     */
385
    public function min($column, bool $distinct = true)
386
    {
387
        (new ColumnExpression($this->queryStatement))->min($column, null, $distinct);
388
389
        return $this->executeAggregate();
390
    }
391
392
    /**
393
     *
394
     * @param string|Expression|Closure $column
395
     * @param bool $distinct
396
     * @return mixed
397
     */
398
    public function max($column, bool $distinct = true)
399
    {
400
        (new ColumnExpression($this->queryStatement))->max($column, null, $distinct);
401
402
        return $this->executeAggregate();
403
    }
404
405
    /**
406
     * Clone of object
407
     */
408
    public function __clone()
409
    {
410
        parent::__clone();
411
        $this->havingStatement = new HavingStatement($this->queryStatement);
412
    }
413
414
    /**
415
     * Build the instance of EntityQuery
416
     * @return $this
417
     */
418
    protected function buildQuery(): self
419
    {
420
        $this->queryStatement->addTables([$this->mapper->getTable()]);
421
422
        return $this;
423
    }
424
425
    /**
426
     * Execute query and return the result set
427
     * @param array<int, string> $columns
428
     * @param bool $primaryColumn
429
     * @return ResultSet
430
     */
431
    protected function query(array $columns = [], bool $primaryColumn = true): ResultSet
432
    {
433
        if (!$this->buildQuery()->locked && !empty($columns) && $primaryColumn) {
434
            foreach ($this->mapper->getPrimaryKey()->columns() as $pkColumn) {
435
                $columns[] = $pkColumn;
436
            }
437
        }
438
439
        if ($this->mapper->hasSoftDelete()) {
440
            if (!$this->withSoftDeleted) {
441
                $this->where($this->mapper->getSoftDeleteColumn())->isNull();
442
            } elseif ($this->onlySoftDeleted) {
443
                $this->where($this->mapper->getSoftDeleteColumn())->isNotNull();
444
            }
445
        }
446
447
        $this->select($columns);
448
449
        $connection = $this->manager->getConnection();
450
        $driver = $connection->getDriver();
451
452
        return $connection->query(
453
            $driver->select($this->queryStatement),
454
            $driver->getParams()
455
        );
456
    }
457
458
    /**
459
     * Return the relations data loaders
460
     * @param array<int, mixed>|false $results
461
     * @return array<string, \Platine\Orm\Relation\RelationLoader>
462
     */
463
    protected function getRelationLoaders($results): array
464
    {
465
        if (empty($this->with) || empty($results)) {
466
            return [];
467
        }
468
469
        $loaders = [];
470
        $attributes = $this->getWithAttributes();
471
        $relations = $this->mapper->getRelations();
472
473
        foreach ($attributes['with'] as $with => $callback) {
474
            if (!isset($relations[$with])) {
475
                continue;
476
            }
477
478
            /** @var RelationLoader $loader */
479
            $loader = $relations[$with]->getLoader($this->manager, $this->mapper, [
480
                'results' => $results,
481
                'callback' => $callback,
482
                'with' => isset($attributes['extra'][$with])
483
                            ? $attributes['extra'][$with]
484
                            : [],
485
                'immediate' => $this->immediate
486
            ]);
487
488
            $loaders[$with] = $loader;
489
        }
490
491
        return $loaders;
492
    }
493
494
    /**
495
     * Execute the aggregate
496
     * @return mixed
497
     */
498
    protected function executeAggregate()
499
    {
500
        $this->queryStatement->addTables([$this->mapper->getTable()]);
501
502
        if ($this->mapper->hasSoftDelete()) {
503
            if (!$this->withSoftDeleted) {
504
                $this->where($this->mapper->getSoftDeleteColumn())->isNull();
505
            } elseif ($this->onlySoftDeleted) {
506
                $this->where($this->mapper->getSoftDeleteColumn())->isNotNull();
507
            }
508
        }
509
510
        $connection = $this->manager->getConnection();
511
        $driver = $connection->getDriver();
512
513
        return $connection->column(
514
            $driver->select($this->queryStatement),
515
            $driver->getParams()
516
        );
517
    }
518
519
    /**
520
     * Check if the current entity query is read only
521
     * @return bool
522
     */
523
    protected function isReadOnly(): bool
524
    {
525
        return !empty($this->queryStatement->getJoins());
526
    }
527
528
    /**
529
     * Execute the transaction
530
     * @param Closure $callback
531
     *
532
     * @return mixed
533
     */
534
    protected function transaction(Closure $callback)
535
    {
536
        return $this->manager->getConnection()
537
                             ->transaction($callback);
538
    }
539
}
540