LDAPIterator   B
last analyzed

Complexity

Total Complexity 46

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 118
c 1
b 0
f 0
dl 0
loc 268
rs 8.72
wmc 46

17 Methods

Rating   Name   Duplication   Size   Complexity  
A getBaseDn() 0 3 1
A getAttributeRecursive() 0 32 5
A __construct() 0 8 1
A getLdap() 0 3 1
A resolveRangedAttributes() 0 17 3
A key() 0 10 3
B fetchPagedResult() 0 53 10
A getConvertedEntry() 0 24 6
A getResolveRangedAttributes() 0 3 1
A getFilter() 0 3 1
A valid() 0 7 2
A getReturnAttributes() 0 3 1
A next() 0 10 2
A current() 0 10 3
A getPageSize() 0 3 1
A getConvertedEntries() 0 13 3
A rewind() 0 9 2

How to fix   Complexity   

Complex Class

Complex classes like LDAPIterator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use LDAPIterator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\LDAP\Iterators;
4
5
use Iterator;
6
use Zend\Ldap\ErrorHandler;
7
use Zend\Ldap\Exception\LdapException;
8
use Zend\Ldap\Ldap;
9
10
/**
11
 * Class LDAPIterator
12
 * @package SilverStripe\LDAP\Iterators
13
 *
14
 * Original implementation provided by ThaDafinser: https://gist.github.com/ThaDafinser/1d081bed8e5e6505e97bedf5863a187c
15
 * See also: https://github.com/zendframework/zend-ldap/issues/41
16
 *
17
 * This uses the ldap_control_paged_result() and ldap_control_paged_result_response() functions to request multiple
18
 * pages of data from LDAP as needed, to ensure that the end result is everything that matches the requested filter,
19
 * regardless of the maximum page size set by the LDAP server.
20
 *
21
 * Note: The page size attribute provided to this class must be less than the LDAP server's page size, or objects may
22
 * still be missing from results.
23
 */
