Composing SearchConditions

In this chapter you will learn how to compose your SearchConditions, without using an input processor. Composing Conditions manually is perfect for testing your integration, or using RollerworksSearch as a SDK for (REST) APIs.

Creating a FieldSet

Before you can compose a SearchCondition you first need a FieldSet configuration, even when using the Condition only for exporting you need a FieldSet as the exporter needs to know which data transformer(s) to use.

A FieldSet is build using either the FieldSetBuilder or a FieldSetConfigurator, using a FieldSetConfigurator makes it possible for the FieldSet to be serialized and makes your configuration easily sharable for other processors.

For now we will use the FieldSetBuilder, building a FieldSet configuration for a user registration system.

Say your user registration system has the following columns (with the storage type):

  • user_id: integer
  • username: text
  • password: text
  • first_name: text
  • last_name: text
  • reg_date: datetime

You want to allow searching in all columns except password, because RollerworksSearch is agnostic to your data system you need tell the system which fields there are and then later map these fields to a (table) column.

Note

It may feel redundant to map these fields twice, but this is with a reason.

A FieldSet can be used for any data system or storage, if the FieldSet was aware of your data system it would be only possible for one storage. And switching to ElasticSearch from Doctrine would be more difficult.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
use Rollerworks\Component\Search\Extension\Core\Type\TextType;
use Rollerworks\Component\Search\Extension\Core\Type\IntegerType;
use Rollerworks\Component\Search\Extension\Core\Type\DateTimeType;

$userFieldSet = $searchFactory->createFieldSetBuilder()
    ->add('id', IntegerType::class)
    ->add('username', TextType::class)
    ->add('firstName', TextType::class)
    ->add('lastName', TextType::class)
    ->add('regDate', DateTimeType::class)
    ->getFieldSet('users');

That’s it. The FieldSet is now ready for usage, the getFieldSet method has an optional argument where you can provide the name of the set (eg. users).

Naming a set makes it easier to identify the set’s configuration when passed to a Condition processor. But it is not required for normal usage.

Tip

The getFieldSet method produces a new FieldSet configuration every time, this allows to create multiple FieldSet configurations who share a common structure and configuration.

And now you can use the FieldSet for the SearchConditionBuilder, note that values must be provided in the “model” format of the field-type.

For the DateTimeType this is a DateTime object, for the id this is an integer, and username is a string.

For this example we want to search for users:

Registered between the years 2015 and 2016 (but not within the month May) or have a username which contains the word “admin”.

use Rollerworks\Component\Search\ConditionOptimizer\DuplicateRemover;
use Rollerworks\Component\Search\SearchConditionBuilder;
use Rollerworks\Component\Search\Test\SearchConditionOptimizerTestCase;
use Rollerworks\Component\Search\Value\Compare;
use Rollerworks\Component\Search\Value\ExcludedRange;
use Rollerworks\Component\Search\Value\PatternMatch;
use Rollerworks\Component\Search\Value\Range;
use Rollerworks\Component\Search\Value\ValuesBag;
use Rollerworks\Component\Search\Value\ValuesGroup;

$condition = SearchConditionBuilder::create($fieldSet, ValuesGroup::GROUP_LOGICAL_OR)
    ->field('regDate')
        ->add(new Range(new DateTime('2015-01-01 00:00:00 UTC'), new DateTime('2017-01-01 00:00:00 UTC'), true, false))
        ->add(new ExcludedRange(new DateTime('2015-03-01 00:00:00 UTC'), new DateTime('2015-04-01 00:00:00 UTC'), true, false))
    end()
    ->field('name')
        ->add(new PatternMatch('admin', PatternMatch::PATTERN_CONTAINS))
    ->end()
    ->getSearchCondition();

Lets break this down, field('name') tells the builder we want to work on a SearchField named name. Once we are done we call end and return back to the ValuesGroup were we can add/remove other fields, and add new/remove (sub)groups.

new Range(new DateTime('2015-01-01 00:00:00 UTC'), new DateTime('2017-01-01 00:00:00 UTC'), true, false)

This code segment creates a new Range value value-holder with an inclusive lower bound and exclusive upper bound (everything lower then the value itself).

Note

Calling field('name') multiple times will return the same instance of the ValuesBagBuilder. Use field('name', true) to overwrite the existing instance.

The SearchConditionBuilder is really powerful and developer friendly, but instead of explaining everything, the following full example should give you an idea about whats possible:

use Rollerworks\Component\Search\ConditionOptimizer\DuplicateRemover;
use Rollerworks\Component\Search\SearchConditionBuilder;
use Rollerworks\Component\Search\Test\SearchConditionOptimizerTestCase;
use Rollerworks\Component\Search\Value\Compare;
use Rollerworks\Component\Search\Value\ExcludedRange;
use Rollerworks\Component\Search\Value\PatternMatch;
use Rollerworks\Component\Search\Value\Range;
use Rollerworks\Component\Search\Value\ValuesBag;
use Rollerworks\Component\Search\Value\ValuesGroup;

$condition = SearchConditionBuilder::create($fieldSet, ValuesGroup::GROUP_LOGICAL_OR)
    ->field('regDate')
        ->add(new Range(new DateTime('2015-01-01 00:00:00 UTC'), new DateTime('2017-01-01 00:00:00 UTC'), true, false))
        ->add(new ExcludedRange(new DateTime('2015-03-01 00:00:00 UTC'), new DateTime('2015-04-01 00:00:00 UTC'), true, false))
    end()
    ->field('name')
        ->add(new PatternMatch('admin', PatternMatch::PATTERN_CONTAINS))
    ->end()
    ->group()
        ->field('id')
            ->addSimpleValue(2)
            ->addSimpleValue(5)
            ->add(new Compare(10, '>'))
        ->end()
        ->group(ValuesGroup::GROUP_LOGICAL_OR)
            ->field('first-name')
                ->addSimpleValue('homer')
                ->add(new PatternMatch('spider', PatternMatch::PATTERN_CONTAINS))
                ->add(new PatternMatch('pig', PatternMatch::PATTERN_CONTAINS))
                ->add(new PatternMatch('doctor', PatternMatch::PATTERN_STARTS_WITH, true)) // case-insensitive match
                ->add(new PatternMatch('who', PatternMatch::PATTERN_CONTAINS, true)) // case-insensitive match
            ->end()
        ->end()
    ->end()
    ->getSearchCondition();

You can group as deep as needed, and always return to the previous level using end(). The builder support all of your complex and simple conditions.

See also SearchConditions in action

Further reading