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