Completed
Push — master ( 8dfd89...b5db7b )
by Christopher
02:35
created

Query   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 248
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 6

Importance

Changes 9
Bugs 0 Features 0
Metric Value
wmc 22
c 9
b 0
f 0
lcom 1
cbo 6
dl 0
loc 248
rs 10

11 Methods

Rating   Name   Duplication   Size   Complexity  
B execute() 0 24 5
A prepare() 0 4 1
A all() 0 6 1
A populate() 0 16 4
A one() 0 6 1
A count() 0 5 1
A exists() 0 5 1
A select() 0 8 2
A addSelect() 0 12 3
A andFilterCompare() 0 10 2
A create() 0 11 1
1
<?php
2
/**
3
 * @link      https://github.com/chrmorandi/yii2-ldap for the canonical source repository
4
 * @package   yii2-ldap
5
 * @author    Christopher Mota <[email protected]>
6
 * @license   Mit License - view the LICENSE file that was distributed with this source code.
7
 */
8
9
namespace chrmorandi\ldap;
10
11
use chrmorandi\ldap\Connection;
12
use Yii;
13
use yii\base\Component;
14
use yii\base\InvalidValueException;
15
use yii\db\Expression;
16
use yii\db\QueryInterface;
17
use yii\db\QueryTrait;
18
19
/**
20
 * Query represents a SEARCH in LDAP database directory.
21
 *
22
 * Query provides a set of methods to facilitate the specification of different clauses
23
 * in a SEARCH statement. These methods can be chained together.
24
 *
25
 * For example,
26
 *
27
 * ```php
28
 * $query = new Query;
29
 * // compose the query
30
 * $query->select('id, name')
31
 *     ->from('user')
32
 *     ->limit(10);
33
 * // build and execute the query
34
 * $rows = $query->all();
35
 * ```
36
 *
37
 * Query internally uses the [[FilterBuilder]] class to generate the LDAP filters.
38
 *
39
 * @author Christopher Mota <[email protected]>
40
 * @since 1.0.0
41
 */
