PEAR is archived and read-only

This mirror preserves historical PEAR package releases and metadata so existing references remain available.

Home » Networking » Net_LDAP2 » Manual

Net_LDAP2 allows you to query and manipulate the data stored in directory servers using PHP in an object-oriented way. Detailed usage examples are available at: /your/pear/path/docs/Net_LDAP2/examples/

Introduction

Introduction – What Net_LDAP2 is and general information

Welcome to Net_LDAP2!

Net_LDAP2 is a clone of Perls Net::LDAP package. PEAR Net_LDAP2 for PHP does, besides some own features, provide most of Perl Net::LDAP methods. Net_LDAP2 allows you to query and manipulate the data stored in directory servers using PHP in an object-oriented way. A directory server is a database server providing a hierarchical database and is usually queried using the LDAP protocol.

Net_LDAP2 is intendet as replacement of Net_LDAP.

Classes of the Net_LDAP2 package

The following table gives you a short overview which classes are available in Net_LDAP2 and what they can be used for. Their relations will be explained too.

Classes of Net_LDAP2
Class name Description
Net_LDAP2 This is the main class. It enables you to connect and bind to a LDAP-server and to run ldap-querys like searching and manipulating entries. Most common, you will run a search using a LDAP-Filter and will get a Net_LDAP2_Search-object. You may also fetch an entry directly, which gives you a Net_LDAP2_Entry-object.
Net_LDAP2_Search Objects of this class are returned from search querys. You can use this object to retrieve informations about a search result, like how much entries you have found for the provided filter. You can retrieve the found entries as Net_LDAP2_Entry-objects in various forms: sorted, consecutively starting from the end or beginning or unsorted at once.
Net_LDAP2_Entry Objects of this kind are either produced by casting fresh entries manually, by retrieving the result of a LDAP-search or by fetching an entry directly. It gives you the possibility to read and/or manipulate the attributes of an entry which describes the characteristic of the specific object.
Net_LDAP2_Util The utility class contains only static methods, so you should not need to make an instance of it. It features some helpful methods, some of them are used internally by Net_LDAP2 but may be used externally from you as well. The most methods deal with escaping issues, since LDAP has some metacharacters with special meaning so that they usually need to be properly escaped.
Net_LDAP2_Filter You are free to give LDAP-Filters on your own to the Net_LDAP2->search() method, however this has some drawbacks (including escaping issues). For this reason, you can use the Net_LDAP2_Filter class to easily build and combine your filters. LDAP filters are extensively explained at the chapter LDAP filters.
Net_LDAP2_Error This is a error class. Most methods of Net_LDAP2 will return a object of this class if something went wrong. You can use this object to identify errors and to get detailed knowledge on what went wrong. See Errorhandling for more information.
Net_LDAP2_LDIF LDIF files are human readable, plain text files containing directory data and/or change commands, much like an SQL file. Unlike SQL files, it is data centric, not action centric. Net_LDAP2_LDIF enables you to convert between Net_LDAP2_Entry-objects and LDIF files. Please note, that Net_LDAP2_LDIF has a little different error handling explained later.
Net_LDAP2_SimpleFileSchemaCache Net_LDAP2 features a schema caching facility. This class implements a simple file based cache that allows Net_LDAP2 to store the schema data that get fetched from LDAP inside a file to accelerate schema access. Caching the schema can gain some performance, especially with slow servers or connections. You may use this cache class for example to store the schema object in a linux tmpfs which will result in caching the schema in the computers memory, enabling nearly instant access. This cache also features a cache ageing mechanism. Please see Schema caching for more information.

Error handling

Error handling – How handling errors works in Net_LDAP2

Error handling

Nearly all of Net_LDAPs methods return a Net_LDAP2_Error object if something went wrong. You always should check for errors after you performed an action to be sure that your application doesn't do things you don't want it to do.

Handling errors is an easy task, you just have to test the return value as shown below. If an error occured, you can halt the script for example. In other cases, you may just log the error, but what exactly happens depends on your specific situation, of course.

You can use the getMessage() method of the error object to retrieve the error message explaining the problem and getCode() to get the error code which is usually the LDAP-Error code (see Table below) and may be used for automated reaction on errors.

Dealing with errors

<?php
// Perform an arbitrary action:
$result = $ldap->search($searchbase, $filter, $options);

// Check, if an error occured and do something.
// Here we use die() to show the message of the error.
if (Net_LDAP2::isError($result)) {
    die($result->getMessage());
}
?>
Error codes Net_LDAP2
Error code Description
0x00LDAP_SUCCESS
0x01LDAP_OPERATIONS_ERROR
0x02LDAP_PROTOCOL_ERROR
0x03LDAP_TIMELIMIT_EXCEEDED
0x04LDAP_SIZELIMIT_EXCEEDED
0x05LDAP_COMPARE_FALSE
0x06LDAP_COMPARE_TRUE
0x07LDAP_AUTH_METHOD_NOT_SUPPORTED
0x08LDAP_STRONG_AUTH_REQUIRED
0x09LDAP_PARTIAL_RESULTS
0x0aLDAP_REFERRAL
0x0bLDAP_ADMINLIMIT_EXCEEDED
0x0cLDAP_UNAVAILABLE_CRITICAL_EXTENSION
0x0dLDAP_CONFIDENTIALITY_REQUIRED
0x0eLDAP_SASL_BIND_INPROGRESS
0x10LDAP_NO_SUCH_ATTRIBUTE
0x11LDAP_UNDEFINED_TYPE
0x12LDAP_INAPPROPRIATE_MATCHING
0x13LDAP_CONSTRAINT_VIOLATION
0x14LDAP_TYPE_OR_VALUE_EXISTS
0x15LDAP_INVALID_SYNTAX
0x20LDAP_NO_SUCH_OBJECT
0x21LDAP_ALIAS_PROBLEM
0x22LDAP_INVALID_DN_SYNTAX
0x23LDAP_IS_LEAF
0x24LDAP_ALIAS_DEREF_PROBLEM
0x30LDAP_INAPPROPRIATE_AUTH
0x31LDAP_INVALID_CREDENTIALS
0x32LDAP_INSUFFICIENT_ACCESS
0x33LDAP_BUSY
0x34LDAP_UNAVAILABLE
0x35LDAP_UNWILLING_TO_PERFORM
0x36LDAP_LOOP_DETECT
0x3CLDAP_SORT_CONTROL_MISSING
0x3DLDAP_INDEX_RANGE_ERROR
0x40LDAP_NAMING_VIOLATION
0x41LDAP_OBJECT_CLASS_VIOLATION
0x42LDAP_NOT_ALLOWED_ON_NONLEAF
0x43LDAP_NOT_ALLOWED_ON_RDN
0x44LDAP_ALREADY_EXISTS
0x45LDAP_NO_OBJECT_CLASS_MODS
0x46LDAP_RESULTS_TOO_LARGE
0x47LDAP_AFFECTS_MULTIPLE_DSAS
0x50LDAP_OTHER
0x51LDAP_SERVER_DOWN
0x52LDAP_LOCAL_ERROR
0x53LDAP_ENCODING_ERROR
0x54LDAP_DECODING_ERROR
0x55LDAP_TIMEOUT
0x56LDAP_AUTH_UNKNOWN
0x57LDAP_FILTER_ERROR
0x58LDAP_USER_CANCELLED
0x59LDAP_PARAM_ERROR
0x5aLDAP_NO_MEMORY
0x5bLDAP_CONNECT_ERROR
0x5cLDAP_NOT_SUPPORTED
0x5dLDAP_CONTROL_NOT_FOUND
0x5eLDAP_NO_RESULTS_RETURNED
0x5fLDAP_MORE_RESULTS_TO_RETURN
0x60LDAP_CLIENT_LOOP
0x61LDAP_REFERRAL_LIMIT_EXCEEDED
1000Unknown Net_LDAP2 Error

