1<?php 2/** 3 * This file is part of the FreeDSx LDAP package. 4 * 5 * (c) Chad Sikorra <Chad.Sikorra@gmail.com> 6 * 7 * For the full copyright and license information, please view the LICENSE 8 * file that was distributed with this source code. 9 */ 10 11namespace FreeDSx\Ldap\Search; 12 13use FreeDSx\Ldap\Control\Control; 14use FreeDSx\Ldap\Control\PagingControl; 15use FreeDSx\Ldap\Controls; 16use FreeDSx\Ldap\Entry\Entries; 17use FreeDSx\Ldap\Exception\ProtocolException; 18use FreeDSx\Ldap\LdapClient; 19use FreeDSx\Ldap\Operation\Request\SearchRequest; 20use FreeDSx\Ldap\Operation\Response\SearchResponse; 21 22/** 23 * Provides a simple wrapper around paging a search operation. 24 * 25 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 26 */ 27class Paging 28{ 29 /** 30 * @var PagingControl|null 31 */ 32 protected $control; 33 34 /** 35 * @var LdapClient 36 */ 37 protected $client; 38 39 /** 40 * @var int 41 */ 42 protected $size; 43 44 /** 45 * @var SearchRequest 46 */ 47 protected $search; 48 49 /** 50 * @var bool 51 */ 52 protected $ended = false; 53 54 /** 55 * @param LdapClient $client 56 * @param SearchRequest $search 57 * @param int $size 58 */ 59 public function __construct(LdapClient $client, SearchRequest $search, int $size = 1000) 60 { 61 $this->search = $search; 62 $this->client = $client; 63 $this->size = $size; 64 } 65 66 /** 67 * Start a new paging operation with a search request. This must be called first if you reuse the paging object. 68 * 69 * @param SearchRequest $search 70 * @param int|null $size 71 */ 72 public function start(SearchRequest $search, ?int $size = null): void 73 { 74 $this->size = $size ?? $this->size; 75 $this->search = $search; 76 $this->control = null; 77 $this->ended = false; 78 } 79 80 /** 81 * End the paging operation. This can be triggered at any time. 82 * 83 * @return $this 84 */ 85 public function end() 86 { 87 $this->send(0); 88 $this->ended = true; 89 90 return $this; 91 } 92 93 /** 94 * Get the next set of entries of results. 95 * 96 * @param int|null $size 97 * @return Entries 98 */ 99 public function getEntries(?int $size = null): Entries 100 { 101 return $this->send($size); 102 } 103 104 /** 105 * @return bool 106 */ 107 public function hasEntries() 108 { 109 if ($this->ended) { 110 return false; 111 } 112 113 return $this->control === null || !($this->control->getCookie() === ''); 114 } 115 116 /** 117 * The size may be set to the server's estimate of the total number of entries in the entire result set. Servers 118 * that cannot provide such an estimate may set this size to zero. 119 * 120 * @return int|null 121 */ 122 public function sizeEstimate(): ?int 123 { 124 return ($this->control !== null) ? $this->control->getSize() : null; 125 } 126 127 /** 128 * @param int|null $size 129 * @return Entries 130 * @throws ProtocolException 131 */ 132 protected function send(?int $size = null) 133 { 134 $cookie = ($this->control !== null) ? $this->control->getCookie() : ''; 135 $message = $this->client->sendAndReceive($this->search, Controls::paging($size ?? $this->size, $cookie)); 136 $control = $message->controls()->get(Control::OID_PAGING); 137 if ($control !== null && !$control instanceof PagingControl) { 138 throw new ProtocolException(sprintf( 139 'Expected a paging control, but received: %s.', 140 get_class($control) 141 )); 142 } 143 # OpenLDAP returns no paging control in response to an abandon request. However, other LDAP implementations do; 144 # such as Active Directory. It's not clear from the paging RFC which is correct. 145 if ($control === null && $size !== 0) { 146 throw new ProtocolException('Expected a paging control, but received none.'); 147 } 148 $this->control = $control; 149 /** @var SearchResponse $response */ 150 $response = $message->getResponse(); 151 152 return $response->getEntries(); 153 } 154} 155