Skip to content

Commit 9827437

Browse files
committed
Add docs for syncrepl.
1 parent dfd225e commit 9827437

File tree

4 files changed

+279
-32
lines changed

4 files changed

+279
-32
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ It supports encryption of the LDAP connection through TLS via the OpenSSL extens
2525
* [Searching and Filters](/docs/Client/Searching-and-Filters.md)
2626
* [Range Retrieval](/docs/Client/Range-Retrieval.md)
2727
* [DirSync](/docs/Client/DirSync.md)
28+
* [SyncRepl](/docs/Client/SyncRepl.md)
2829
* [LDAP Server](/docs/Server)
2930
* [Configuration](/docs/Server/Configuration.md)
3031
* [General Usage](/docs/Server/General-Usage.md)

docs/Client/SyncRepl.md

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
SyncRepl
2+
================
3+
4+
SyncRepl leverages Directory Synchronization described in RFC-4533. It was first implemented in OpenLDAP, but has implementations
5+
in other directory servers. It can be used to track / sync changes against LDAP entries.
6+
7+
The sync process extends the LDAP search functionality and can contain any valid LDAP filter you want. There is an included
8+
helper class in this library for performing a SyncRepl request more easily.
9+
10+
* [General Usage](#general-usage)
11+
* [The Polling Method](#the-polling-method)
12+
* [The Listen Method](#the-listen-method)
13+
* [Sync Handlers](#sync-handlers)
14+
* [The Entry Handler](#the-entry-handler)
15+
* [The IdSet Handler](#the-idset-handler)
16+
* [The Referral Handler](#the-referral-handler)
17+
* [The Cookie Handler](#the-cookie-handler)
18+
* [SyncRepl Class Methods](#syncrepl-class-methods)
19+
* [useFilter](#usefilter)
20+
* [useCookie](#usecookie)
21+
* [useCookieHandler](#usecookiehandler)
22+
* [useEntryHandler](#useentryhandler)
23+
* [useIdSetHandler](#useidsethandler)
24+
* [useReferralHandler](#usereferralhandler)
25+
26+
# General Usage
27+
28+
To use the SyncRepl helper class you can instantiate it from the main LdapClient class using the `syncRepl()` method:
29+
30+
```php
31+
use FreeDSx\Ldap\Search\Filters;
32+
use FreeDSx\Ldap\Operations;
33+
34+
# The most simple way to start SyncRepl:
35+
# * Uses the default baseDn provided in the client options.
36+
# * Uses the LDAP filter '(objectClass=*)', which all return all entries.
37+
$syncRepl = $client->syncRepl();
38+
39+
# Can optionally pass a specific filter as an argument.
40+
# For example, only sync user changes.
41+
$syncRepl = $client->syncRepl(Filters::equal('objectClass', 'user'));
42+
```
43+
44+
There are two main methods for making use of the helper class listed below, depending on which better fits your needs.
45+
There are also several methods available on the SyncRepl class for further customizing how it should work, which are also
46+
defined further below.
47+
48+
## The Polling Method
49+
50+
The polling method iterates through all sync changes then stops. You would then call it at some future point using the
51+
same sync session cookie to see what has changed since the last polling.
52+
53+
```php
54+
use FreeDSx\Ldap\Sync\Result\SyncEntryResult;
55+
56+
// Saving the cookie to a file.
57+
// With the cookie handler, you determine where to save it.
58+
$cookieFile = __DIR__ . '/.sync_cookie';
59+
60+
// Retrieve a previous sync cookie if you have one.
61+
// If you provide a null cookie, the poll will return initial content.
62+
$cookie = file_get_contents($cookieFile) ?: null;
63+
64+
$ldap
65+
->syncRepl()
66+
->useCookie($cookie)
67+
// The cookie may change at many points during a sync. This handler should react to the new cookie to save it off
68+
// somewhere to be used in the future.
69+
->useCookieHandler(fn (string $cookie) => file_put_contents($cookieFile, $cookie))
70+
->poll(function(SyncEntryResult $result) {
71+
$entry = $result->getEntry();
72+
$uuid = $result->getEntryUuid();
73+
74+
// "Add" here means either it changed **or** was added.
75+
if ($result->isAdd()) {
76+
77+
// This should represent an entry being modified...but in OpenLDAP, I have not seen this used?
78+
} elseif ($result->isModify()) {
79+
// The entry was removed. Note that the entry attributes will be empty in this case.
80+
// Use the UUID from the result to remove it on the sync side.
81+
} elseif ($result->isDelete()) {
82+
// The entry is present and has not changed.
83+
} elseif ($result->isPresent()) {
84+
}
85+
});
86+
```
87+
88+
## The Listen Method
89+
90+
The listen method iterates waits for sync changes in a never-ending search operation.
91+
92+
```php
93+
use FreeDSx\Ldap\Sync\Result\SyncEntryResult;
94+
95+
// Saving the cookie to a file.
96+
// With the cookie handler, you determine where to save it.
97+
$cookieFile = __DIR__ . '/.sync_cookie';
98+
99+
// Retrieve a previous sync cookie if you have one.
100+
// If you provide a null cookie, the poll will return initial content.
101+
$cookie = file_get_contents($cookieFile) ?: null;
102+
103+
$ldap
104+
->syncRepl()
105+
->useCookie($cookie)
106+
// The cookie may change at many points during a sync. This handler should react to the new cookie to save it off
107+
// somewhere to be used in the future.
108+
->useCookieHandler(fn (string $cookie) => file_put_contents($cookieFile, $cookie))
109+
->listen(function(SyncEntryResult $result) {
110+
$entry = $result->getEntry();
111+
$uuid = $result->getEntryUuid();
112+
113+
// "Add" here means either it changed **or** was added.
114+
if ($result->isAdd()) {
115+
116+
// This should represent an entry being modified...but in OpenLDAP, I have not seen this used?
117+
} elseif ($result->isModify()) {
118+
// The entry was removed. Note that the entry attributes will be empty in this case.
119+
// Use the UUID from the result to remove it on the sync side.
120+
} elseif ($result->isDelete()) {
121+
// The entry is present and has not changed.
122+
} elseif ($result->isPresent()) {
123+
}
124+
});
125+
```
126+
127+
# Sync Handlers
128+
129+
There are three main handlers you can define to react to sync messages that are encountered. Not all are needed, as you
130+
could choose to ignore referrals. However, you should take action on both Entry and IdSet changes.
131+
132+
## The Entry Handler
133+
134+
The Entry handler should always be defined. It is passed to either the `poll()` or `listen()` method directly, or can optionally
135+
be passed to the `useEntryHandler()` method. This handler must be a closure that receives a `SyncEntryResult` as the first argument.
136+
The `SyncEntryResult` represents a single sync entry change.
137+
138+
For more details, see [useEntryHandler](#useentryhandler).
139+
140+
## The IdSet Handler
141+
142+
The IdSet handler is set using the `useIdSetHandler()` method. This handler must be a closure that receives a `SyncIdSetResult`
143+
as the first argument. The `SyncIdSetResult` represents multiple entry changes in LDAP, however the change represented is
144+
only one of: delete, present.
145+
146+
For more details, see [useIdSetHandler](#useidsethandler). You should define this handler to react to large LDAP sync changes.
147+
148+
## The Referral Handler
149+
150+
The Referral handler is set using the `useReferralHandler()` method. This handler must be a closure that receives a `SyncReferralResult`
151+
as the first argument. The `SyncReferralResult` represents an entry that has changed but is located on a different server via a referral.
152+
If you do not want to sync referral information, these can be ignored.
153+
154+
For more details, see [useReferralHandler](#usereferralhandler).
155+
156+
## The Cookie Handler
157+
158+
The Cookie handler is set using the `useCookieHandler()` method. This handler must be a closure that receives a `string` cookie value
159+
as the first argument. This handler is different from the others as it does not represent a sync change, but a change in
160+
the sync session cookie. If you wish to restart this sync session at some later point, you should be defining this to save
161+
the changed cookie somewhere and reload it before starting the sync again.
162+
163+
For more details, see [useCookieHandler](#usecookiehandler) and [useCookie](#usecookie).
164+
165+
# SyncRepl Class Methods
166+
167+
## useCookie
168+
169+
You can use the `useCookie()` method to explicitly set the cookie for the sync. The cookie is an opaque, binary value,
170+
that is used to identify the sync. For instance, if you start the sync and want to later restart it, you could save the
171+
cookie value somewhere then set it here with this method to restart the sync:
172+
173+
**Note**: This assumes that server will still accept the cookie as valid. It may not and decide to force an initial sync.
174+
175+
```php
176+
# Set the cookie later to potentially resume the sync where you left off
177+
# Continue with the getChanges() / hasChanges() like you normally would
178+
$syncRepl->useCookie($cookie);
179+
```
180+
181+
## useCookieHandler
182+
183+
This method takes a closure that can be used to save the cookie as it changes during the sync process. You will want to
184+
use this if you plan to reuse a previous sync session.
185+
186+
Below is a very simple example of using this to save the cookie off to a local file:
187+
188+
```php
189+
// Saving the cookie to a file.
190+
// With the cookie handler, you determine where to save it.
191+
$cookieFile = __DIR__ . '/.sync_cookie';
192+
193+
$syncRepl->useCookieHandler(
194+
fn (string $cookie) => file_put_contents(
195+
$cookieFile,
196+
$cookie,
197+
)
198+
);
199+
```
200+
201+
## useEntryHandler
202+
203+
This method takes a closure that reacts to a single entry change / sync. It is basically required if you want to get
204+
anything useful from the sync.
205+
206+
```php
207+
use FreeDSx\Ldap\Sync\Result\SyncEntryResult;
208+
use FreeDSx\Ldap\Control\Sync\SyncStateControl;
209+
210+
$syncRepl->useEntryHandler(function(SyncEntryResult $result) {
211+
// The Entry object associated with this sync change.
212+
$result->getEntry();
213+
// The raw result state of the entry. See "SyncStateControl::STATE_*"
214+
$result->getState();
215+
// The raw LDAP message for this entry. Can get the result code / controls / etc.
216+
$result->getMessage();
217+
});
218+
```
219+
220+
## useIdSetHandler
221+
222+
This method defines a closure that handles IdSets received during the sync process. IdSets are arrays of entry UUIDs
223+
that represent a large set of entry deletes or entries still present, but do not contain other information about the
224+
records (such as the full Entry object).
225+
226+
You should define a handler for this, otherwise you may miss important large sync changes.
227+
228+
```php
229+
use FreeDSx\Ldap\Sync\Result\SyncIdSetResult;
230+
use FreeDSx\Ldap\Control\Sync\SyncStateControl;
231+
232+
$syncRepl->useIdSetHandler(function(SyncIdSetResult $result) {
233+
// The array of UUID entry strings that have changed.
234+
$result->getEntryUuids();
235+
// Are the entries represented present?
236+
$result->isPresent();
237+
// Are the entries represented deleted?
238+
$result->isDeleted();
239+
});
240+
```
241+
242+
## useReferralHandler
243+
244+
This method defines a closure that handles referrals received during the sync process. If you do not care to handle
245+
referrals, you do not have to define this.
246+
247+
```php
248+
use FreeDSx\Ldap\Sync\Result\SyncReferralResult;
249+
use FreeDSx\Ldap\Control\Sync\SyncStateControl;
250+
251+
$syncRepl->useReferralHandler(function(SyncReferralResult $result) {
252+
// The array of LdapUrl objects for this referral result.
253+
$result->getReferrals();
254+
// The raw result state of the referral. See "SyncStateControl::STATE_*"
255+
$result->getState();
256+
// The raw LDAP message for this referral. Can get the result code / controls / etc.
257+
$result->getMessage();
258+
});
259+
```
260+
261+
## useFilter
262+
263+
This method can be passed an object implementing the `FilterInterface`. This is the same filter that is constructed for
264+
an LDAP search with the client. This filter is what limits the results for what is returned for the changes:
265+
266+
```php
267+
use FreeDSx\Ldap\Search\Filters;
268+
269+
# Use the Filters factory helper to construct the LDAP filter to use.
270+
# This filter would limit the results to just user objects.
271+
$syncRepl->useFilter(Filters::equal('objectClass', 'user'));
272+
```

src/FreeDSx/Ldap/Sync/SyncRepl.php

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ class SyncRepl
4444

4545
public function __construct(
4646
LdapClient $client,
47-
?SyncRequest $syncRequest = null
47+
?FilterInterface $filter = null
4848
) {
4949
$this->client = $client;
50-
$this->syncRequest = $syncRequest ?? Operations::sync();
50+
$this->syncRequest = Operations::sync($filter);
5151
$this->controls = new ControlBag();
5252
}
5353

@@ -102,13 +102,6 @@ public function useIdSetHandler(Closure $handler): self
102102
return $this;
103103
}
104104

105-
public function useRequest(SyncRequest $syncRequest): self
106-
{
107-
$this->syncRequest = $syncRequest;
108-
109-
return $this;
110-
}
111-
112105
/**
113106
* A convenience method to set the filter to use for this sync. This can also be set using {@see self::request()}.
114107
*/
@@ -120,8 +113,8 @@ public function useFilter(FilterInterface $filter): self
120113
}
121114

122115
/**
123-
* Set the cookie to use as part of the sync operation. This should be a cookie from a previous sync. To retrieve the
124-
* cookie during the sync use {@see Session::getCookie()} from the Sync session in the handlers.
116+
* Set the cookie to use as part of the sync operation. This should be a cookie from a previous sync. To retrieve
117+
* the cookie during the sync use {@see Session::getCookie()} from the Sync session in the handlers.
125118
*/
126119
public function useCookie(?string $cookie): self
127120
{
@@ -150,7 +143,8 @@ public function request(): SyncRequest
150143
* In a listen based sync, the server sends updates of entries that are changed after the initial refresh content is
151144
* determined. The sync continues indefinitely until the connection is terminated or the sync is canceled.
152145
*
153-
* **Note**: The LdapClient should be instantiated with no timeout via {@see ClientOptions::setTimeoutRead(-1)}. Otherwise, the listen operation will terminate due to a network timeout.
146+
* **Note**: The LdapClient should be instantiated with no timeout via {@see ClientOptions::setTimeoutRead(-1)}.
147+
* Otherwise, the listen operation will terminate due to a network timeout.
154148
*/
155149
public function listen(Closure $entryHandler = null): void
156150
{

tests/spec/FreeDSx/Ldap/Sync/SyncReplSpec.php

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -97,26 +97,6 @@ public function it_should_use_a_filter_if_specified(LdapClient $client): void
9797
$this->poll();
9898
}
9999

100-
public function it_should_use_a_sync_request_if_specified(LdapClient $client): void
101-
{
102-
$syncRequest = new SyncRequest(Filters::present('foo'));
103-
104-
$this->useRequest($syncRequest);
105-
106-
$client->sendAndReceive(
107-
Argument::exact($syncRequest),
108-
Argument::any(),
109-
Argument::any(),
110-
)->shouldBeCalledOnce()
111-
->willReturn(new LdapMessageResponse(
112-
1,
113-
new SearchResultDone(0),
114-
new SyncDoneControl('foo')
115-
));
116-
117-
$this->poll();
118-
}
119-
120100
public function it_should_use_added_controls_if_specified(LdapClient $client): void
121101
{
122102
$control = new Control('foo');

0 commit comments

Comments
 (0)