Configuration and connecting

Configuration and connecting – How to configure Net_LDAP2 and connect to an LDAP server

Connecting to an LDAP server

To connect to an LDAP server, you should use Net_LDAP2's static connect() method. It takes one parameter, an array full of configuration options, and either returns a Net_LDAP2 object if connecting works, or a Net_LDAP2_Error object in case of a failure.

The following table lists all configuration options. If the default value for an option fits your needs, you don't need add it to your configuration array.

Possible configuration options
Name Description Default
host LDAP server name to connect to. You can provide several hosts in an array in which case the hosts are tried from left to right. localhost
port Port on the server 389
version LDAP version 3
starttls TLS is started after connecting false
binddn The distinguished name to bind as (username). If you don't supply this, an anonymous bind will be established. (none)
bindpw Password for the binddn. If the credentials are wrong, the bind will fail server-side and an anonymous bind will be established instead. An empty bindpw string requests an unauthenticated bind. This can cause security problems in your application, if you rely on a bind to make security decisions (see RFC-4513, section 6.3.1). (none)
basedn LDAP base name (root directory) (none)
options Array of additional ldap options as key-value pairs array()
filter Default search filter (string or preferably Net_LDAP2_Filter object). See LDAP filters (objectClass=*)
scope Default search scope, see Search sub

Connecting to an LDAP server

<?php
// Inclusion of the Net_LDAP2 package:
require_once 'Net/LDAP2.php';

// The configuration array:
$config = array (
    'binddn'    => 'cn=admin,ou=users,dc=example,dc=org',
    'bindpw'    => 'password',
    'basedn'    => 'dc=example,dc=org',
    'host'      => 'ldap.example.org'
);

// Connecting using the configuration:
$ldap = Net_LDAP2::connect($config);

// Testing for connection error
if (PEAR::isError($ldap)) {
    die('Could not connect to LDAP-server: '.$ldap->getMessage());
}
?>

Fetching entries

Fetching entries – Retrieving entries directly or from a searchresult

Retrieving entries directly

You can retrieve directory entries in several ways, either directly or from a performed search request. If you want to fetch an entry directly, you need to know its absolute distinguished name (DN). To directly fetch an known entry from the directory server, you use Net_LDAP2's getEntry() method. It takes two parameters: The DN of the entry and the attributes you want to read from the entry. It returns a Net_LDAP2_Entry object if fetching worked, or a Net_LDAP2_Error object in case of a failure.

You may also check if the entry exists in the server before you fetch it. This can be achieved by Net_LDAP2's dnExists() which takes the DN to test and returns either true or false.

Fetching an entry directly

<?php
// Defining the DN we want to fetch;
// we want to select the given- and the surname
$dn = 'cn=admin,o=example,dc=org';
$entry = $ldap->getEntry($dn, array('gn', 'sn'));

// Error checking is important!
if (Net_LDAP2::isError($entry)) {
    die('Could not fetch entry: '.$entry->getMessage());
}
?>

Retrieving entries from a searchresult

The second way to retrieve entries is from a searchresult. As described in chapter "Search", you access the entries of a search result through the Net_LDAP2_Search-object resulting from Net_LDAP2's search() method. Each of the following methods return a Net_LDAP2_Error-object if something goes wrong, so remember to test for errors! You have several ways to read the entries:

Possible ways to fetch entries
Method of Net_LDAP2_Search Description
entries() This returns the entries at once unsorted.
as_struct() This returns all entries as multidimensional array instead of Net_LDAP2_Entry-objects. The array keys of the first dimension are the DNs and the value is an array containing all attributes. The array keys of the second level are the attributes names; the value of the second level is an array containing all the attributes values. Note, that even if there are no or just one value, an array is present.
sorted() Use this if you want to get the entries at once but sorted. You can sort by several attributes which can contain multiple values. You can of course sort ascending (default) or descending - just pass the PHP constant SORT_ASC or SORT_DESC as second parameter.
sorted_as_struct() Like as_struct(), this returns the entries as multidimensional array, but in this case sorted. For parameters, see sorted()
shiftEntry() This returns one entry from the beginning of the search result. Since this returns FALSE if all entries are fetched, shiftEntry() is perfectly appropriate to get used inside a while-loop. Take care not to mix shiftEntry() and popEntry()!
popEntry() Exactly the same as shiftEntry, but returns the entry from the end of the searchresult. Again, be sure to not mix shiftEntry() and popEntry()!

To directly fetch an known entry from the directory server, you use Net_LDAP2's getEntry() method. It takes two parameters: The DN of the entry and the attributes you want to read from the entry. It returns a Net_LDAP2_Entry object if fetching worked, or a Net_LDAP2_Error object in case of a failure.

You may also check if the entry exists in the server before you fetch it. This can be achieved by Net_LDAP2's dnExists() which takes the DN to test and returns either true or false.

Fetching all entries from searchresult

