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.