24
final class LDAPIterator implements Iterator
25
{
26
    private $ldap;
27
    private $filter;
28
    private $baseDn;
29
    private $returnAttributes;
30
    private $pageSize;
31
    private $resolveRangedAttributes;
32
    private $entries;
33
    private $current;
34
    /**
35
     * Required for paging
36
     *
37
     * @var unknown
0 ignored issues
show
Bug introduced by
The type SilverStripe\LDAP\Iterators\unknown was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
38
     */
39
    private $currentResult;
0 ignored issues
show
introduced by
The private property $currentResult is not used, and could be removed.
Loading history...
40
    /**
41
     * Required for paging
42
     *
43
     * @var unknown
44
     */
45
    private $cookie = true;
46
47
    /**
48
     * @param Ldap $ldap The Zend\Ldap\Ldap object that this iterator will use to retrieve results from
49
     * @param string $filter An LDAP search filter (e.g. "(&(objectClass=user)(!(objectClass=computer)))")
50
     * @param string|null $baseDn The Base DN to search from (or null to search from the connection root)
51
     * @param array|null $returnAttributes The attributes to request from the LDAP server, or null to request all
52
     * @param int $pageSize Number of results per page. This *must* be less than the LDAP server MaxPageSize setting
53
     * @param bool $resolveRangedAttributes Whether or not to return ranged attributes
54
     */
55
    public function __construct(Ldap $ldap, $filter = "", $baseDn = null, array $returnAttributes = null, $pageSize = 250, $resolveRangedAttributes = false)
56
    {
57
        $this->ldap = $ldap;
58
        $this->filter = $filter;
59
        $this->baseDn = $baseDn;
60
        $this->returnAttributes = $returnAttributes;
61
        $this->pageSize = $pageSize;
62
        $this->resolveRangedAttributes = $resolveRangedAttributes;
63
    }
64
65
    private function getLdap()
66
    {
67
        return $this->ldap;
68
    }
69
70
    private function getFilter()
71
    {
72
        return $this->filter;
73
    }
74
75
    private function getBaseDn()
76
    {
77
        return $this->baseDn;
78
    }
79
80
    private function getReturnAttributes()
81
    {
82
        return $this->returnAttributes;
83
    }
84
85
    private function getPageSize()
86
    {
87
        return $this->pageSize;
88
    }
89
90
    /**
91
     * @return bool
92
     */
93
    private function getResolveRangedAttributes()
94
    {
95
        return $this->resolveRangedAttributes;
96
    }
97
98
    private function fetchPagedResult()
99
    {
100
        if ($this->cookie === null || $this->cookie === '') {
101
            return false;
102
        }
103
104
        if ($this->cookie === true) {
0 ignored issues
show
introduced by
The condition $this->cookie === true is always false.
Loading history...
105
            // First fetch!
106
            $this->cookie = '';
107
        }
108
109
        $ldap = $this->getLdap();
110
        $resource = $ldap->getResource();
111
112
        $baseDn = $this->getBaseDn();
113
        if (!$baseDn) {
114
            $baseDn = $ldap->getBaseDn();
115
        }
116
117
        ldap_control_paged_result($resource, $this->getPageSize(), true, $this->cookie);
118
        if ($this->getReturnAttributes() !== null) {
119
            $resultResource = ldap_search($resource, $baseDn, $this->getFilter(), $this->getReturnAttributes());
120
        } else {
121
            $resultResource = ldap_search($resource, $baseDn, $this->getFilter());
122
        }
123
        if (! is_resource($resultResource)) {
124
            /*
125
             * @TODO better exception msg
126
             */
127
            throw new \Exception('ldap_search returned something wrong...' . ldap_error($resource));
128
        }
129
130
        $entries = ldap_get_entries($resource, $resultResource);
131
        if ($entries === false) {
132
            throw new LdapException($ldap, 'Entries could not get fetched');
133
        }
134
        $entries = $this->getConvertedEntries($entries);
135
136
        ErrorHandler::start();
137
        $response = ldap_control_paged_result_response($resource, $resultResource, $this->cookie);
138
        ErrorHandler::stop();
139
140
        if ($response !== true) {
141
            throw new LdapException($ldap, 'Paged result was empty');
142
        }
143
144
        if ($this->entries === null) {
145
            $this->entries = [];
146
        }
147
148
        $this->entries = array_merge($this->entries, $entries);
149
150
        return true;
151
    }
152
    private function getConvertedEntries(array $entries)
153
    {
154
        $result = [];
155
156
        foreach ($entries as $key => $entry) {
157
            if ($key === 'count') {
158
                continue;
159
            }
160
161
            $result[$key] = $this->getConvertedEntry($entry);
162
        }
163
164
        return $result;
165
    }
166
    private function getConvertedEntry(array $entry)
167
    {
168
        $result = [];
169
170
        foreach ($entry as $key => $value) {
171
            if (is_int($key)) {
172
                continue;
173
            }
174
            if ($key === 'count') {
175
                continue;
176
            }
177
178
            if (isset($value['count'])) {
179
                unset($value['count']);
180
            }
181
182
            $result[$key] = $value;
183
        }
184
185
        if ($this->getResolveRangedAttributes() === true) {
186
            $result = $this->resolveRangedAttributes($result);
187
        }
188
189
        return $result;
190
    }
191
    private function resolveRangedAttributes(array $row)
192
    {
193
        $result = [];
194
        foreach ($row as $key => $value) {
195
            $keyExploded = explode(';range=', $key);
196
197
            if (count($keyExploded) === 2) {
198
                $range = explode('-', $keyExploded[1]);
199
                $offsetAndLimit = (int) $range[1] + 1;
200
201
                $result[$keyExploded[0]] = array_merge($value, $this->getAttributeRecursive($row['dn'], $keyExploded[0], $offsetAndLimit, $offsetAndLimit));
202
            } else {
203
                $result[$key] = $value;
204
            }
205
        }
206
207
        return $result;
208
    }
209
    private function getAttributeRecursive($dn, $attrName, $offset, $maxPerRequest)
210
    {
211
        $attributeValue = [];
212
213
        $limit = $offset + $maxPerRequest - 1;
214
        $searchedAttribute = $attrName . ';range=' . $offset . '-' . $limit;
215
216
        $ldap = $this->getLdap();
217
        $entry = $ldap->getEntry($dn, [
218
            $searchedAttribute
219
        ], true);
220
        foreach ($entry as $key => $value) {
221
            // skip DN and other fields (if returned)
222
            if (stripos($key, $attrName) === false) {
223
                continue;
224
            }
225
226
            $attributeValue = $value;
227
228
            // range result (pagination)
229
            $keyExploded = explode(';range=', $key);
230
231
            $range = explode('-', $keyExploded[1]);
232
            $rangeEnd = (int) $range[1];
233
234
            if ($range[0] == $offset && $range[1] == $limit) {
235
                // more pages, there are more pages to fetch
236
                $attributeValue = array_merge($attributeValue, $this->getAttributeRecursive($dn, $attrName, $rangeEnd + 1, $maxPerRequest));
237
            }
238
        }
239
240
        return $attributeValue;
241
    }
242
    public function current()
243
    {
244
        if (! is_array($this->current)) {
245
            $this->rewind();
246
        }
247
        if (! is_array($this->current)) {
248
            return;
249
        }
250
251
        return $this->current;
252
    }
253
    public function key()
254
    {
255
        if (! is_array($this->current)) {
256
            $this->rewind();
257
        }
258
        if (! is_array($this->current)) {
259
            return;
260
        }
261
262
        return $this->current['dn'];
263
    }
264
    public function next()
265
    {
266
        // initial
267
        if ($this->entries === null) {
268
            $this->fetchPagedResult();
269
        }
270
271
        next($this->entries);
272
273
        $this->current = current($this->entries);
274
    }
275
    public function rewind()
276
    {
277
        // initial
278
        if ($this->entries === null) {
279
            $this->fetchPagedResult();
280
        }
281
282
        reset($this->entries);
283
        $this->current = current($this->entries);
284
    }
285
    public function valid()
286
    {
287
        if (is_array($this->current)) {
288
            return true;
289
        }
290
291
        return $this->fetchPagedResult();
292
    }
293
}
294