Completed
Push — master ( ab1148...78a8ff )
by Christopher
02:58
created

Query::execute()   C

Complexity

Conditions 7
Paths 26

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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