<?php
// return all entries:
$entry = $search->entries();
?>

Fetching all entries from searchresult: sorted

<?php
// return sorted by first 'sn' then 'gn', but descending:
$entry = $search->sorted(array('sn', 'gn'), SORT_DESC);
?>

Fetching entries one by one inside a while loop

<?php
// return entries one by one:
while ( $entry = $search->shiftEntry() ) {
    // do something, like printing the DN of the entry;
    // in a real case, dont forget to test for errors!
    echo "ENTRY: " . $entry->dn();
}
?>

Retrieving entries via iteration (foreach)

Since Net_LDAP2 you are able to use PHPs Standard Library (SPL) to iterate over search results. This is done easily by just using the Net_LDAP2_Search search result object inside an foreach loop similar to an array. You may optionally retrieve the DN of each entry by the same mechanism you use to retrieve the key of an associative array.

Fetching entries via foreach()

<?php
foreach ($search as $dn => $entry) {
    // do something:
    $sn = $entry->getValue('sn', 'single');
    echo "Fetched DN: $dn; Surname: $sn";
}
?>

Attributes

Attributes – Reading/adding/changing/deleting attributes from entries

Reading attributes

Reading attribute values depends on the selection of those attributes at search time. You can only access attributes that where selected! You can read attribute values using either Net_LDAP2_Entry's getValues() or getValue() method. getValue() will return an array where the keys are the attributes names. If you use getValues() you may pass an option:

Also note that if you try to fetch an attribute, that is not set at the entry, an empty string will be returned.

Reading attributes

<?php
// read Surename, singlevalued
$surename = $entry->getValue('sn', 'single');

// read mail adress which may be multivalued
$mail = $entry->getValue('mail', 'all');
?>

If you want to read the distinguished name of an Entry (DN), you must use a different method: dn()

Reading an entries DN

<?php
$dn = $entry->dn();
?>

Regular expressions on attributes

PEAR::Net_LDAP2 has the unique feature to apply a regular expression match directly against attributes, so you do not need to manually fetch all values and run the regex against them. Instead, you can use Net_LDAP2_Entry's preg_match() function. The behavior of this function is the same as PHPs preg_match(), but the $matches array is slightly different. It features one dimension more, since it may match for several attribute values if the attribute is multivalued. If you pass $matches, be sure to do it via REFERENCE, because otherwise $matches remains empty. preg_match() returns true or false, depending on match.

Performing preg_match on attribute values

<?php
// Look, if the user has an emailadress for 'example', if so,
// we want to display the tld:
// (be sure to pass $matches as reference!)
$matches = array();
if ( $entry->preg_match('mail', '/example\.(.+)/', &$matches) ) {
    // print every TLD found for 'example':
    foreach ($matches as $match) {
        echo $match[1];
    }
}
?>

General information regarding attribute changing

It is important to know how attribute changing works. Modifications to an entry through the Net_LDAP2_Entry-object are local only. After you have made all changes and want to transfer them to the directory server, you must call update() of the Net_LDAP2_Entry object. This will return either TRUE or an Net_LDAP2_Error. Another good information is, that you must select attributes at search time if you want to add/change/delete attribute values. Otherwise Net_LDAP2 will most likely fail silently giving you the wrong assumtion that everything was okay - Net_LDAP2 needs knowledge of the attributes it should work with!

Modification of attributes is also possible through Net_LDAP2's modify() method. This method will call the methods described here on the Net_LDAP2_Entry object given, and directly calls an update() after that, thus performing the changes directly on the server. The parameter is an complex array describing the changes to be performed. It is considered for more advanced users, because it is more compact, so please refer to the latest API documentation for more information.

Adding attributes

Adding attrbiute values to an entry is an easy task. You just need to call add()! The parameter is an array whose keys are the attribute names and values the attributes values. If only one attribute value should be added, the second level may be a string. If the attribute doesn't exist so far, it will be added, if it exists, the attributes values will be added.

Adding attributes

<?php
// Adding several attributes:
$result = $entry->add(
    array(
        'sn'   => 'Doe',
        'gn'   => array('John'),
        'mail' => array('john@doe.org', 'j.doe@example.org')
    )
);
?>

Changing attributes

Changing values is with the replace() method as easy as adding values. However, you have to be a little more careful. The expected parameter is an array describing the new absolute state of the named attributes. This means, if you specify a NULL value for an attribute, this attribute will get deleted! You may specify single values as string too. The keys of the array are expected to be the attributes names.

Changing attributes

<?php
// Changing several attributes:
// 'sn' is changed to "Smith", 'gn' gets deleted and mail will
// be changed to te two new adresses
$result = $entry->replace(
    array(
        'sn'   => 'Smith',
        'gn'   => null,
        'mail' => array('smith@example.org', 'smith@example.de')
    )
);
?>

Deleting attributes

Using the delete() method you are able to delete specific attributes values as well as delete a whole attribute. You need to specify the attribute names as array keys, the array values are the values you want to delete. If you want to delete whole attributes, specify them as single level array. Special care must be taken not to delete the whole entry which will be the case if the parameter array is omitted or set to NULL! Also, don't mix syntax modes. If you want to delete whole attributes you can't delete specific values from another attribute in the same function call.

Deleting attributes

<?php
// Delete the whole entry:
$result = $entry->delete();

// Delete the whole telephone number attribute:
$result = $entry->delete('telephoneNumber');

// Delete one specific mail attributes value:
$result = $entry->delete( array('mail' => 'j.doe@example.org') );

// Delete mail and telephone attributes as a whole:
$result = $entry->delete( array('mail', 'telephoneNumber') );

// Delete two specific mail adresses:
$result = $entry->delete( array('mail' => array('smith@example.org', 'smith@example.de')) );
?>

Changing Objectclasses

Object classes describe the attribute set of an entry with this objectclass set. The entry stores the objectclass in a special attribute named "objectClass", and of course you may alter that attribute like any other attribute.

However, special care must be taken if changing this attribute since most directory servers impose rules on the other attributes the object class define. For example, it is usually not possible to delete an objectclass if some of the attributes the class describes are still in use by the entry. This should be not much of a problem with optional attributes, but sometimes objectclasses have mandatory attributes set. Also structural objectclasses can only be added when creating new entrys. Because of the internal architecture of Net_LDAP2 it is currently not possible to resolve those cases.

