Passed
Pull Request — master (#178)
by Wilmer
11:42
created

SqlDataProvider::prepareModels()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 31
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 17
c 1
b 0
f 0
nc 6
nop 0
dl 0
loc 31
rs 9.7
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Db\Data;
6
7
use Yiisoft\Db\Connection\ConnectionInterface;
8
use Yiisoft\Db\Expression\Expression;
9
use Yiisoft\Db\Query\Query;
10
11
/**
12
 * SqlDataProvider implements a data provider based on a plain SQL statement.
13
 *
14
 * SqlDataProvider provides data in terms of arrays, each representing a row of query result.
15
 *
16
 * Like other data providers, SqlDataProvider also supports sorting and pagination.
17
 *
18
 * It does so by modifying the given {@see sql} statement with "ORDER BY" and "LIMIT" clauses. You may configure the
19
 * {@see sort} and {@see pagination} properties to customize sorting and pagination behaviors.
20
 *
21
 * SqlDataProvider may be used in the following way:
22
 *
23
 * ```php
24
 * $count = Yii::$app->db->createCommand('
25
 *     SELECT COUNT(*) FROM user WHERE status=:status
26
 * ', [':status' => 1])->queryScalar();
27
 *
28
 * $dataProvider = new SqlDataProvider([
29
 *     'sql' => 'SELECT * FROM user WHERE status=:status',
30
 *     'params' => [':status' => 1],
31
 *     'totalCount' => $count,
32
 *     'sort' => [
33
 *         'attributes' => [
34
 *             'age',
35
 *             'name' => [
36
 *                 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
37
 *                 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
38
 *                 'default' => SORT_DESC,
39
 *                 'label' => 'Name',
40
 *             ],
41
 *         ],
42
 *     ],
43
 *     'pagination' => [
44
 *         'pageSize' => 20,
45
 *     ],
46
 * ]);
47
 *
48
 * // get the user records in the current page
49
 * $models = $dataProvider->getModels();
50
 * ```
51
 *
52
 * Note: if you want to use the pagination feature, you must configure the {@see totalCount} property to be the total
53
 * number of rows (without pagination). And if you want to use the sorting feature, you must configure the {@see sort}
54
 * property so that the provider knows which columns can be sorted.
55
 *
56
 * For more details and usage information on SqlDataProvider.
57
 * See the [guide article on data providers](guide:output-data-providers).
58
 */
59
final class SqlDataProvider extends DataProvider
60
{
61
    private ConnectionInterface $db;
62
    private string $sql;
63
    private array $params = [];
64
65
    /**
66
     * @var string|callable|null the column that is used as the key of the data models.
67
     * This can be either a column name, or a callable that returns the key value of a given data model.
68
     *
69
     * If this is not set, the keys of the [[models]] array will be used.
70
     */
71
    private $key = null;
72
73
    public function __construct(ConnectionInterface $db, string $sql, array $params = [])
74
    {
75
        $this->db = $db;
76
        $this->sql = $sql;
77
        $this->params = $params;
78
    }
79
80
    /**
81
     * Prepares the data models that will be made available in the current page.
82
     *
83
     * @return array the available data models.
84
     */
85
    protected function prepareModels(): array
86
    {
87
        $sort = $this->getSort();
88
        $pagination = $this->getPagination();
89
90
        $sql = $this->sql;
91
        $orders = [];
92
        $limit = $offset = null;
93
94
        if ($sort !== null) {
95
            $orders = $sort->getOrders();
96
97
            $pattern = '/\s+order\s+by\s+([\w\s,\.]+)$/i';
98
99
            if (preg_match($pattern, $sql, $matches)) {
100
                array_unshift($orders, new Expression($matches[1]));
101
102
                $sql = preg_replace($pattern, '', $sql);
103
            }
104
        }
105
106
        if ($pagination !== null) {
107
            $pagination->totalCount = $this->getTotalCount();
0 ignored issues
show
Bug introduced by
Accessing totalCount on the interface Yiisoft\Data\Paginator\PaginatorInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
108
109
            $limit = $pagination->getLimit();
0 ignored issues
show
Bug introduced by
The method getLimit() does not exist on Yiisoft\Data\Paginator\PaginatorInterface. ( Ignorable by Annotation )

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

109
            /** @scrutinizer ignore-call */ 
110
            $limit = $pagination->getLimit();

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...
110
            $offset = $pagination->getOffset();
0 ignored issues
show
Bug introduced by
The method getOffset() does not exist on Yiisoft\Data\Paginator\PaginatorInterface. It seems like you code against a sub-type of Yiisoft\Data\Paginator\PaginatorInterface such as Yiisoft\Data\Paginator\OffsetPaginator. ( Ignorable by Annotation )

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

110
            /** @scrutinizer ignore-call */ 
111
            $offset = $pagination->getOffset();
Loading history...
111
        }
112
113
        $sql = $this->db->getQueryBuilder()->buildOrderByAndLimit($sql, $orders, $limit, $offset);
0 ignored issues
show
Bug introduced by
The method getQueryBuilder() does not exist on Yiisoft\Db\Connection\ConnectionInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Yiisoft\Db\Connection\ConnectionInterface. ( Ignorable by Annotation )

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

113
        $sql = $this->db->/** @scrutinizer ignore-call */ getQueryBuilder()->buildOrderByAndLimit($sql, $orders, $limit, $offset);
Loading history...
114
115
        return $this->db->createCommand($sql, $this->params)->queryAll();
116
    }
117
118
    /**
119
     * Prepares the keys associated with the currently available data models.
120
     *
121
     * @param array $models the available data models.
122
     *
123
     * @return array the keys.
124
     */
125
    protected function prepareKeys(array $models = []): array
126
    {
127
        $keys = [];
128
129
        if ($this->key !== null) {
130
            foreach ($models as $model) {
131
                if (is_string($this->key)) {
132
                    $keys[] = $model[$this->key];
133
                } else {
134
                    $keys[] = call_user_func($this->key, $model);
135
                }
136
            }
137
138
            return $keys;
139
        }
140
141
        return array_keys($models);
142
    }
143
144
    /**
145
     * Returns a value indicating the total number of data models in this data provider.
146
     *
147
     * @return int total number of data models in this data provider.
148
     */
149
    protected function prepareTotalCount(): int
150
    {
151
        return (int) (new Query($this->db))->from(['sub' => "({$this->sql})"])->params($this->params)->count('*');
152
    }
153
}
154