Completed
Push — master ( 7ae754...4444a4 )
by Nate
01:19
created

DomainsQuery::createModel()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 1
crap 2
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/domains/license
6
 * @link       https://www.flipboxfactory.com/software/domains/
7
 */
8
9
namespace flipbox\domains\db;
10
11
use craft\db\Connection as CraftConnection;
12
use craft\db\FixedOrderExpression;
13
use craft\db\Query;
14
use craft\db\QueryAbortedException;
15
use craft\helpers\StringHelper;
16
use flipbox\domains\Domains as DomainsPlugin;
17
use flipbox\domains\fields\Domains;
18
use flipbox\domains\models\Domain;
19
use flipbox\spark\db\traits\AuditAttributes;
20
use flipbox\spark\db\traits\PopulateModel;
21
use yii\base\ArrayableTrait;
22
use yii\base\Exception;
23
use yii\db\Connection;
24
25
class DomainsQuery extends Query
26
{
27
    use ArrayableTrait, PopulateModel, traits\Attributes, AuditAttributes;
28
29
    /**
30
     * @var bool Whether results should be returned in the order specified by [[domain]].
31
     */
32
    public $fixedOrder = false;
33
34
    /**
35
     * @inheritdoc
36
     */
37
    public $orderBy = 'sortOrder';
38
39
    /**
40
     * @var Domain[]|null The cached query result
41
     * @see setCachedResult()
42
     */
43
    private $result;
44
45
    /**
46
     * @var Domain[]|null The criteria params that were set when the cached query result was set
47
     * @see setCachedResult()
48
     */
49
    private $resultCriteria;
50
51
    /**
52
     * @var Domains
53
     */
54
    private $field;
55
56
    /**
57
     * @inheritdoc
58
     */
59
    public function __construct(Domains $domains, $config = [])
60
    {
61
        $this->field = $domains;
62
        parent::__construct($config);
63
    }
64
65
    /**
66
     * @inheritdoc
67
     */
68
    public function init()
69
    {
70
        parent::init();
71
72
        if ($this->select === null) {
73
            // Use ** as a placeholder for "all the default columns"
74
            $this->select = ['*'];
75
        }
76
77
        // Set table name
78
        if ($this->from === null) {
79
            $fieldService = DomainsPlugin::getInstance()->getField();
80
            $this->from([
81
                $fieldService->getTableAlias($this->field) . ' ' . $fieldService->getTableName($this->field)
82
            ]);
83
        }
84
    }
85
86
    /**
87
     * @return Domains
88
     */
89
    public function getField(): Domains
90
    {
91
        return $this->field;
92
    }
93
94
    /**
95
     * @inheritdoc
96
     */
97
    protected function getIndexBy()
98
    {
99
        return $this->indexBy;
100
    }
101
102
    /**
103
     * @inheritdoc
104
     * return static
105
     */
106
    public function fixedOrder(bool $value = true)
107
    {
108
        $this->fixedOrder = $value;
109
110
        return $this;
111
    }
112
113
    // Query preparation/execution
114
    // -------------------------------------------------------------------------
115
116
    /**
117
     * @inheritdoc
118
     *
119
     * @throws QueryAbortedException if it can be determined that there won’t be any results
120
     */
121
    public function prepare($builder)
122
    {
123
        // Is the query already doomed?
124
        if ($this->domain !== null && empty($this->domain)) {
125
            throw new QueryAbortedException();
126
        }
127
128
        // Build the query
129
        // ---------------------------------------------------------------------
130
        $this->applyConditions();
131
        $this->applyAuditAttributeConditions();
132
        $this->applyOrderByParams($builder->db);
133
134
        return parent::prepare($builder);
135
    }
136
137
    /**
138
     * Applies the 'fixedOrder' and 'orderBy' params to the query being prepared.
139
     *
140
     * @param Connection|null $db The database connection used to generate the SQL statement.
141
     *                            If this parameter is not given, the `db` application component will be used.
142
     *
143
     * @throws Exception if the DB connection doesn't support fixed ordering
144
     * @throws QueryAbortedException
145
     */
146
    private function applyOrderByParams(Connection $db)
147
    {
148
        if ($this->orderBy === null) {
149
            return;
150
        }
151
152
        // Any other empty value means we should set it
153
        if (empty($this->orderBy)) {
154
            $this->applyEmptyOrderByParams($db);
155
        }
156
157
        $this->orderBy($this->orderBy);
158
    }
159
160
    /**
161
     * @param Connection $db
162
     * @throws Exception
163
     * @throws QueryAbortedException
164
     */
165
    private function applyEmptyOrderByParams(Connection $db)
166
    {
167
        if ($this->fixedOrder) {
168
            $domains = $this->domain;
169
            if (!is_array($domains)) {
170
                $domains = is_string($domains) ? StringHelper::split($domains) : [$domains];
171
            }
172
173
            if (empty($domains)) {
174
                throw new QueryAbortedException;
175
            }
176
177
            // Order the elements in the exact order that the Search service returned them in
178
            if (!$db instanceof CraftConnection) {
179
                throw new Exception('The database connection doesn\'t support fixed ordering.');
180
            }
181
182
            $this->orderBy = [new FixedOrderExpression('domain', $domains, $db)];
0 ignored issues
show
Documentation Bug introduced by
It seems like array(new \craft\db\Fixe...omain', $domains, $db)) of type array<integer,object<cra...ixedOrderExpression>"}> is incompatible with the declared type string of property $orderBy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
183
        } else {
184
            $this->orderBy = ['dateCreated' => SORT_DESC];
0 ignored issues
show
Documentation Bug introduced by
It seems like array('dateCreated' => SORT_DESC) of type array<string,integer,{"dateCreated":"integer"}> is incompatible with the declared type string of property $orderBy.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
185
        }
186
    }
187
188
    /**
189
     * @inheritdoc
190
     */
191
    public function count($q = '*', $db = null)
192
    {
193
        // Cached?
194
        if (($cachedResult = $this->getCachedResult()) !== null) {
195
            return count($cachedResult);
196
        }
197
198
        return parent::count($q, $db) ?: 0;
199
    }
200
201
    /**
202
     * @inheritdoc
203
     */
204
    public function all($db = null)
205
    {
206
        // Cached?
207
        if (($cachedResult = $this->getCachedResult()) !== null) {
208
            return $cachedResult;
209
        }
210
211
        return parent::all($db);
212
    }
213
214
    /**
215
     * @inheritdoc
216
     */
217
    public function one($db = null)
218
    {
219
        // Cached?
220
        if (($cachedResult = $this->getCachedResult()) !== null) {
221
            // Conveniently, reset() returns false on an empty array, just like one() should do for an empty result
222
            return reset($cachedResult);
0 ignored issues
show
Bug Compatibility introduced by
The expression reset($cachedResult); of type flipbox\domains\models\Domain|false adds the type flipbox\domains\models\Domain to the return on line 222 which is incompatible with the return type declared by the interface yii\db\QueryInterface::one of type array|boolean.
Loading history...
223
        }
224
225
        $row = parent::one($db);
226
227
        if ($row === false) {
228
            return false;
229
        }
230
231
        return $this->createModel($row);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $this->createModel($row); (flipbox\domains\models\Domain) is incompatible with the return type declared by the interface yii\db\QueryInterface::one of type array|boolean.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
232
    }
233
234
    /**
235
     * Executes the query and returns a single row of result at a given offset.
236
     *
237
     * @param int $n The offset of the row to return. If [[offset]] is set, $offset will be added to it.
238
     * @param Connection|null $db The database connection used to generate the SQL statement.
239
     *                            If this parameter is not given, the `db` application component will be used.
240
     *
241
     * @return Domain|array|bool The element or row of the query result. False is returned if the query
242
     * results in nothing.
243
     */
244
    public function nth(int $n, Connection $db = null)
245
    {
246
        // Cached?
247
        if (($cachedResult = $this->getCachedResult()) !== null) {
248
            return $cachedResult[$n] ?? false;
0 ignored issues
show
Bug Compatibility introduced by
The expression $cachedResult[$n] ?? false; of type flipbox\domains\models\Domain|false adds the type flipbox\domains\models\Domain to the return on line 248 which is incompatible with the return type of the parent method craft\db\Query::nth of type false|array.
Loading history...
249
        }
250
251
        return parent::nth($n, $db);
252
    }
253
254
    /**
255
     * Returns the resulting domains set by [[setCachedResult()]], if the criteria params haven’t changed since then.
256
     *
257
     * @return Domain[]|null The resulting domains, or null if setCachedResult() was never called or the criteria has
258
     * changed
259
     * @see setCachedResult()
260
     */
261
    public function getCachedResult()
262
    {
263
        if ($this->result === null) {
264
            return null;
265
        }
266
267
        // Make sure the criteria hasn't changed
268
        if ($this->resultCriteria !== $this->getCriteria()) {
269
            $this->result = null;
270
271
            return null;
272
        }
273
274
        return $this->result;
275
    }
276
277
    /**
278
     * Sets the resulting domains.
279
     *
280
     * If this is called, [[all()]] will return these domains rather than initiating a new SQL query,
281
     * as long as none of the parameters have changed since setCachedResult() was called.
282
     *
283
     * @param Domain[] $elements The resulting elements.
284
     *
285
     * @see getCachedResult()
286
     */
287
    public function setCachedResult(array $elements)
288
    {
289
        $this->result = $elements;
290
        $this->resultCriteria = $this->getCriteria();
291
    }
292
293
    /**
294
     * Returns an array of the current criteria attribute values.
295
     *
296
     * @return array
297
     */
298
    public function getCriteria(): array
299
    {
300
        return $this->toArray($this->criteriaAttributes(), [], false);
301
    }
302
303
    /**
304
     * Returns the query's criteria attributes.
305
     *
306
     * @return string[]
307
     */
308
    public function criteriaAttributes(): array
309
    {
310
        // By default, include all public, non-static properties that were defined by a sub class, and certain ones
311
        // in this class
312
        $class = new \ReflectionClass($this);
313
        $names = [];
314
315
        foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
316
            if (!$property->isStatic()) {
317
                $dec = $property->getDeclaringClass();
318
                if (($dec->getName() === self::class || $dec->isSubclassOf(self::class))
319
                ) {
320
                    $names[] = $property->getName();
321
                }
322
            }
323
        }
324
325
        return $names;
326
    }
327
328
    /**
329
     * @param $row
330
     *
331
     * @return Domain
332
     */
333
    function createModel($row): Domain
334
    {
335
        return new Domain($this->getField(), $row);
336
    }
337
}
338