To add or remove objectclasses with mandatory attributes or new structural object classes, you need to delete the old entry from the directory server and add the new one with the new objectclass and attributes as fresh entry.

Changing complex objectclasses

<?php
// Let's assume that the objectclass myClass enforce the attribute "fooattr"
// Take care that you have all attributes requested, otherwise the new
// entry will not have all attributes set!
$entry->add(array(
    'objectClass'   => 'myClass',
    'fooatrr'       => 'foo',
    'someotherattr' => array('bar', 'baz')
    ));

// Calling $entry->update() now will not succeed under some circumstances!
// We construct a fresh entry object which is in fact a copy of the already
// existing entry with all changes already applied (the local copy).
// It is important, that at fetching time of $entry all attributes where selected!
// Only the selected attributes will get copied.
$changed_entry = Net_LDAP2_Entry::createFresh($entry->dn(), $entry->getValues());

// Now delete the old entry and add the new one:
$ldap->delete($entry);
$ldap->add($changed_entry);
?>

Schema checks

When operating on an LDAP connection, you might want to retrieve informations regarding the directory servers schema. Often this is the case to verify that your program only querys attributes that are valid for an entry or to ensure that you only try to write such attributes to the server.

To get that inforamtion, you can use the Net_LDAP2_Schema which is retrieved via the Net_LDAP2 object. It allows you to perform various querys, not only on attributes and object classes, but also on DIT content rules, for example. For often needed functionality, shorthand methods are implemented since version 2.0.10 like attributeExists(), objectClassExists(), getAssignedOCLs() and checkAttribute().

Performing basic schema checks

<?php
// Fetch the schema object for the connected directory server.
$schema = $ldap->schema();

// this may have failed since not every server allows us
// to fetch the schema without permission. Also technical
// problems may prevent us from this.
if ( Net_LDAP2::isError($schema) ) {
    die('SCHEMA ERROR: '.$schema->getMessage()."\n");
}

// lets see, if an attribute is defined in the schema:
if ( $schema->attributeExists('myCoolAttribute') ) {
    echo "Attribute 'myCoolAttribute' is defined in the schema!";
}

// lets see, if an object class is defined in the schema:
if ( $schema->attributeExists('myCoolOCL') ) {
    echo "Object class 'myCoolOCL' is defined in the schema!";
}

// Check, if the attribute is defined in objectClasses.
// This is especially useful if you want to know if
// attributes are valid for a given set of object classes.
if ( $schema->checkAttribute('myCoolAttribute', array('person', 'myCoolOCL')) ) {
    echo "Attribute 'myCoolAttribute' is defined for the given OCLs!";
}
?>

Managing entries

Managing entries – Adding/renaming/moving/deleting entries

Adding (fresh or old) entries to the directory

Adding new entries is performed in two ways. First, you need to establish a fresh Net_LDAP2_Entry object. After that, you can add that entry like you would add already-existent entries using Net_LDAP2's add() method.

Adding a fresh entry

<?php
// Build a new fresh entry:
$dn         = 'cn=new-admin,o=example,dc=org';
$attributes = array(
    'cn'              => 'new-admin',
    'mail'            => array('new-admin@exaple.org', 'n.admin@example.de'),
    'telephoneNumber' => '1234567890'
);
$entry = Net_LDAP2_Entry::createFresh($dn, $attributes);

// Add the entry to the directory:
$ldap->add($entry);
?>

Renaming or moving entries

Renaming and/or moving an entry is an operation on the DN of an entry. Moving an entry means, to rename a DN in such a way, that the entry becomes a new base-DN. You can rename or move an entry, if you call the dn() method of the entry you want to relocate. Alternatively, you may call Net_LDAP2's move() method that also ca handle only DNs. Remember that you must call the entires update() method to carry out the move/rename. Net_LDAP2's move() will move the entry immediately. If you use an entryobject togehter with Net_LDAP2's move(), you are able to perform cross directory moves.

Moving an entry using Net_LDAP2_Entry

<?php
// Defining the DN we want to fetch;
$dn    = 'cn=admin,o=example,dc=org';
$newdn = 'cn=admin,o=new-example,dc=org';
$entry = $ldap->getEntry($dn);

$entry->dn($newdn);
?>

Moving an entry using Net_LDAP2 and Net_LDAP2_Entry

<?php
// Defining the DN we want to fetch;
$dn    = 'cn=admin,o=example,dc=org';
$newdn = 'cn=admin,o=new-example,dc=org';
$entry = $ldap->getEntry($dn);

$ldap->move($entry, $newdn);
?>

Renaming an entry using Net_LDAP2 and DNs

<?php
// Defining the DN we want to fetch;
$dn    = 'cn=admin,o=example,dc=org';
$newdn = 'cn=admin2,o=new-example,dc=org';

$ldap->move($dn, $newdn);
?>

Performing a cross directory move

<?php
// $ldap_src is the source ldap and $ldap_tgt the target
$dn    = 'cn=admin,o=example,dc=org';
$newdn = 'cn=admin,o=new-example,dc=org';
$entry = $ldap_src->getEntry($dn);

$ldap_src->move($entry, $newdn, $ldap_tgt);
?>

Deleting entries

Deleting entries is performed using Net_LDAP2's delete() method. Just pass the Net_LDAP2_Entry object or the DN of the entry you want to delete. In the case that the DN contains subentrys, you need to pass TRUE as second parameter which will make delete() delete recursive.

A second way exist: You may simply call delete() from the Net_LDAP2_Entry you want to delete. Don't forget that you must call update() to carry out the delete in this case.

Deleting an entry

<?php
$dn  = 'cn=new-admin,o=example,dc=org';
$ldap->delete($dn);
?>

Deleting an entry using a Net_LDAP2_Entry object

<?php
$entry->delete();
$entry->update();
?>

LDAP filters

LDAP filters – Introduction to and usage of LDAP filters

What are LDAP filters?

LDAP filters usually serve as parameter to a LDAP Search request.

LDAP filters are defined in RFC 2254 and can be compared to the WHERE clause in SQL select statements - they filter the data returned from some search request - in this case the entries returned from the directory server. With Net_LDAP2, you may use plain strings as filters, or preferably, the Net_LDAP2_Filter class which mostly releases you of the burden to escape yourself and to remember all the various special characters needed for constructing and combining filters.

Some LDIF filter basics

