xref: /plugin/pureldap/vendor/freedsx/ldap/src/FreeDSx/Ldap/LdapClient.php (revision dad993c57a70866aa1db59c43f043769c2eb7ed0)
10b3fd2d3SAndreas Gohr<?php
2*dad993c5SAndreas Gohr
30b3fd2d3SAndreas Gohr/**
40b3fd2d3SAndreas Gohr * This file is part of the FreeDSx LDAP package.
50b3fd2d3SAndreas Gohr *
60b3fd2d3SAndreas Gohr * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
70b3fd2d3SAndreas Gohr *
80b3fd2d3SAndreas Gohr * For the full copyright and license information, please view the LICENSE
90b3fd2d3SAndreas Gohr * file that was distributed with this source code.
100b3fd2d3SAndreas Gohr */
110b3fd2d3SAndreas Gohr
120b3fd2d3SAndreas Gohrnamespace FreeDSx\Ldap;
130b3fd2d3SAndreas Gohr
140b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Control;
150b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\ControlBag;
160b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Sorting\SortingControl;
170b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Control\Sorting\SortKey;
18*dad993c5SAndreas Gohruse FreeDSx\Ldap\Entry\Dn;
190b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entries;
200b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Entry\Entry;
21*dad993c5SAndreas Gohruse FreeDSx\Ldap\Entry\Rdn;
220b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Exception\OperationException;
230b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\ExtendedRequest;
240b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\RequestInterface;
250b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\Request\SearchRequest;
260b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Operation\ResultCode;
270b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\ClientProtocolHandler;
280b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Protocol\LdapMessageResponse;
290b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\DirSync;
300b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\Filter\FilterInterface;
310b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\Paging;
320b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\RangeRetrieval;
330b3fd2d3SAndreas Gohruse FreeDSx\Ldap\Search\Vlv;
34*dad993c5SAndreas Gohruse FreeDSx\Sasl\Exception\SaslException;
350b3fd2d3SAndreas Gohr
360b3fd2d3SAndreas Gohr/**
370b3fd2d3SAndreas Gohr * The LDAP client.
380b3fd2d3SAndreas Gohr *
390b3fd2d3SAndreas Gohr * @author Chad Sikorra <Chad.Sikorra@gmail.com>
400b3fd2d3SAndreas Gohr */
410b3fd2d3SAndreas Gohrclass LdapClient
420b3fd2d3SAndreas Gohr{
430b3fd2d3SAndreas Gohr    public const REFERRAL_IGNORE = 'ignore';
440b3fd2d3SAndreas Gohr
450b3fd2d3SAndreas Gohr    public const REFERRAL_FOLLOW = 'follow';
460b3fd2d3SAndreas Gohr
470b3fd2d3SAndreas Gohr    public const REFERRAL_THROW = 'throw';
480b3fd2d3SAndreas Gohr
490b3fd2d3SAndreas Gohr    /**
500b3fd2d3SAndreas Gohr     * @var array
510b3fd2d3SAndreas Gohr     */
520b3fd2d3SAndreas Gohr    protected $options = [
530b3fd2d3SAndreas Gohr        'version' => 3,
540b3fd2d3SAndreas Gohr        'servers' => [],
550b3fd2d3SAndreas Gohr        'port' => 389,
56*dad993c5SAndreas Gohr        'transport' => 'tcp',
570b3fd2d3SAndreas Gohr        'base_dn' => null,
580b3fd2d3SAndreas Gohr        'page_size' => 1000,
590b3fd2d3SAndreas Gohr        'use_ssl' => false,
600b3fd2d3SAndreas Gohr        'ssl_validate_cert' => true,
610b3fd2d3SAndreas Gohr        'ssl_allow_self_signed' => null,
620b3fd2d3SAndreas Gohr        'ssl_ca_cert' => null,
630b3fd2d3SAndreas Gohr        'ssl_peer_name' => null,
640b3fd2d3SAndreas Gohr        'timeout_connect' => 3,
650b3fd2d3SAndreas Gohr        'timeout_read' => 10,
660b3fd2d3SAndreas Gohr        'referral' => 'throw',
670b3fd2d3SAndreas Gohr        'referral_chaser' => null,
680b3fd2d3SAndreas Gohr        'referral_limit' => 10,
690b3fd2d3SAndreas Gohr    ];
700b3fd2d3SAndreas Gohr
710b3fd2d3SAndreas Gohr    /**
720b3fd2d3SAndreas Gohr     * @var ClientProtocolHandler|null
730b3fd2d3SAndreas Gohr     */
740b3fd2d3SAndreas Gohr    protected $handler;
750b3fd2d3SAndreas Gohr
760b3fd2d3SAndreas Gohr    /**
770b3fd2d3SAndreas Gohr     * @param array $options
780b3fd2d3SAndreas Gohr     */
790b3fd2d3SAndreas Gohr    public function __construct(array $options = [])
800b3fd2d3SAndreas Gohr    {
810b3fd2d3SAndreas Gohr        $this->options = array_merge($this->options, $options);
820b3fd2d3SAndreas Gohr    }
830b3fd2d3SAndreas Gohr
840b3fd2d3SAndreas Gohr    /**
850b3fd2d3SAndreas Gohr     * A Simple Bind to LDAP with a username and password.
860b3fd2d3SAndreas Gohr     *
870b3fd2d3SAndreas Gohr     * @param string $username
880b3fd2d3SAndreas Gohr     * @param string $password
890b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
90*dad993c5SAndreas Gohr     * @throws Exception\BindException
910b3fd2d3SAndreas Gohr     */
920b3fd2d3SAndreas Gohr    public function bind(string $username, string $password): LdapMessageResponse
930b3fd2d3SAndreas Gohr    {
940b3fd2d3SAndreas Gohr        return $this->sendAndReceive(Operations::bind($username, $password)->setVersion($this->options['version']));
950b3fd2d3SAndreas Gohr    }
960b3fd2d3SAndreas Gohr
970b3fd2d3SAndreas Gohr    /**
980b3fd2d3SAndreas Gohr     * A SASL Bind to LDAP with SASL options and an optional specific mechanism type.
990b3fd2d3SAndreas Gohr     *
1000b3fd2d3SAndreas Gohr     * @param array $options The SASL options (ie. ['username' => '...', 'password' => '...'])
1010b3fd2d3SAndreas Gohr     * @param string $mechanism A specific mechanism to use. If none is supplied, one will be selected.
1020b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
103*dad993c5SAndreas Gohr     * @throws Exception\BindException
1040b3fd2d3SAndreas Gohr     * @throws OperationException
105*dad993c5SAndreas Gohr     * @throws SaslException
1060b3fd2d3SAndreas Gohr     */
1070b3fd2d3SAndreas Gohr    public function bindSasl(array $options = [], string $mechanism = ''): LdapMessageResponse
1080b3fd2d3SAndreas Gohr    {
1090b3fd2d3SAndreas Gohr        return $this->sendAndReceive(Operations::bindSasl($options, $mechanism)->setVersion($this->options['version']));
1100b3fd2d3SAndreas Gohr    }
1110b3fd2d3SAndreas Gohr
1120b3fd2d3SAndreas Gohr    /**
113*dad993c5SAndreas Gohr     * Check whether an entry matches a certain attribute and value.
1140b3fd2d3SAndreas Gohr     *
115*dad993c5SAndreas Gohr     * @param string|Dn $dn
1160b3fd2d3SAndreas Gohr     * @param string $attributeName
1170b3fd2d3SAndreas Gohr     * @param string $value
1180b3fd2d3SAndreas Gohr     * @param Control ...$controls
1190b3fd2d3SAndreas Gohr     * @return bool
1200b3fd2d3SAndreas Gohr     * @throws OperationException
1210b3fd2d3SAndreas Gohr     */
1220b3fd2d3SAndreas Gohr    public function compare($dn, string $attributeName, string $value, Control ...$controls): bool
1230b3fd2d3SAndreas Gohr    {
1240b3fd2d3SAndreas Gohr        /** @var \FreeDSx\Ldap\Operation\Response\CompareResponse $response */
1250b3fd2d3SAndreas Gohr        $response = $this->sendAndReceive(Operations::compare($dn, $attributeName, $value), ...$controls)->getResponse();
1260b3fd2d3SAndreas Gohr
1270b3fd2d3SAndreas Gohr        return $response->getResultCode() === ResultCode::COMPARE_TRUE;
1280b3fd2d3SAndreas Gohr    }
1290b3fd2d3SAndreas Gohr
1300b3fd2d3SAndreas Gohr    /**
1310b3fd2d3SAndreas Gohr     * Create a new entry.
1320b3fd2d3SAndreas Gohr     *
1330b3fd2d3SAndreas Gohr     * @param Entry $entry
1340b3fd2d3SAndreas Gohr     * @param Control ...$controls
1350b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
1360b3fd2d3SAndreas Gohr     * @throws OperationException
1370b3fd2d3SAndreas Gohr     */
1380b3fd2d3SAndreas Gohr    public function create(Entry $entry, Control ...$controls): LdapMessageResponse
1390b3fd2d3SAndreas Gohr    {
1400b3fd2d3SAndreas Gohr        $response = $this->sendAndReceive(Operations::add($entry), ...$controls);
1410b3fd2d3SAndreas Gohr        $entry->changes()->reset();
1420b3fd2d3SAndreas Gohr
1430b3fd2d3SAndreas Gohr        return $response;
1440b3fd2d3SAndreas Gohr    }
1450b3fd2d3SAndreas Gohr
1460b3fd2d3SAndreas Gohr    /**
1470b3fd2d3SAndreas Gohr     * Read an entry.
1480b3fd2d3SAndreas Gohr     *
1490b3fd2d3SAndreas Gohr     * @param string $entry
1500b3fd2d3SAndreas Gohr     * @param string[] $attributes
1510b3fd2d3SAndreas Gohr     * @param Control ...$controls
1520b3fd2d3SAndreas Gohr     * @return Entry|null
153*dad993c5SAndreas Gohr     * @throws OperationException
1540b3fd2d3SAndreas Gohr     */
1550b3fd2d3SAndreas Gohr    public function read(string $entry = '', $attributes = [], Control ...$controls): ?Entry
1560b3fd2d3SAndreas Gohr    {
1570b3fd2d3SAndreas Gohr        try {
1580b3fd2d3SAndreas Gohr            return $this->readOrFail($entry, $attributes, ...$controls);
1590b3fd2d3SAndreas Gohr        } catch (Exception\OperationException $e) {
1600b3fd2d3SAndreas Gohr            if ($e->getCode() === ResultCode::NO_SUCH_OBJECT) {
1610b3fd2d3SAndreas Gohr                return null;
1620b3fd2d3SAndreas Gohr            }
1630b3fd2d3SAndreas Gohr            throw $e;
1640b3fd2d3SAndreas Gohr        }
1650b3fd2d3SAndreas Gohr    }
1660b3fd2d3SAndreas Gohr
1670b3fd2d3SAndreas Gohr    /**
1680b3fd2d3SAndreas Gohr     * Read an entry from LDAP. If the entry is not found an OperationException is thrown.
1690b3fd2d3SAndreas Gohr     *
1700b3fd2d3SAndreas Gohr     * @param string $entry
1710b3fd2d3SAndreas Gohr     * @param string[] $attributes
1720b3fd2d3SAndreas Gohr     * @param Control ...$controls
1730b3fd2d3SAndreas Gohr     * @return Entry
1740b3fd2d3SAndreas Gohr     * @throws OperationException
1750b3fd2d3SAndreas Gohr     */
1760b3fd2d3SAndreas Gohr    public function readOrFail(string $entry = '', $attributes = [], Control ...$controls): Entry
1770b3fd2d3SAndreas Gohr    {
1780b3fd2d3SAndreas Gohr        $entryObj = $this->search(Operations::read($entry, ...$attributes), ...$controls)->first();
1790b3fd2d3SAndreas Gohr        if ($entryObj === null) {
1800b3fd2d3SAndreas Gohr            throw new OperationException(sprintf(
1810b3fd2d3SAndreas Gohr                'The entry "%s" was not found.',
1820b3fd2d3SAndreas Gohr                $entry
1830b3fd2d3SAndreas Gohr            ), ResultCode::NO_SUCH_OBJECT);
1840b3fd2d3SAndreas Gohr        }
1850b3fd2d3SAndreas Gohr
1860b3fd2d3SAndreas Gohr        return $entryObj;
1870b3fd2d3SAndreas Gohr    }
1880b3fd2d3SAndreas Gohr
1890b3fd2d3SAndreas Gohr    /**
1900b3fd2d3SAndreas Gohr     * Delete an entry.
1910b3fd2d3SAndreas Gohr     *
1920b3fd2d3SAndreas Gohr     * @param string $entry
1930b3fd2d3SAndreas Gohr     * @param Control ...$controls
1940b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
1950b3fd2d3SAndreas Gohr     * @throws OperationException
1960b3fd2d3SAndreas Gohr     */
1970b3fd2d3SAndreas Gohr    public function delete(string $entry, Control ...$controls): LdapMessageResponse
1980b3fd2d3SAndreas Gohr    {
1990b3fd2d3SAndreas Gohr        return $this->sendAndReceive(Operations::delete($entry), ...$controls);
2000b3fd2d3SAndreas Gohr    }
2010b3fd2d3SAndreas Gohr
2020b3fd2d3SAndreas Gohr    /**
2030b3fd2d3SAndreas Gohr     * Update an existing entry.
2040b3fd2d3SAndreas Gohr     *
2050b3fd2d3SAndreas Gohr     * @param Entry $entry
2060b3fd2d3SAndreas Gohr     * @param Control ...$controls
2070b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
2080b3fd2d3SAndreas Gohr     * @throws OperationException
2090b3fd2d3SAndreas Gohr     */
2100b3fd2d3SAndreas Gohr    public function update(Entry $entry, Control ...$controls): LdapMessageResponse
2110b3fd2d3SAndreas Gohr    {
2120b3fd2d3SAndreas Gohr        $response = $this->sendAndReceive(Operations::modify($entry->getDn(), ...$entry->changes()), ...$controls);
2130b3fd2d3SAndreas Gohr        $entry->changes()->reset();
2140b3fd2d3SAndreas Gohr
2150b3fd2d3SAndreas Gohr        return $response;
2160b3fd2d3SAndreas Gohr    }
2170b3fd2d3SAndreas Gohr
2180b3fd2d3SAndreas Gohr    /**
2190b3fd2d3SAndreas Gohr     * Move an entry to a new location.
2200b3fd2d3SAndreas Gohr     *
2210b3fd2d3SAndreas Gohr     * @param string|Entry $dn
2220b3fd2d3SAndreas Gohr     * @param string|Entry $newParentDn
2230b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
2240b3fd2d3SAndreas Gohr     * @throws OperationException
2250b3fd2d3SAndreas Gohr     */
2260b3fd2d3SAndreas Gohr    public function move($dn, $newParentDn): LdapMessageResponse
2270b3fd2d3SAndreas Gohr    {
2280b3fd2d3SAndreas Gohr        return $this->sendAndReceive(Operations::move($dn, $newParentDn));
2290b3fd2d3SAndreas Gohr    }
2300b3fd2d3SAndreas Gohr
2310b3fd2d3SAndreas Gohr    /**
2320b3fd2d3SAndreas Gohr     * Rename an entry (changing the RDN).
2330b3fd2d3SAndreas Gohr     *
2340b3fd2d3SAndreas Gohr     * @param string|Entry $dn
235*dad993c5SAndreas Gohr     * @param string|Rdn $newRdn
2360b3fd2d3SAndreas Gohr     * @param bool $deleteOldRdn
2370b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
2380b3fd2d3SAndreas Gohr     * @throws OperationException
2390b3fd2d3SAndreas Gohr     */
2400b3fd2d3SAndreas Gohr    public function rename($dn, $newRdn, bool $deleteOldRdn = true): LdapMessageResponse
2410b3fd2d3SAndreas Gohr    {
2420b3fd2d3SAndreas Gohr        return $this->sendAndReceive(Operations::rename($dn, $newRdn, $deleteOldRdn));
2430b3fd2d3SAndreas Gohr    }
2440b3fd2d3SAndreas Gohr
2450b3fd2d3SAndreas Gohr    /**
2460b3fd2d3SAndreas Gohr     * Send a search response and return the entries.
2470b3fd2d3SAndreas Gohr     *
2480b3fd2d3SAndreas Gohr     * @param SearchRequest $request
2490b3fd2d3SAndreas Gohr     * @param Control ...$controls
250*dad993c5SAndreas Gohr     * @return Entries
2510b3fd2d3SAndreas Gohr     * @throws OperationException
2520b3fd2d3SAndreas Gohr     */
2530b3fd2d3SAndreas Gohr    public function search(SearchRequest $request, Control ...$controls): Entries
2540b3fd2d3SAndreas Gohr    {
2550b3fd2d3SAndreas Gohr        /** @var \FreeDSx\Ldap\Operation\Response\SearchResponse $response */
2560b3fd2d3SAndreas Gohr        $response = $this->sendAndReceive($request, ...$controls)->getResponse();
2570b3fd2d3SAndreas Gohr
2580b3fd2d3SAndreas Gohr        return $response->getEntries();
2590b3fd2d3SAndreas Gohr    }
2600b3fd2d3SAndreas Gohr
2610b3fd2d3SAndreas Gohr    /**
2620b3fd2d3SAndreas Gohr     * A helper for performing a paging based search.
2630b3fd2d3SAndreas Gohr     *
2640b3fd2d3SAndreas Gohr     * @param SearchRequest $search
265*dad993c5SAndreas Gohr     * @param null|int $size
2660b3fd2d3SAndreas Gohr     * @return Paging
2670b3fd2d3SAndreas Gohr     */
2680b3fd2d3SAndreas Gohr    public function paging(SearchRequest $search, ?int $size = null): Paging
2690b3fd2d3SAndreas Gohr    {
2700b3fd2d3SAndreas Gohr        return new Paging($this, $search, $size ?? $this->options['page_size']);
2710b3fd2d3SAndreas Gohr    }
2720b3fd2d3SAndreas Gohr
2730b3fd2d3SAndreas Gohr    /**
2740b3fd2d3SAndreas Gohr     * A helper for performing a VLV (Virtual List View) based search.
2750b3fd2d3SAndreas Gohr     *
2760b3fd2d3SAndreas Gohr     * @param SearchRequest $search
2770b3fd2d3SAndreas Gohr     * @param SortingControl|string|SortKey $sort
2780b3fd2d3SAndreas Gohr     * @param int $afterCount
2790b3fd2d3SAndreas Gohr     * @return Vlv
2800b3fd2d3SAndreas Gohr     */
2810b3fd2d3SAndreas Gohr    public function vlv(SearchRequest $search, $sort, int $afterCount): Vlv
2820b3fd2d3SAndreas Gohr    {
2830b3fd2d3SAndreas Gohr        return new Vlv($this, $search, $sort, $afterCount);
2840b3fd2d3SAndreas Gohr    }
2850b3fd2d3SAndreas Gohr
2860b3fd2d3SAndreas Gohr    /**
2870b3fd2d3SAndreas Gohr     * A helper for performing a DirSync search operation against AD.
2880b3fd2d3SAndreas Gohr     *
2890b3fd2d3SAndreas Gohr     * @param string|null $rootNc
2900b3fd2d3SAndreas Gohr     * @param FilterInterface|null $filter
2910b3fd2d3SAndreas Gohr     * @param mixed ...$attributes
2920b3fd2d3SAndreas Gohr     * @return DirSync
2930b3fd2d3SAndreas Gohr     */
2940b3fd2d3SAndreas Gohr    public function dirSync(?string $rootNc = null, FilterInterface $filter = null, ...$attributes): DirSync
2950b3fd2d3SAndreas Gohr    {
2960b3fd2d3SAndreas Gohr        return new DirSync($this, $rootNc, $filter, ...$attributes);
2970b3fd2d3SAndreas Gohr    }
2980b3fd2d3SAndreas Gohr
2990b3fd2d3SAndreas Gohr    /**
3000b3fd2d3SAndreas Gohr     * Send a request operation to LDAP. This may return null if the request expects no response.
3010b3fd2d3SAndreas Gohr     *
3020b3fd2d3SAndreas Gohr     * @param RequestInterface $request
3030b3fd2d3SAndreas Gohr     * @param Control ...$controls
3040b3fd2d3SAndreas Gohr     * @return LdapMessageResponse|null
305*dad993c5SAndreas Gohr     * @throws Exception\BindException
3060b3fd2d3SAndreas Gohr     * @throws Exception\ConnectionException
3070b3fd2d3SAndreas Gohr     * @throws OperationException
3080b3fd2d3SAndreas Gohr     */
3090b3fd2d3SAndreas Gohr    public function send(RequestInterface $request, Control ...$controls): ?LdapMessageResponse
3100b3fd2d3SAndreas Gohr    {
3110b3fd2d3SAndreas Gohr        return $this->handler()->send($request, ...$controls);
3120b3fd2d3SAndreas Gohr    }
3130b3fd2d3SAndreas Gohr
3140b3fd2d3SAndreas Gohr    /**
3150b3fd2d3SAndreas Gohr     * Send a request to LDAP that expects a response. If none is received an OperationException is thrown.
3160b3fd2d3SAndreas Gohr     *
3170b3fd2d3SAndreas Gohr     * @param RequestInterface $request
3180b3fd2d3SAndreas Gohr     * @param Control ...$controls
3190b3fd2d3SAndreas Gohr     * @return LdapMessageResponse
320*dad993c5SAndreas Gohr     * @throws Exception\BindException
321*dad993c5SAndreas Gohr     * @throws Exception\ConnectionException
3220b3fd2d3SAndreas Gohr     * @throws OperationException
3230b3fd2d3SAndreas Gohr     */
3240b3fd2d3SAndreas Gohr    public function sendAndReceive(RequestInterface $request, Control ...$controls): LdapMessageResponse
3250b3fd2d3SAndreas Gohr    {
3260b3fd2d3SAndreas Gohr        $response = $this->send($request, ...$controls);
3270b3fd2d3SAndreas Gohr        if ($response === null) {
3280b3fd2d3SAndreas Gohr            throw new OperationException('Expected an LDAP message response, but none was received.');
3290b3fd2d3SAndreas Gohr        }
3300b3fd2d3SAndreas Gohr
3310b3fd2d3SAndreas Gohr        return $response;
3320b3fd2d3SAndreas Gohr    }
3330b3fd2d3SAndreas Gohr
3340b3fd2d3SAndreas Gohr    /**
3350b3fd2d3SAndreas Gohr     * Issue a startTLS to encrypt the LDAP connection.
3360b3fd2d3SAndreas Gohr     *
3370b3fd2d3SAndreas Gohr     * @return $this
338*dad993c5SAndreas Gohr     * @throws Exception\ConnectionException
3390b3fd2d3SAndreas Gohr     * @throws OperationException
3400b3fd2d3SAndreas Gohr     */
341*dad993c5SAndreas Gohr    public function startTls(): self
3420b3fd2d3SAndreas Gohr    {
3430b3fd2d3SAndreas Gohr        $this->send(Operations::extended(ExtendedRequest::OID_START_TLS));
3440b3fd2d3SAndreas Gohr
3450b3fd2d3SAndreas Gohr        return $this;
3460b3fd2d3SAndreas Gohr    }
3470b3fd2d3SAndreas Gohr
3480b3fd2d3SAndreas Gohr    /**
3490b3fd2d3SAndreas Gohr     * Unbind and close the LDAP TCP connection.
3500b3fd2d3SAndreas Gohr     *
3510b3fd2d3SAndreas Gohr     * @return $this
352*dad993c5SAndreas Gohr     * @throws Exception\ConnectionException
3530b3fd2d3SAndreas Gohr     * @throws OperationException
3540b3fd2d3SAndreas Gohr     */
355*dad993c5SAndreas Gohr    public function unbind(): self
3560b3fd2d3SAndreas Gohr    {
3570b3fd2d3SAndreas Gohr        $this->send(Operations::unbind());
3580b3fd2d3SAndreas Gohr
3590b3fd2d3SAndreas Gohr        return $this;
3600b3fd2d3SAndreas Gohr    }
3610b3fd2d3SAndreas Gohr
3620b3fd2d3SAndreas Gohr    /**
3630b3fd2d3SAndreas Gohr     * Perform a whoami request and get the returned value.
3640b3fd2d3SAndreas Gohr     *
3650b3fd2d3SAndreas Gohr     * @return string
3660b3fd2d3SAndreas Gohr     * @throws OperationException
3670b3fd2d3SAndreas Gohr     */
3680b3fd2d3SAndreas Gohr    public function whoami(): ?string
3690b3fd2d3SAndreas Gohr    {
3700b3fd2d3SAndreas Gohr        /** @var \FreeDSx\Ldap\Operation\Response\ExtendedResponse $response */
3710b3fd2d3SAndreas Gohr        $response = $this->sendAndReceive(Operations::whoami())->getResponse();
3720b3fd2d3SAndreas Gohr
3730b3fd2d3SAndreas Gohr        return $response->getValue();
3740b3fd2d3SAndreas Gohr    }
3750b3fd2d3SAndreas Gohr
3760b3fd2d3SAndreas Gohr    /**
3770b3fd2d3SAndreas Gohr     * Get a helper class for handling ranged attributes.
3780b3fd2d3SAndreas Gohr     *
3790b3fd2d3SAndreas Gohr     * @return RangeRetrieval
3800b3fd2d3SAndreas Gohr     */
3810b3fd2d3SAndreas Gohr    public function range(): RangeRetrieval
3820b3fd2d3SAndreas Gohr    {
3830b3fd2d3SAndreas Gohr        return new RangeRetrieval($this);
3840b3fd2d3SAndreas Gohr    }
3850b3fd2d3SAndreas Gohr
3860b3fd2d3SAndreas Gohr    /**
3870b3fd2d3SAndreas Gohr     * Access to add/set/remove/reset the controls to be used for each request. If you want request specific controls in
3880b3fd2d3SAndreas Gohr     * addition to these, then pass them as a parameter to the send() method.
3890b3fd2d3SAndreas Gohr     *
3900b3fd2d3SAndreas Gohr     * @return ControlBag
3910b3fd2d3SAndreas Gohr     */
3920b3fd2d3SAndreas Gohr    public function controls(): ControlBag
3930b3fd2d3SAndreas Gohr    {
3940b3fd2d3SAndreas Gohr        return $this->handler()->controls();
3950b3fd2d3SAndreas Gohr    }
3960b3fd2d3SAndreas Gohr
3970b3fd2d3SAndreas Gohr    /**
3980b3fd2d3SAndreas Gohr     * Get the options currently set.
3990b3fd2d3SAndreas Gohr     *
4000b3fd2d3SAndreas Gohr     * @return array
4010b3fd2d3SAndreas Gohr     */
4020b3fd2d3SAndreas Gohr    public function getOptions(): array
4030b3fd2d3SAndreas Gohr    {
4040b3fd2d3SAndreas Gohr        return $this->options;
4050b3fd2d3SAndreas Gohr    }
4060b3fd2d3SAndreas Gohr
4070b3fd2d3SAndreas Gohr    /**
408*dad993c5SAndreas Gohr     * Merge a set of options. Depending on what you are changing, you many want to set the $forceDisconnect param to
409*dad993c5SAndreas Gohr     * true, which forces the client to disconnect. After which you would have to manually bind again.
4100b3fd2d3SAndreas Gohr     *
411*dad993c5SAndreas Gohr     * @param array $options The set of options to merge in.
412*dad993c5SAndreas Gohr     * @param bool $forceDisconnect Whether the client should disconnect; forcing a manual re-connect / bind. This is
413*dad993c5SAndreas Gohr     *                              false by default.
4140b3fd2d3SAndreas Gohr     * @return $this
4150b3fd2d3SAndreas Gohr     */
416*dad993c5SAndreas Gohr    public function setOptions(
417*dad993c5SAndreas Gohr        array $options,
418*dad993c5SAndreas Gohr        bool $forceDisconnect = false
419*dad993c5SAndreas Gohr    ): self {
420*dad993c5SAndreas Gohr        $this->options = array_merge(
421*dad993c5SAndreas Gohr            $this->options,
422*dad993c5SAndreas Gohr            $options
423*dad993c5SAndreas Gohr        );
424*dad993c5SAndreas Gohr        if ($forceDisconnect) {
425*dad993c5SAndreas Gohr            $this->unbindIfConnected();
426*dad993c5SAndreas Gohr        }
4270b3fd2d3SAndreas Gohr
4280b3fd2d3SAndreas Gohr        return $this;
4290b3fd2d3SAndreas Gohr    }
4300b3fd2d3SAndreas Gohr
4310b3fd2d3SAndreas Gohr    /**
4320b3fd2d3SAndreas Gohr     * @param ClientProtocolHandler|null $handler
4330b3fd2d3SAndreas Gohr     * @return $this
4340b3fd2d3SAndreas Gohr     */
435*dad993c5SAndreas Gohr    public function setProtocolHandler(ClientProtocolHandler $handler = null): self
4360b3fd2d3SAndreas Gohr    {
4370b3fd2d3SAndreas Gohr        $this->handler = $handler;
4380b3fd2d3SAndreas Gohr
4390b3fd2d3SAndreas Gohr        return $this;
4400b3fd2d3SAndreas Gohr    }
4410b3fd2d3SAndreas Gohr
4420b3fd2d3SAndreas Gohr    /**
4430b3fd2d3SAndreas Gohr     * A simple check to determine if this client has an established connection to a server.
4440b3fd2d3SAndreas Gohr     *
4450b3fd2d3SAndreas Gohr     * @return bool
4460b3fd2d3SAndreas Gohr     */
4470b3fd2d3SAndreas Gohr    public function isConnected(): bool
4480b3fd2d3SAndreas Gohr    {
4490b3fd2d3SAndreas Gohr        return ($this->handler !== null && $this->handler->isConnected());
4500b3fd2d3SAndreas Gohr    }
4510b3fd2d3SAndreas Gohr
4520b3fd2d3SAndreas Gohr    /**
4530b3fd2d3SAndreas Gohr     * Try to clean-up if needed.
454*dad993c5SAndreas Gohr     *
455*dad993c5SAndreas Gohr     * @throws Exception\ConnectionException
456*dad993c5SAndreas Gohr     * @throws OperationException
4570b3fd2d3SAndreas Gohr     */
4580b3fd2d3SAndreas Gohr    public function __destruct()
4590b3fd2d3SAndreas Gohr    {
460*dad993c5SAndreas Gohr        $this->unbindIfConnected();
4610b3fd2d3SAndreas Gohr    }
4620b3fd2d3SAndreas Gohr
4630b3fd2d3SAndreas Gohr    protected function handler(): ClientProtocolHandler
4640b3fd2d3SAndreas Gohr    {
4650b3fd2d3SAndreas Gohr        if ($this->handler === null) {
4660b3fd2d3SAndreas Gohr            $this->handler = new Protocol\ClientProtocolHandler($this->options);
4670b3fd2d3SAndreas Gohr        }
4680b3fd2d3SAndreas Gohr
4690b3fd2d3SAndreas Gohr        return $this->handler;
4700b3fd2d3SAndreas Gohr    }
471*dad993c5SAndreas Gohr
472*dad993c5SAndreas Gohr    /**
473*dad993c5SAndreas Gohr     * @throws Exception\ConnectionException
474*dad993c5SAndreas Gohr     * @throws OperationException
475*dad993c5SAndreas Gohr     */
476*dad993c5SAndreas Gohr    private function unbindIfConnected(): void
477*dad993c5SAndreas Gohr    {
478*dad993c5SAndreas Gohr        if ($this->handler !== null && $this->handler->isConnected()) {
479*dad993c5SAndreas Gohr            $this->unbind();
480*dad993c5SAndreas Gohr        }
481*dad993c5SAndreas Gohr    }
4820b3fd2d3SAndreas Gohr}
483