LCMS Using the conditions framework

From Dokeos

Jump to: navigation, search

LCMS_Developer_Cookbook Author: Tim De Pauw

Introduction

Since Dokeos provides persistence layer abstraction, it comes with a way to pass complex conditions to data manager methods as objects. This recipe aims to be an introduction to those conditions.

Let’s take a look under the directory repository/lib/condition. You should see a number of classes, each representing a type of condition. Specifically, the following classes are of interest:

  • EqualityCondition
  • InequalityCondition
  • PatternMatchCondition
  • NotCondition
  • AndCondition
  • OrCondition

As you may expect, these all inherit from the Condition interface. The first three compare an identifier to a value. The last three aggregate other conditions, so you may chain them together.

Examples

First, let’s take a really simple example. Say we want to pass a condition to select learning objects by owner. We already know the ID of the owner’s account; it’s $owner. We will also use the class constant LearningObject :: PROPERTY_OWNER_ID to make sure we use the correct property.

$owner_cond = new EqualityCondition(LearningObject :: PROPERTY_OWNER_ID, $owner);

And that’s all there is to it. The condition may now be passed along, and the data manager class will automatically know how to evaluate it.

Now, on to negating conditions. If we were looking for learning objects that do not belong to the owner we mentioned above, we would simply put:

$inverse_owner_cond = new NotCondition($owner_cond);

As you can see, the NotCondition constructor simply takes another condition and negates it.

But what if we wanted to find all learning objects with an ID below 1000? No problem; we have InequalityCondition at our disposal.

$id_cond = new InequalityCondition(LearningObject :: PROPERTY_ID,InequalityCondition :: LESS_THAN_OR_EQUAL, 1000);

As you can see, the InequalityCondition constructor takes a parameter dealing with the type of comparison that is being performed. There are four class constants to choose from: LESS_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN and GREATER_THAN_OR_EQUAL.

Obviously, you can pass an InequalityCondition to a NotCondition just as easily. In fact, the statement below is equivalent to the one above, but the one above will probably be faster.

$id_cond = new NotCondition(new InequalityCondition(LearningObject :: PROPERTY_ID,InequalityCondition :: GREATER_THAN, 1000));

Now, of course, we’ll want to search the database for random strings at some point. For example, say we wanted to look for learning objects that have the word ‘math’ in their title. For this, we use PatternMatchCondition. Similarly to EqualityCondition, PatternMatchCondition’s constructor takes a name and a value, but this time, the value is a pattern. This pattern allows two kinds of wildcards:

  • *: An asterisk corresponds to any character sequence, including an empty string.
  • ?: A question mark corresponds to a single character.

So, back to our ‘math’ condition. Since we want to look for the word ‘math,’ surrounded by any string, we simply have to prepend and append an asterisk. Thus, the condition becomes:

$math_cond = new PatternMatchCondition(
LearningObject :: PROPERTY_TITLE, '*math*');

Of course, conditions wouldn’t be any good if we couldn’t group them together. We have already taken a brief look at aggregating conditions with NotCondition, but the real magic doesn’t happens until you lay hands upon AndCondition and OrCondition. Since they are pretty similar, we can cover them both at once. AndCondition and OrCondition’s constructors both take either a list or an array of Condition instances—which may in their turn be an AndCondition or an OrCondition. Thus, if you wanted to find all learning objects that belong to $owner and have the word ‘math’ in their title, you would use:

$conditions = array($owner_cond, $math_cond);
$condition = new AndCondition($conditions);

or even simply

$condition = new AndCondition($owner_cond, $math_cond);

Similarly, if either condition would suffice, you would use an OrCondition:

$condition = new OrCondition($owner_cond, $math_cond);

Now, like we said, you can keep aggregating conditions together to create complex nested structures. So, let’s get down to business. Here’s a mean condition for you: we need all learning objects that belong to user 7, 12 or 19, having the word ‘exercise’ in their title. If they are of the ‘link’ type, their URL should not contain the string ‘.com/’ and not start with ‘ftp://.’ Otherwise, they should have an ID greater than 100.

We will start by creating a condition for the user ID. We could just create three individual conditions and pass them to OrCondition’s constructor, but instead, we will use the variant that takes an array. This avoids code duplication somewhat.

$owner_ids = array(7, 12, 19);
$owner_cond = array();
foreach ($owner_ids as $id)
{
 $owner_cond[] = new EqualityCondition(LearningObject :: PROPERTY_OWNER_ID, $id);
}
$owner_cond = new OrCondition($owner_cond);

Next up is a condition to check for the word ‘exercise’ in the title.

$title_cond = new PatternMatchCondition(LearningObject :: PROPERTY_TITLE, '*exercise*');

Now, let’s create a condition to single out ‘link’ objects.

$link_cond = new EqualityCondition(LearningObject :: PROPERTY_TITLE, 'link');

To disallow ‘.com’ URLs, we will use a PatternMatchCondition with asterisks again. However, we only use one asterisk to exclude FTP URLs, since the ‘ftp://’ is right at the start of the URL. Next, we put both conditions in an OrCondition. Note that, alternatively, thanks to boolean logic, we could use an AndCondition of NotConditions.

$url_patterns = array('*.com/*', 'ftp://*');
$url_cond = array();
foreach ($url_patterns as $pattern)
{ 
 $url_cond[] = new PatternMatchCondition(Link :: PROPERTY_URL, $pattern);
}
$url_cond = new OrCondition($url_cond);
$url_cond = new NotCondition($url_cond);

For non-‘link’ objects, we want the ID to be greater than 100, so let’s make another condition. Obviously, this is just another InequalityCondition.

$id_cond = new InequalityCondition(LearningObject :: PROPERTY_ID, InequalityCondition :: GREATER_THAN, 100);

Now, we have got our individual conditions, but we need to aggregate them. Notice that we have three main conditions: the owner condition, the title condition, and a complex one that adds an extra condition according to the object type. These three conditions will form an AndCondition, since all three of them are required. However, we cannot create that condition yet, as we will need to build the aforementioned type-dependent condition first.

As far as the type-dependent condition goes, it is really not as hard as it looks. What we are really dealing with is a restriction on both the ‘link’ type—the disallowed URL thing—and any other object type—the ID thing. For convenience, we will first create a condition to single out non-‘link’ objects, so we can write a more readable condition in a moment.

$non_link_cond = new NotCondition($link_cond);

Objects of the ‘link’ type’s URL may not include certain text, but we have already created $url_cond for that particular purpose. Hence, we can extend $link_cond as follows:

$link_cond = new AndCondition($link_cond, $url_cond);

Almost there. We still need to apply $id_cond to non-‘link’ objects. Much like what we just did, we will just use another AndCondition.

$non_link_cond = new AndCondition($non_link_cond, $id_cond);

Now that we have both components of our complex condition, we still need to aggregate those.

$type_cond = new OrCondition($link_cond, $non_link_cond);

And finally, let’s create our main condition.

$condition = new AndCondition($owner_cond, $title_cond, $type_cond);

And voilà, there’s our condition. That wasn’t so hard, was it? And we can see what a good job we did by invoking PHP’s print_r function, which dumps the entire variable structure.

print_r($condition);
Personal tools