Passed
Push — master ( ae9b6b...aec4ed )
by Wilmer
11:33 queued 09:14
created

ActiveDataProvider::prepareTotalCount()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 10
ccs 0
cts 10
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\ActiveRecord;
6
7
use Yiisoft\Db\Connection\Connection;
8
use Yiisoft\Db\Exception\InvalidConfigException;
9
use Yiisoft\Db\Query\QueryInterface;
10
11
/**
12
 * ActiveDataProvider implements a data provider based on {@see \Yiisoft\Db\Querys\Query} and
13
 * {@see \Yiisoft\ActiveRecord\ActiveQuery}.
14
 *
15
 * ActiveDataProvider provides data by performing DB queries using {@see query}.
16
 *
17
 * The following is an example of using ActiveDataProvider to provide ActiveRecord instances:
18
 *
19
 * ```php
20
 * $provider = new ActiveDataProvider(
21
 *      Post::find()
22
 * );
23
 *
24
 * $provider->pagination' => [
25
 *     'pageSize' => 20,
26
 * ];
27
 *
28
 *
29
 * // get the posts in the current page
30
 * $posts = $provider->getModels(); // or $provider->models
31
 * ```
32
 *
33
 * And the following example shows how to use ActiveDataProvider without ActiveRecord:
34
 *
35
 * ```php
36
 * $query = new Query();
37
 * $provider = new ActiveDataProvider(
38
 *     Yii::get('db'),
39
 *     $query->from('post')
40
 * );
41
 * $provider->pagination' => [
42
 *     'pageSize' => 20,
43
 * ];
44
 *
45
 *
46
 * // get the posts in the current page
47
 * $posts = $provider->getModels();  // or $provider->models
48
 * ```
49
 *
50
 * For more details and usage information on ActiveDataProvider, see the
51
 * [guide article on data providers](guide:output-data-providers).
52
 */