Although you should preferably use the Net_LDAP2_Filter class to construct your LDAP filters, some theory may be interesting and helpful in understanding how to construct LDAP filters and what they are capable of.

Basic LDAP filters are composed of an "[attribute][operator][value]" pair enclosed by round brackets. There are several comparison operators available: "=" (equal), ">" (greater), "<" (less), ">=" (greater or equal), "<=" (less or equal) and "=~" (phonetical similar). The exact match behavior is defined by the attribute syntax of the attribute to which the filter should apply.

Some basic string filters

<?php
// Filter entries whose first name is 'Benedikt':
$filter = '(givenName=Benedikt)';

// Filter entries which have an employeenumber higher or equal than 1424:
$filter = '(employeeNumber>=1424)';

// Filter entries whose first name sounds similar to "Stephane"
// This should also find "Stephen" and "Stefan" (depending on implementation)
$filter = '(givenName~=Stephane)';
?>

The value part of the basic filter construct could also include a special character: "*". The star acts as placeholder for none, one or several characters at that position. "V*lue" would therefore match against "Value", "Vlue", "VaaAaAalue" and so on. There are some special named combinations using the star, but they work exactly the same way:

Special named placeholder combinations
Name Filter Description
present(attr=*)Also refered to as "any". Finds any entry containing any (unless empty) value for the named attribute.
begins(attr=value*)value starts with some fixed string
ends(attr=*value)value ends with some fixed string
contains(attr=*value*)value contains some fixed string

Combining string filters

Basic filters can be combined using the three logical operators "&" (and), "|" (or) and "!" (not). Note, that the smallest filter component, the basic filter enclosed in round brackets, remains isolated: instead of just adding another "[attribute][operator][value]" pair into the brackets, a new bracket level is introduced that contains all filter components that should be combined. Note also, that the logical operator does stand in front of all filter components, not between them as common in programming languages.

Combining string filters

<?php
// Search all 'Benedikt's with phone number 1234567890
    $filter = '(&(givenName=Benedikt)(telephoneNumber=1234567890))';
    
    // Search the same, but exclude person "Benedikt Foobar"
    // Note that the "not" is a logical operator and thus needs its own
    // surrounding bracket. This explains nicely, that each bracket level
    // is evaluated independently from surrounding brackets.
    $filter = '(&(givenName=Benedikt)(telephoneNumber=1234567890)(!(sureName=Foobar)))';
?>

The Net_LDAP2_Filter class

As you will read below, there are some special characters inside the LDAP filter definition and thus must be escaped. These are mainly the special characters used directly by the filter syntax like braces and the logical operators. Despite those, there are some other cases which need special threatment. Nearly all of this cases are hidden through the Net_LDAP2_Filter class so you should only consider using string filters if you need to or you know what you are doing. If you need a filter string, you may also use Net_LDAP2_Filters asString() function after building the filter.

The filter class has two different usage models: one for constructing basic filters and another to combine them logically. This has to do with the syntax of LDAP filters you may read below.

Creating filters

For creating basic filter components, you need to use the create() factory method. There, you combine three items: an attribute to filter for, a matching rule for comparison and a value that is beeing compared with the servers entries. The given value is automatically escaped, so you need take care if you want to use the star placeholder. In this case, you need to pass FALSE as fourth parameter to create() which causes value to be threaten as-is. This of course also means, that you need to escape the parts of the value that may contain restricted characters yourself using Net_LDAP2_Util::escape_filter_value(). To learn what characters are restricted, refer to RFC 2254 or the documentation of Net_LDAP2_Util::escape_filter_value(); otherwise its safe to always escape.

The matching rules partly follow the basic filter matching rules described above, but are enhanced to make your life easier:

create()s matching rules
Rule Description
equalsOne of attributes values is exactly value. Please note that case sensitiviness depends on the matching rule defined in the attributes schema syntax.
beginsOne of attributes values must begin with value
endsOne of attributes values must end with value
containsOne of attributes values must contain value
present | anyThe attribute can contain any value but must be existent
greaterThe attributes value is greater than value
lessThe attributes value is less than value
greaterOrEqualThe attributes value is greater or equal than value
lessOrEqualThe attributes value is less or equal than value
approxOne of attributes values sounds similar to value. The matching behavior depends on the server implementation.

Since Net_LDAP2 2.0.12 you can also negate the match rules by using the not keyword to easily negate the basic filter expression. Prior to that, you had to use combine() manually.

Creating LDAP filters

<?php
// Filter entries whose first name is 'Benedikt':
$filter = Net_LDAP2_Filter::create('givenName', 'equals', 'Benedikt');

// Filter entries whose first name is NOT 'Benedikt':
// (this was first introduced in 2.0.12)
$filter = Net_LDAP2_Filter::create('givenName', 'not equals', 'Benedikt');
$filter = Net_LDAP2_Filter::create('givenName', '! =', 'Benedikt'); // works too :)

// Filter entries whose first name starts with 'Steph':
$filter = Net_LDAP2_Filter::create('givenName', 'begins', 'Steph');

// Filter entries containing 'Lone*'; matching the star character.
// The automatic escaping of $value will conveniently escape the star for us.
$filter = Net_LDAP2_Filter::create('givenName', 'contains', 'Lone*');

// Filter entries containing 'Foo[something]Bar'; not matching the star character.
// For this to work, we need to disable automatic escaping of $value by passing
// false as fourth parameter. This however implies, that we take care of
// proper escaping, which is showed in the example.
$escaped_values = Net_LDAP2_Util::escape_filter_value(array('Foo', 'Bar'));
$foo =& $escaped_values[0];
$bar =& $escaped_values[1];
$filter = Net_LDAP2_Filter::create('givenName', 'contains', "$foo*$bar", false);

// Filter entries whose first name sounds similar to "Stephane"
// This should also find "Stephen" and "Stefan" (depending on implementation)
$filter = '(givenName=~Stephane)';
?>

Combining filters

Although the filters can be used stand alone, they can be combined to match sophisticated search requiremets. This is done by using the combine() to combine several present Net_LDAP2_Filter objects using a logical operator. The execption is the not operator since it only allows one filter object to be negated.

combine()s operators
Rule Description
andAll filter components must evaluate to true for the combined filter to be true
orAt least one filter component must evaluate to true for the combined filter to be true
notThe result of the filter component is inversed (true becomes false and vice versa). Note that this operator only accepts one filter object.

Combining LDAP filters

