Query   A
last analyzed

Complexity

Total Complexity 32

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 32
eloc 84
c 1
b 0
f 0
dl 0
loc 206
rs 9.84

15 Methods

Rating   Name   Duplication   Size   Complexity  
A paginate() 0 14 2
A getStatement() 0 5 1
A resetGuards() 0 5 1
A first() 0 5 1
A chunk() 0 23 5
A get() 0 5 1
A applyGuards() 0 12 4
A setGuards() 0 11 3
A load() 0 14 3
A __construct() 0 7 1
A count() 0 7 1
A subSelectForJoinWith() 0 7 1
A __call() 0 8 3
A joinWith() 0 10 2
A __clone() 0 7 3
1
<?php
2
declare(strict_types=1);
3
4
namespace Sirius\Orm;
5
6
use Sirius\Orm\Collection\Collection;
7
use Sirius\Orm\Collection\PaginatedCollection;
8
use Sirius\Sql\Bindings;
9
use Sirius\Sql\Select;
10
11
class Query extends Select
12
{
13
    /**
14
     * @var Mapper
15
     */
16
    protected $mapper;
17
18
    /**
19
     * @var array
20
     */
21
    protected $load = [];
22
23
    /**
24
     * @var array
25
     */
26
    protected $guards = [];
27
28
    /**
29
     * @var array
30
     */
31
    protected $scopes = [];
32
33
    public function __construct(Mapper $mapper, Bindings $bindings = null, string $indent = '')
34
    {
35
        parent::__construct($mapper->getReadConnection(), $bindings, $indent);
36
        $this->mapper = $mapper;
37
        $this->from($this->mapper->getTableReference());
38
        $this->resetColumns();
39
        $this->columns($this->mapper->getTableAlias(true) . '.*');
40
    }
41
42
    public function __call(string $method, array $params)
43
    {
44
        $scope = $this->mapper->getQueryScope($method);
45
        if ($scope && is_callable($scope)) {
46
            return $scope($this, ...$params);
47
        }
48
49
        return parent::__call($method, $params);
50
    }
51
52
    public function __clone()
53
    {
54
        $vars = get_object_vars($this);
55
        unset($vars['mapper']);
56
        foreach ($vars as $name => $prop) {
57
            if (is_object($prop)) {
58
                $this->$name = clone $prop;
59
            }
60
        }
61
    }
62
63
64
    public function load(...$relations): self
65
    {
66
        foreach ($relations as $relation) {
67
            if (is_array($relation)) {
68
                $name     = key($relation);
69
                $callback = current($relation);
70
            } else {
71
                $name     = $relation;
72
                $callback = null;
73
            }
74
            $this->load[$name] = $callback;
75
        }
76
77
        return $this;
78
    }
79
80
    public function joinWith($name): Query
81
    {
82
        if (! $this->mapper->hasRelation($name)) {
83
            throw new \InvalidArgumentException(
84
                sprintf("Relation %s, not defined for %s", $name, $this->mapper->getTable())
85
            );
86
        }
87
        $relation = $this->mapper->getRelation($name);
88
89
        return $relation->joinSubselect($this, $name);
90
    }
91
92
    public function subSelectForJoinWith(): Query
93
    {
94
        $subselect = new Query($this->mapper, $this->bindings, $this->indent . '    ');
95
        $subselect->resetFrom();
96
        $subselect->resetColumns();
97
98
        return $subselect;
99
    }
100
101
    public function first()
102
    {
103
        $row = $this->fetchOne();
104
105
        return $this->mapper->newEntityFromRow($row, $this->load);
106
    }
107
108
    public function get(): Collection
109
    {
110
        return $this->mapper->newCollectionFromRows(
111
            $this->connection->fetchAll($this->getStatement(), $this->getBindValues()),
112
            $this->load
113
        );
114
    }
115
116
    public function paginate($perPage, $page): PaginatedCollection
117
    {
118
        /** @var Query $countQuery */
119
        $countQuery = clone $this;
120
        $total      = $countQuery->count();
121
122
        if ($total == 0) {
123
            $this->mapper->newPaginatedCollectionFromRows([], $total, $perPage, $page, $this->load);
124
        }
125
126
        $this->perPage($perPage);
127
        $this->page($page);
128
129
        return $this->mapper->newPaginatedCollectionFromRows($this->fetchAll(), $total, $perPage, $page, $this->load);
130
    }
131
132
    /**
133
     * Executes the query with a limit of $size and applies the callback on each entity
134
     * The callback can change the DB in such a way that you can end up in an infinite loop
135
     * (depending on the sorting) so we set a limit on the number of chunks that can be processed
136
     *
137
     * @param int $size
138
     * @param callable $callback
139
     * @param int $limit
140
     */
141
    public function chunk(int $size, callable $callback, int $limit = 100000)
142
    {
143
        if (!$this->orderBy->build()) {
144
            $this->orderBy(...(array) $this->mapper->getPrimaryKey());
145
        }
146
147
        $run = 0;
148
        while ($run < $limit) {
149
            $query = clone $this;
150
            $query->limit($size);
151
            $query->offset($run * $size);
152
153
            $results = $query->get();
154
155
            if (count($results) === 0) {
156
                break;
157
            }
158
159
            foreach ($results as $entity) {
160
                $callback($entity);
161
            }
162
163
            $run++;
164
        }
165
    }
166
167
    public function count()
168
    {
169
        $this->resetOrderBy();
170
        $this->resetColumns();
171
        $this->columns('COUNT(*) AS total');
172
173
        return (int)$this->fetchValue();
174
    }
175
176
    public function setGuards(array $guards)
177
    {
178
        foreach ($guards as $column => $value) {
179
            if (is_int($column)) {
180
                $this->guards[] = $value;
181
            } else {
182
                $this->guards[$column] = $value;
183
            }
184
        }
185
186
        return $this;
187
    }
188
189
    public function resetGuards()
190
    {
191
        $this->guards = [];
192
193
        return;
194
    }
195
196
    protected function applyGuards()
197
    {
198
        if (empty($this->guards)) {
199
            return;
200
        }
201
202
        $this->groupCurrentWhere();
203
        foreach ($this->guards as $column => $value) {
204
            if (is_int($column)) {
205
                $this->where($value);
206
            } else {
207
                $this->where($column, $value);
208
            }
209
        }
210
    }
211
212
    public function getStatement(): string
213
    {
214
        $this->applyGuards();
215
216
        return parent::getStatement();
217
    }
218
}
219