53
class ActiveDataProvider
54
{
55
    /**
56
     * @var QueryInterface|null the query that is used to fetch data models and {@see totalCount}
57
     *
58
     * if it is not explicitly set.
59
     */
60
    public ?QueryInterface $query = null;
61
62
    /**
63
     * @var string|callable the column that is used as the key of the data models.
64
     *
65
     * This can be either a column name, or a callable that returns the key value of a given data model.
66
     *
67
     * If this is not set, the following rules will be used to determine the keys of the data models:
68
     *
69
     * - If {@see query} is an {@see \Yiisoft\ActiveRecord\ActiveQuery} instance, the primary keys of
70
     * {@see \Yiisoft\ActiveRecord\ActiveQuery::modelClass} will be used.
71
     *
72
     * - Otherwise, the keys of the {@see models} array will be used.
73
     *
74
     * @see getKeys()
75
     */
76
    public $key;
77
78
    /**
79
     * @var Connection|null the DB connection object or the application component ID of the DB connection.
80
     */
81
    public ?Connection $db = null;
82
83
84
    /**
85
     * Create the ActiveDataProvider object.
86
     *
87
     * @param Connection $db database connection.
88
     * @param QueryInterface $query query to be executed
89
     */
90
    public function __construct(Connection $db, QueryInterface $query)
91
    {
92
        $this->db = $db;
93
        $this->query = $query;
94
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99
    protected function prepareModels(): array
100
    {
101
        $query = $this->prepareQuery();
102
        if ($query->emulateExecution) {
0 ignored issues
show
Bug introduced by
Accessing emulateExecution on the interface Yiisoft\Db\Query\QueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
103
            return [];
104
        }
105
        return $query->all($this->db);
0 ignored issues
show
Unused Code introduced by
The call to Yiisoft\Db\Query\QueryInterface::all() has too many arguments starting with $this->db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

105
        return $query->/** @scrutinizer ignore-call */ all($this->db);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
106
    }
107
108
    /**
109
     * Prepares the sql-query that will get the data for current page.
110
     *
111
     * @throws InvalidConfigException
112
     *
113
     * @return QueryInterface
114
     */
115
    public function prepareQuery(): QueryInterface
116
    {
117
        if (!$this->query instanceof QueryInterface) {
118
            throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. Yiisoft\Db\Query or its subclasses.');
119
        }
120
        $query = clone $this->query;
121
        if (($pagination = $this->getPagination()) !== false) {
0 ignored issues
show
Bug introduced by
The method getPagination() does not exist on Yiisoft\ActiveRecord\ActiveDataProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

121
        if (($pagination = $this->/** @scrutinizer ignore-call */ getPagination()) !== false) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
122
            $pagination->totalCount = $this->getTotalCount();
0 ignored issues
show
Bug introduced by
The method getTotalCount() does not exist on Yiisoft\ActiveRecord\ActiveDataProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

122
            /** @scrutinizer ignore-call */ 
123
            $pagination->totalCount = $this->getTotalCount();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
123
            if ($pagination->totalCount === 0) {
124
                $query->emulateExecution();
125
            }
126
            $query->limit($pagination->getLimit())->offset($pagination->getOffset());
127
        }
128
        if (($sort = $this->getSort()) !== false) {
0 ignored issues
show
Bug introduced by
The method getSort() does not exist on Yiisoft\ActiveRecord\ActiveDataProvider. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

128
        if (($sort = $this->/** @scrutinizer ignore-call */ getSort()) !== false) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
129
            $query->addOrderBy($sort->getOrders());
130
        }
131
132
        return $query;
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    protected function prepareKeys($models): array
139
    {
140
        $keys = [];
141
        if ($this->key !== null) {
142
            foreach ($models as $model) {
143
                if (\is_string($this->key)) {
144
                    $keys[] = $model[$this->key];
145
                } else {
146
                    $keys[] = \call_user_func($this->key, $model);
147
                }
148
            }
149
150
            return $keys;
151
        } elseif ($this->query instanceof ActiveQueryInterface) {
152
            /* @var $class ActiveRecordInterface */
153
            $class = $this->query->modelClass;
0 ignored issues
show
Bug introduced by
Accessing modelClass on the interface Yiisoft\Db\Query\QueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
154
            $pks = $class::primaryKey();
155
            if (\count($pks) === 1) {
156
                $pk = $pks[0];
157
                foreach ($models as $model) {
158
                    $keys[] = $model[$pk];
159
                }
160
            } else {
161
                foreach ($models as $model) {
162
                    $kk = [];
163
                    foreach ($pks as $pk) {
164
                        $kk[$pk] = $model[$pk];
165
                    }
166
                    $keys[] = $kk;
167
                }
168
            }
169
170
            return $keys;
171
        }
172
173
        return \array_keys($models);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    protected function prepareTotalCount(): int
180
    {
181
        if (!$this->query instanceof QueryInterface) {
182
            throw new InvalidConfigException(
183
                'The "query" property must be an instance of a class that implements the QueryInterface e.g. '
184
                . 'Yiisoft\Db\Query or its subclasses.'
185
            );
186
        }
187
        $query = clone $this->query;
188
        return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
0 ignored issues
show
Unused Code introduced by
The call to Yiisoft\Db\Query\Query::count() has too many arguments starting with $this->db. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

188
        return (int) $query->limit(-1)->offset(-1)->orderBy([])->/** @scrutinizer ignore-call */ count('*', $this->db);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
189
    }
190
191
    /**
192
     * {@inheritdoc}
193
     */
194
    public function setSort($value): void
0 ignored issues
show
Unused Code introduced by
The parameter $value is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

194
    public function setSort(/** @scrutinizer ignore-unused */ $value): void

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
195
    {
196
        if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {
197
            /* @var $modelClass Model */
198
            $modelClass = $this->query->modelClass;
0 ignored issues
show
Bug introduced by
Accessing modelClass on the interface Yiisoft\Db\Query\QueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
199
            $model = $modelClass::instance();
200
            if (empty($sort->attributes)) {
201
                foreach ($model->attributes() as $attribute) {
202
                    $sort->attributes[$attribute] = [
203
                        'asc' => [$attribute => SORT_ASC],
204
                        'desc' => [$attribute => SORT_DESC],
205
                        'label' => $model->getAttributeLabel($attribute),
206
                    ];
207
                }
208
            } else {
209
                foreach ($sort->attributes as $attribute => $config) {
210
                    if (!isset($config['label'])) {
211
                        $sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute);
212
                    }
213
                }
214
            }
215
        }
216
    }
217
}
218