<?php
// Create some test filters
$filter_benedikt = Net_LDAP2_Filter::create('givenName', 'equals', 'Benedikt');
$filter_steph    = Net_LDAP2_Filter::create('givenName', 'begins', 'Steph');
$filter_foobar   = Net_LDAP2_Filter::create('sureName', 'equals', 'Foobar');
$filter_height   = Net_LDAP2_Filter::create('personHeight', 'greater', '175');

// Negate 'foobar' filter.
// This filters every entry whose sure name is not 'Foobar'
$filter_not_foobar = Net_LDAP2_Filter::combine('not', $filter_foobar);

// Build a 'and' combination to be able to search for people whose
// first names start with 'Steph' and who are are taller than 175
// except those whose surname is 'Foobar'
$filter_stephs_tall = Net_LDAP2_Filter::combine('and',
    array($filter_steph, $filter_height, $filter_not_foobar));

// In any case, add every person whose first name is
// 'Benedikt' to the search result.
$filter_add_benedikt = Net_LDAP2_Filter::combine('or', array($filter_benedikt, $filter_stephs_tall));
?>

Client side filtering

Since version 2.1.0 the filter class can filter entries client side. Just pass an entry or an array of entries to the filter objects matches() method. With this you can check that an given entry matches some LDAP filter as well as select matching entries out of an array containing many of them. The method always returns the number of matched entries. If you want to retrieve the filtered entries you have to provide some result array to which the matching entries are appended.

Supported features
Feature Description Since
Matching Operators: equals, begins, ends, contains, present|anyMatching of the basic "=" filter argument with wildcards in value. As of 2.1.0, the match performed is not case sensitive (this will change once schema attribute syntax is honored).2.1.0
Matching Operators: greater, greaterOrEqual, less, lessOrEqual, approxComparisons and approximate literal matchingunsupported
Matching rules of attribute syntaxUsing of the matching rule of the schema attribute syntax (eg. date/time comparisons, case sensitiveness, etc)unsupported
Logical Operators: AND, OR, NOTCombining filters for more advanced expressions.2.1.0

Client side filter matching

<?php
// Create a simple test filter (may be arbitary complex and combined)
$filter = Net_LDAP_Filter::create('givenName', 'equals', 'Benedikt');

// see if the filter matches a given Entry
// (note that you should check for Net_LDAP2_Error)
$matches_count = $filter->matches($singleEntry);
// $matches_count contains the number of matched entries, here 0 or 1.

// see, if the filter matches some entries of a given array of entries
$matches_count = $filter->matches($arrayOfEntries);
// $matches_count contains again the number of matched entries, here 0 or n.

// what entries do match exactly?
$filteredEntries = array(); // provide a results array (doesn't have to be empty)
$matches_count = $filter->matches($arrayOfEntries, $filteredEntries);
// $filteredEntries array contains now all matched entries, eg a filtered result.
?>

Advanced features

You may need some advanced functionality if you have to deal with string representation of filters.

Advanced methods
Method Description
parse()Takes an filter string and parses it into a Net_LDAP2_Filter object. It also verifies, that the filter syntax is correct.
printMe()In PERLs interface, this method is called "print" but due to language constraints, we cannot use that name. Prints the string representation of this filter object to standard output or to an optional filehandle passed as parameter.
asString()Returns the string representation of the filter object.

LDIF files

LDIF files – Converting between Net_LDAP2_Entries and LDIF files

Avaible since

LDIF support was added to Net_LDAP2 in release 1.1.0a1.

What are LDIF files?

LDIF files are in detail described at RFC 2849. Shortly, they contain data records in an plain text, human readable format, much like a SQL file does. An LDIF file specifies a set of directory entries, or a set of changes to be applied to directory entries, but not both at the same time: the formats cannot be mixed inside the same file. LDIF-content files are very usable for manual transporting data or for full backups. LDIF-change files are an easy way to perform adjustments to a database like automated data synchonisation. LDIF files can contain comments (first character on line is a hash "#") which are very useful in case humans need to interpret or read the data.

Example LDIF content file

#
# This is a content LDIF file.
# It contains one single entry featuring several
# attributes and one comment (this one).
# attr1, attr4 and cn are single valued, the others are
# multivalued. objectclass is a special case, LDAP servers
# will interpret this operational attribute to define the
# classes the object will belong to and thus, which attributes
# it may contain. the OCL-attribute is usually multivalued.
#
version: 1
dn: cn=test1,ou=example,dc=cno
objectclass: someobjectclass
attr1: 12345
attr2: 1234
attr2: baz
attr3: foo
attr3: bar
attr4: brrrzztt
cn: test1

LDIF files could describe not only the data an entry contains, but also various changes to the entry itself. If such an LDIF file would then be given to a LDAP server, he would interpret those changes instead just importing the data. It is comparable to a diff file on unix and familiar to the SQL dump.

Example LDIF change file

# Delete attr1, replace values of attr2 and add new attribute attr42
# The attribute "changetype" is special: it says, what to do with
# this entries dataset. It could also be "delete" or "add" to delete
# a whole entry or to add a completely fresh one. "modrdn" will
# move the entry to a new location once the LDIF file is imported.
dn: cn=test2,ou=example,dc=cno
changetype: modify
delete: attr1
-
replace: attr2
attr2: 123456_newtest
-
add: attr42
attr42: the answer

Error handling when using Net_LDAP2_LDIF

Before we can start using Net_LDAP2_LDIF we must say some short words about how error handling works. Net_LDAP2_LDIF was designed to have mostly the same API as the original PERL Net::LDAP::LDIF has. Because of this, the methods of Net_LDAP2_LDIF do not return a Net_LDAP2_Error object. You must use the error() method that will return a Net_LDAP2_Error object in case of failure or true in case everything was ok. In LDIF reading mode, you can additionally use error_lines() to get knowledge about where in the input file the error occured.

Construction and options

Regardless if you want to read or write a LDIF file, you always have to use the constructor of Net_LDAP2_LDIF to initialize your access to the LDIF file. You need to pass at least one parameter to Net_LDAP2_LDIF(): the path of the file that should be read or written. You may pass the open mode as second parameter. The possible file open modes are "r" (read), "w" (write, clears the file first) and "a" (append to the end). In case you omit the open mode, read mode is assumed. The third optional parameter is an associative array containing one or several of the following options:

