Completed
Push — master ( b5db7b...fee94a )
by Christopher
02:25
created

Query::all()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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