Completed
Push — master ( 0ddc3b...e53fc0 )
by Alexander
78:38 queued 38:39
created

ActiveDataProvider::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 6
1
<?php
2
/**
3
 * @link http://www.yiiframework.com/
4
 * @copyright Copyright (c) 2008 Yii Software LLC
5
 * @license http://www.yiiframework.com/license/
6
 */
7
8
namespace yii\data;
9
10
use yii\base\InvalidConfigException;
11
use yii\base\Model;
12
use yii\db\ActiveQueryInterface;
13
use yii\db\Connection;
14
use yii\db\QueryInterface;
15
use yii\di\Instance;
16
17
/**
18
 * ActiveDataProvider implements a data provider based on [[\yii\db\Query]] and [[\yii\db\ActiveQuery]].
19
 *
20
 * ActiveDataProvider provides data by performing DB queries using [[query]].
21
 *
22
 * The following is an example of using ActiveDataProvider to provide ActiveRecord instances:
23
 *
24
 * ```php
25
 * $provider = new ActiveDataProvider([
26
 *     'query' => Post::find(),
27
 *     'pagination' => [
28
 *         'pageSize' => 20,
29
 *     ],
30
 * ]);
31
 *
32
 * // get the posts in the current page
33
 * $posts = $provider->getModels();
34
 * ```
35
 *
36
 * And the following example shows how to use ActiveDataProvider without ActiveRecord:
37
 *
38
 * ```php
39
 * $query = new Query();
40
 * $provider = new ActiveDataProvider([
41
 *     'query' => $query->from('post'),
42
 *     'pagination' => [
43
 *         'pageSize' => 20,
44
 *     ],
45
 * ]);
46
 *
47
 * // get the posts in the current page
48
 * $posts = $provider->getModels();
49
 * ```
50
 *
51
 * For more details and usage information on ActiveDataProvider, see the [guide article on data providers](guide:output-data-providers).
52
 *
53
 * @author Qiang Xue <[email protected]>
54
 * @since 2.0
55
 */
56
class ActiveDataProvider extends BaseDataProvider
57
{
58
    /**
59
     * @var QueryInterface the query that is used to fetch data models and [[totalCount]]
60
     * if it is not explicitly set.
61
     */
62
    public $query;
63
    /**
64
     * @var string|callable the column that is used as the key of the data models.
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 [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used.
70
     * - Otherwise, the keys of the [[models]] array will be used.
71
     *
72
     * @see getKeys()
73
     */
74
    public $key;
75
    /**
76
     * @var Connection|array|string the DB connection object or the application component ID of the DB connection.
77
     * If not set, the default DB connection will be used.
78
     * Starting from version 2.0.2, this can also be a configuration array for creating the object.
79
     */
80
    public $db;
81
82
83
    /**
84
     * Initializes the DB connection component.
85
     * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
86
     * @throws InvalidConfigException if [[db]] is invalid.
87
     */
88 26
    public function init()
89
    {
90 26
        parent::init();
91 26
        if (is_string($this->db)) {
92
            $this->db = Instance::ensure($this->db, Connection::className());
0 ignored issues
show
Deprecated Code introduced by
The method yii\base\BaseObject::className() has been deprecated with message: since 2.0.14. On PHP >=5.5, use `::class` instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
93
        }
94 26
    }
95
96
    /**
97
     * {@inheritdoc}
98
     */
99 24
    protected function prepareModels()
100
    {
101 24
        if (!$this->query instanceof QueryInterface) {
102
            throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
103
        }
104 24
        $query = clone $this->query;
105 24
        if (($pagination = $this->getPagination()) !== false) {
106 24
            $pagination->totalCount = $this->getTotalCount();
107 24
            if ($pagination->totalCount === 0) {
108 3
                return [];
109
            }
110 21
            $query->limit($pagination->getLimit())->offset($pagination->getOffset());
111
        }
112 21
        if (($sort = $this->getSort()) !== false) {
113 21
            $query->addOrderBy($sort->getOrders());
114
        }
115
116 21
        return $query->all($this->db);
117
    }
118
119
    /**
120
     * {@inheritdoc}
121
     */
122 26
    protected function prepareKeys($models)
123
    {
124 26
        $keys = [];
125 26
        if ($this->key !== null) {
126
            foreach ($models as $model) {
127
                if (is_string($this->key)) {
128
                    $keys[] = $model[$this->key];
129
                } else {
130
                    $keys[] = call_user_func($this->key, $model);
131
                }
132
            }
133
134
            return $keys;
135 26
        } elseif ($this->query instanceof ActiveQueryInterface) {
136
            /* @var $class \yii\db\ActiveRecordInterface */
137 14
            $class = $this->query->modelClass;
0 ignored issues
show
Bug introduced by
Accessing modelClass on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
138 14
            $pks = $class::primaryKey();
139 14
            if (count($pks) === 1) {
140 14
                $pk = $pks[0];
141 14
                foreach ($models as $model) {
142 14
                    $keys[] = $model[$pk];
143
                }
144
            } else {
145
                foreach ($models as $model) {
146
                    $kk = [];
147
                    foreach ($pks as $pk) {
148
                        $kk[$pk] = $model[$pk];
149
                    }
150
                    $keys[] = $kk;
151
                }
152
            }
153
154 14
            return $keys;
155
        }
156
157 12
        return array_keys($models);
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163 24
    protected function prepareTotalCount()
164
    {
165 24
        if (!$this->query instanceof QueryInterface) {
166
            throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
167
        }
168 24
        $query = clone $this->query;
169 24
        return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
170
    }
171
172
    /**
173
     * {@inheritdoc}
174
     */
175 23
    public function setSort($value)
176
    {
177 23
        parent::setSort($value);
178 23
        if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {
179
            /* @var $modelClass Model */
180 14
            $modelClass = $this->query->modelClass;
0 ignored issues
show
Bug introduced by
Accessing modelClass on the interface yii\db\ActiveQueryInterface suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
181 14
            $model = $modelClass::instance();
182 14
            if (empty($sort->attributes)) {
183 13
                foreach ($model->attributes() as $attribute) {
184 13
                    $sort->attributes[$attribute] = [
185 13
                        'asc' => [$attribute => SORT_ASC],
186 13
                        'desc' => [$attribute => SORT_DESC],
187 13
                        'label' => $model->getAttributeLabel($attribute),
188
                    ];
189
                }
190
            } else {
191 1
                foreach ($sort->attributes as $attribute => $config) {
192 1
                    if (!isset($config['label'])) {
193 1
                        $sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute);
194
                    }
195
                }
196
            }
197
        }
198 23
    }
199
    
200
    public function __clone() 
201
    {
202
        if (is_object($this->query)) {
203
            $this->query = clone $this->query;
204
        }
205
        
206
        parent::__clone();
207
    }
208
}
209