Possible configuration options
Name Description Default
encode Some DN values in LDIF cannot be written verbatim and have to be encoded in some way. Possible values are: "none", "canonical" and "base64" (RFC default) base64
onerror What should be done on errors? "undef" will let error handling in your hands, in this case you use error() and error_lines() to process errors manually. "die" aborts the script printing the error - this is sometimes useful for CLI scripts. "warn" just prints out the error but continues like "undef" would. undef
change Turning this to "1" (true) will tell Net_LDAP2_LDIF to write change sets instead of content files. false
lowercase Set this to true to convert attribute names to lowercase when writing. 0
sort If true, sort attribute names when writing entries according to the rule: objectclass first then all other attributes alphabetically sorted by attribute name 0
version Set the LDIF version to write to the resulting LDIF file. According to RFC 2849 currently the only legal value for this option is 1. 1
wrap Number of columns where output line wrapping shall occur. Setting it to 40 or lower inhibits wrapping. Useful for better human readability of the resulting file. 78
raw Using this option, you are able to tell Net_LDAP2_LDIF which attributes to treat as binary data. If you pass in entries having a valid LDAP connection (eg from some Net_LDAP2->search() operation) this additionally will be detected by automatic checks against the schema. empty

For advanced users: instead of passing a file path, you also may pass an already initialized file handle. In this case, the mode parameter will be ignored. You may use this, if you want to mix LDIF content and LDIF change mode by using two Net_LDAP2_LDIF instances to write to the same filehandle, but it could be very useful in other cases too. To initialize the second instance of Net_LDAP2_LDIF, you can use handle() to get the filehandle from the first instance.

Reading a LDIF file into Net_LDAP2_Entry-objects

One of the two modes how Net_LDAP2_LDIF can be used is to read a LDIF file and parse its contents into an array of Net_LDAP2_Entry objects. This is done using the read_entry()-method which will return the next entry. If you want to fetch all entries, you use the eof() to detect the end of the input file:

Parsing a LDIF file into Net_LDAP2_Entry objects

<?php
// open some LDIF file for reading
$ldif = new Net_LDAP2_LDIF('somefile.ldif', 'r');
if ($ldif->error()) {
    $error_o = $ldif->error(); // get Net_LDAP2_Error object on error
            die('ERROR: '.$error_o->getMessage());
}

// parse the entries of the LDIF file into objects
 do {
    $entry = $ldif->read_entry();
    if ($ldif->error()) {
        // in case of error, print error.
        // here we use the shorthand parameter, so error()
        // returns a string instead of a Net_LDAP2_Object
        die('ERROR AT INPUT LINE '.$ldif->error_lines().': '.$ldif->error(true));
    } else {
        // No error: do something with the entry
        // Here we just print the entries DN
        echo 'sucessfully parsed '.$entry->dn();
    }
} while (!$ldif->eof());

// We should call done() once we are finished
$ldif->done();
?>

Since version 2.1.0 Net_LDAP2_Filter can do client side filtering on entry objects. This may be especially useful when combined with LDIF reading support as it allows the developer to execute select querys on the LDIF content. That enables for example the development of reports on those files without the need for an LDAP server. Please refer to the documentation of LDAP filters.

Writing Net_LDAP2_Entry objects to a LDIF content file

Writing an LDIF file is very easy too. Just pass the entries you want to have written to the write_entry()-method. Beware, that if you have opened the file in "w" write mode this will clear any previous data of that file. Use "a" (append) if you just want add data.

Writing entries

<?php
// Assume we have some valid Net_LDAP2_Entry objects inside $entries
        // $entries = array( ... );

        // open some file for writing
        $ldif = new Net_LDAP2_LDIF('somewritefile.ldif', 'w');
        if ($ldif->error()) die('ERROR: '.$error_o->getMessage());

        // write the data and check for error
        // you could pass one single Net_LDAP2_Entry object or
        // several objects inside an array
        $ldif->write_entry($entries);
        if ($ldif->error()) die('WRITE ERROR: '.$error_o->getMessage());
?>

Writing Net_LDAP2_Entry objects to a LDIF change file

The process of writing changes is exactly the same like writing entry contents. However there are two differences: Firstly you need to pass the "changes" option and secondly, the entries you want to write need changes. Entries not containing changes will silently be ignored since there is nothing to write.

Writing entry changes

<?php
// cast some test data and three entries
        $testattrs = array(
                'attr1' => '1234',
                'attr2' => 'foo',
                'attr3' => array('bar', 'baz')
            );
        $entries = array(
                Net_LDAP2_Entry::createFresh('cn=foo,dc=example,dc=cno',
                    array_merge(array('cn' => 'foo'), $testattrs)),
                Net_LDAP2_Entry::createFresh('cn=bar,dc=example,dc=cno',
                    array_merge(array('cn' => 'bar'), $testattrs)),
                Net_LDAP2_Entry::createFresh('cn=baz,dc=example,dc=cno',
                    array_merge(array('cn' => 'baz'), $testattrs))
            );

        // make some changes to the first and the last entry
        $entries[0]->add(array('someattr' => 'added'));
        $entries[0]->replace(array('attr1' => 'replaced'));
        $entries[2]->delete(array('attr2'));
        $entries[2]->delete(array('attr3' => 'bar'));

        // open some file for writing, but in change mode
        $ldif = new Net_LDAP2_LDIF('somewritefile.ldif', 'w', array('change' => true));
        if ($ldif->error()) die('ERROR: '.$error_o->getMessage());

        // write the data and check for error
        // you could pass one single Net_LDAP2_Entry object or
        // several objects inside an array
        $ldif->write_entry($entries);
        if ($ldif->error()) die('WRITE ERROR: '.$error_o->getMessage());

        // Now, only two entries are contained in the LDIF file,
        // cn=foo,dc=example,dc=cno and cn=baz,dc=example,dc=cno.
        // cn=bar,dc=example,dc=cno had no changes and was skipped.
?>

Resulting LDIF change file

version: 1
dn: cn=foo,dc=example,dc=cno
changetype: modify
add: someattr
someattr: added
-
replace: attr1
attr1: replaced
-

dn: cn=baz,dc=example,dc=cno
changetype: modify
delete: attr2
-
delete: attr3
attr3: bar
-

Fetching LDIF data