42
class Query extends Component implements QueryInterface
43
{
44
    use QueryTrait;
45
46
    const SEARCH_SCOPE_SUB  = 'ldap_search';
47
    const SEARCH_SCOPE_ONE  = 'ldap_list';
48
    const SEARCH_SCOPE_BASE = 'ldap_read';
49
    
50
    /**
51
     * @var string the scope of search
52
     * The search scope:
53
     * Query::SEARCH_SCOPE_SUB searches the complete subtree including the $baseDn node. This is the default value.
54
     * Query::SEARCH_SCOPE_ONE restricts search to one level below $baseDn.
55
     * Query::SEARCH_SCOPE_BASE restricts search to the $baseDn itself; this can be used to efficiently retrieve a single entry by its DN.
56
     */
57
    public $scope = self::SEARCH_SCOPE_SUB;
58
    
59
    /**
60
     * @var array the columns being selected. For example, `['id', 'name']`.
61
     * This is used to construct the SEARCH function in a LDAP statement. If not set, it means selecting all columns.
62
     * @see select()
63
     */
64
    public $select;
65
    
66
    /**
67
     * @var string The search filter. Format is described in the LDAP documentation.
68
     * @see http://www.faqs.org/rfcs/rfc4515.html
69
     */
70
    public $filter;
71
72
    /**
73
     * Creates a LDAP command that can be used to execute this query.
74
     * @param Connection $db the database connection.
75
     * If this parameter is not given, the `db` application component will be used.
76
     * @return DataReader
77
     */
78
    public function execute($db = null)
79
    {
80
        if ($db === null) {
81
            $db = Yii::$app->get('ldap');
82
        }
83
        
84
        $this->filter = (new FilterBuilder)->build($this->where);        
85
        if(empty($this->filter)){
86
            throw new InvalidValueException('You must define a filter for the search.');
87
        }
88
        
89
        $select = (is_array($this->select)) ? $this->select : [];
90
        $this->limit = empty($this->limit) ? 0 : $this->limit;
91
92
        $params = [
93
            $db->baseDn,
94
            $this->filter,
95
            $select,
96
            0,
97
            $this->limit
98
        ];
99
100
        return $db->execute($this->scope, $params);
101
    }
102
    
103
    /**
104
     * Prepares for building SQL.
105
     * This method is called by [[QueryBuilder]] when it starts to build SQL from a query object.
106
     * You may override this method to do some final preparation work when converting a query into a SQL statement.
107
     * @param QueryBuilder $builder
108
     * @return $this a prepared query instance which will be used by [[QueryBuilder]] to build the SQL
109
     */
110
    public function prepare($builder)
0 ignored issues
show
Unused Code introduced by
The parameter $builder is not used and could be removed.

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

Loading history...
111
    {
112
        return $this;
113
    }
114
115
    /**
116
     * Executes the query and returns all results as an array.
117
     * @param Connection $db the database connection.
118
     * If this parameter is not given, the `db` application component will be used.
119
     * @return array the query results. If the query results in nothing, an empty array will be returned.
120
     */
121
    public function all($db = null)
122
    {        
123
        /** @var $result DataReader */
124
        $result = $this->execute($db);        
125
        return $this->populate($result->toArray());
126
    }
127
128
    /**
129
     * Converts the raw query results into the format as specified by this query.
130
     * This method is internally used to convert the data fetched from database
131
     * into the format as required by this query.
132
     * @param array $rows the raw query result from database
133
     * @return array the converted query result
134
     */
135
    public function populate($rows)
136
    {
137
        if ($this->indexBy === null) {
138
            return $rows;
139
        }
140
        $result = [];
141
        foreach ($rows as $row) {
142
            if (is_string($this->indexBy)) {
143
                $key = $row[$this->indexBy];
144
            } else {
145
                $key = call_user_func($this->indexBy, $row);
146
            }
147
            $result[$key] = $row;
148
        }
149
        return $result;
150
    }
151
152
    /**
153
     * Executes the query and returns a single row of result.
154
     * @param Connection $db the database connection.
155
     * If this parameter is not given, the `db` application component will be used.
156
     * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
157
     * results in nothing.
158
     */
159
    public function one($db = null)
160
    {
161
        $this->limit = 1;
162
        $result = $this->execute($db);
163
        return $result->toArray();
164
    }
165
166
    /**
167
     * Returns the number of entries in a search.
168
     * @param Connection $db the database connection
169
     * If this parameter is not given (or null), the `db` application component will be used.
170
     * @return integer number of entries.
171
     */
172
    public function count($q = '*', $db = NULL)
173
    {        
174
        $result = $this->execute($db);
175
        return $result->count();
176
    }
177
    
178
179
    /**
180
     * Returns a value indicating whether the query result contains any row of data.
181
     * @param Connection $db the database connection.
182
     * If this parameter is not given, the `db` application component will be used.
183
     * @return boolean whether the query result contains any row of entries.
184
     */
185
    public function exists($db = null)
186
    {        
187
        $result = $this->execute($db);
188
        return (boolean) $result->count();
189
    }
190
191
    /**
192
     * Sets the SELECT part of the query.
193
     * @param string|array $columns the columns to be selected.
194
     * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
195
     *
196
     * ```php
197
     * $query->addSelect(['cn, mail'])->one();
198
     * ```
199
     *
200
     * @return $this the query object itself
201
     */
202
    public function select($columns)
203
    {
204
        if (!is_array($columns)) {
205
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
206
        }
207
        $this->select = $columns;
208
        return $this;
209
    }
210
211
    /**
212
     * Add more columns to the select part of the query.
213
     *
214
     * ```php
215
     * $query->addSelect(['cn, mail'])->one();
216
     * ```
217
     *
218
     * @param string|array|Expression $columns the columns to add to the select. See [[select()]] for more
219
     * details about the format of this parameter.
220
     * @return $this the query object itself
221
     * @see select()
222
     */
223
    public function addSelect($columns)
224
    {
225
        if (!is_array($columns)) {
226
            $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
227
        }
228
        if ($this->select === null) {
229
            $this->select = $columns;
230
        } else {
231
            $this->select = array_merge($this->select, $columns);
232
        }
233
        return $this;
234
    }
235
236
    /**
237
     * Adds a filtering condition for a specific column and allow the user to choose a filter operator.
238
     *
239
     * It adds an additional WHERE condition for the given field and determines the comparison operator
240
     * based on the first few characters of the given value.
241
     * The condition is added in the same way as in [[andFilterWhere]] so [[isEmpty()|empty values]] are ignored.
242
     * The new condition and the existing one will be joined using the 'AND' operator.
243
     *
244
     * The comparison operator is intelligently determined based on the first few characters in the given value.
245
     * In particular, it recognizes the following operators if they appear as the leading characters in the given value:
246
     *
247
     * - `<`: the column must be less than the given value.
248
     * - `>`: the column must be greater than the given value.
249
     * - `<=`: the column must be less than or equal to the given value.
250
     * - `>=`: the column must be greater than or equal to the given value.
251
     * - `~=`: the column must approximate the given value.
252
     * - `=`: the column must be equal to the given value.
253
     * - If none of the above operators is detected, the `$defaultOperator` will be used.
254
     *
255
     * @param string $name the column name.
256
     * @param string $value the column value optionally prepended with the comparison operator.
257
     * @param string $defaultOperator The operator to use, when no operator is given in `$value`.
258
     * Defaults to `=`, performing an exact match.
259
     * @return $this The query object itself
260
     */
261
    public function andFilterCompare($name, $value, $defaultOperator = '=')
262
    {
263
        if (preg_match("/^(~=|>=|>|<=|<|=)/", $value, $matches)) {
264
            $operator = $matches[1];
265
            $value = substr($value, strlen($operator));
266
        } else {
267
            $operator = $defaultOperator;
268
        }
269
        return $this->andFilterWhere([$operator, $name, $value]);
270
    }
271
272
    /**
273
     * Creates a new Query object and copies its property values from an existing one.
274
     * The properties being copies are the ones to be used by query builders.
275
     * @param Query $from the source query object
276
     * @return Query the new Query object
277
     */
278
    public static function create(Query $from)
279
    {
280
        return new self([
281
            'where' => $from->where,
282
            'limit' => $from->limit,
283
            'offset' => $from->offset,
284
            'orderBy' => $from->orderBy,
285
            'indexBy' => $from->indexBy,
286
            'select' => $from->select,
287
        ]);
288
    }
289
}
290