Sometimes you are interested in the lines inside the LDIF file. For those cases you can use the current_lines() and next_lines() methods. They work in the current context, which may be confusing: current_lines() will always return the lines that have built up the current Net_LDAP2_Entry object when called current_entry() after read_entry() has been called. next_lines() will always return the lines, that will build up the next entry from the current point of view, meaning "relative to the entry that was just been read". However, you can override this by activating the "force" parameter of next_lines() which allows you to loop over all entries. current_entry() behaves exactly like current_lines().

If you think, that the lines you have read would be better in form of an Net_LDAP2_Entry object, use the parseLines() method to parse those lines into an entry. This is a good way if you need just a few specific entries of a large LDIF file.

Reading LDIF lines

<?php
// open some LDIF file for reading
// (error checking code is ommitted in this example for
//  better readability - in production, test for errors!)
$ldif = new Net_LDAP2_LDIF('somefile.ldif', 'r');

// since nothing has been read until now, this will
// return an empty array
$empty_array = $ldif->current_lines();

// so let's read the first entries data
$first_entry_lines = $ldif->next_lines();

// if we call it again, we will not read ahead to the
// second entry - we again read the first one!
$first_entry_lines_again = $ldif->next_lines();

// If we call current_lines() now, we haven't read ahead
// like we learned from the last statement.
$empty_array_again = $ldif->current_lines();

// If we want to shift, we must use
// the read_entry() method, which will read ahead.
$first_entry = $ldif->read_entry();

// Now, current_lines() returns the lines of the
// first entry and next_lines() the lines of the second:
$first_entry_lines  = $ldif->current_lines();
$second_entry_lines = $ldif->next_lines();

// There is another way to shift the lines which is faster if
// you are just interested in the LDIFs content - you
// need to pass the "force" parameter to next_lines():
$third_entry_lines  = $ldif->next_lines(true);
$fourth_entry_lines = $ldif->next_lines(true);
$fifth_entry_lines  = $ldif->next_lines(true);

// If you want to convert the lines to an Net_LDAP2_Entry,
// you may do so anytime by using parseLines()
$fourth_entry = $ldif->parseLines($fourth_entry_lines);

// Since we shifted manually only the lines,
// current_lines() will return the lines that built up the
// last (e.g. the first entry) Net_LDAP2_Entry object:
$first_entry_lines  = $ldif->current_lines();

// If we decide to read the next entry, we can do that:
$sixth_entry = $ldif->read_entry();

// current_lines() is shifted now:
$sixth_entry_lines = $ldif->current_lines();
?>

Schema caching

Schema caching – How to enable schema caching

Net_LDAP2s schema caching facility

Net_LDAP2 features an easy schema caching facility. Caching the schema can gain some performance, especially with slow servers or connections. The facility works with an plugin object that must be passed to Net_LDAP2s registerSchemaCache() method. The cache object can be registered (or unregistered) at any time, but of course it is the best time right after initializing Net_LDAP2.

Enabling/disabling Net_LDAP2s schema caching facility

<?php
// registering a valid schema cache object is enough to enable the caching facility:
$ldap->registerSchemaCache($myCacheObject);

// unregistering is easy too: just supply null as schema cache object:
$ldap->registerSchemaCache(null);
?>

The object that gets passed to registerSchemaCache() must implement the Net_LDAP2_SchemaCache interface which demands two methods. Initialisation of the cache object is dependent on the class itself but should be handled inside the cache class constructor, however this may vary. Please refer to the cache class documentation for those details.

Net_LDAP2_SchemaCache interface methods
Method Parameter Return value Description
loadSchema() none Net_LDAP2_Schema, Net_LDAP2_Error or false Returns the cached schema object. Net_LDAP2 will consider anything returned invalid, except a valid Net_LDAP2_Schema object. In case you return a Net_LDAP2_Error, this error will be routed to the return of the $ldap->schema() call. If you return something else, Net_LDAP2 will fetch a fresh Schema object from the LDAP server and tries to cache it via store(). You may also want to implement a cache aging mechanism here too.
storeSchema() Net_LDAP2_Schema object true or (in special cases) Net_LDAP2_Error Stores a schema object in the cache. This method will be called, if Net_LDAP2 has fetched a fresh schema object from LDAP and wants to init or refresh the cache. In case of errors you may return a Net_LDAP2_Error which will be routet to the client. Note that doing this prevents, that the schema object fetched from LDAP will be given back to the client, so only return errors if storing of the cache is something crucial (e.g. for doing something else with it). Normaly you dont want to give back errors in which case Net_LDAP2 needs to fetch the schema once per script run and use the error functionality of loadSchema().

Packaged schema cache classes

As of Net_LDAP2 2.0.0, there is one default schema caching class: Net_LDAP2_SimpleFileSchemaCache to make your life a little easier. Caching to files should also be the most commonly used case.

This cache class is built to be flexible yet simple to use and may serve as example to write own caching classes. This cache stores the schema object in a flat file. The path is freely configurable. It also servers a cache aging mechanism that can be used to invalidate the cached schema after some time so it will be refreshed regularly.

To use this cache, you firstly need to initialize and configure a fresh cache object. Then the cache must be registered with the Net_LDAP2 instance. After that, Net_LDAP2 will use the cache.

Initializing the SimpleFileSchemaCache

<?php
$myCacheConfig = array(
        'path'    => '/tmp/Net_LDAP_Schema.cache', // may reside on an linux tmpfs for improved performance
        'max_age' => 1200                          // in seconds, use 0 for endlessly
);
$myCacheObject = new Net_LDAP2_SimpleFileSchemaCache($myCacheConfig);
$ldap->registerSchemaCache($myCacheObject);
?>
Net_LDAP2_SimpleFileSchemaCache config options
Option Mandatory? Default Description
path No /tmp/Net_LDAP_Schema.cache The full path to the cache file. To improve the caches performance under linux, you can place the cache file in a tmpfs mounted directory. This will put the file in the computers memory instead on disk, enabling nearly instant access.
max_age No 1200 Maximum cache age in seconds. The age of the cache is determined by the files last change time. If max_age is reached, Net_LDAP2 will fetch a fresh Net_LDAP2_Schema object which is then stored in the cache file again. Setting this to "0" will make the cache endlessly valid.

Writing own schema cache classes

However this is basicly an easy task, this is beyond the scope of this manual. If you want to write your own custom schema cache, please refer to the detailed example at /your/pear/path/docs/Net_LDAP2/examples/schema_cache.php as well as the source/APIdoc of the interface Net_LDAP2_SchemaCache.