How to Use Value Comparators¶
Caution
This page is not updated for Rollerworks v2.0 yet.
A powerful feature of RollerworksSearch is the ability to optimize search conditions and perform basic validation of user input.
But in order to do this the system needs to understand which values are equal or lower/higher to other values. Especially when you are working with objects.
Note
Fields with range support enabled must have a Value Comparator in order to work properly. A missing Comparator will mark every range in the field invalid!
Assuming you have a field that handles invoice numbers as an InvoiceNumber and you configured that the field supports ranges.
InvoiceNumber value class¶
First create the InvoiceNumber class that holds an invoice number.
This technique is known as a ‘value class’, the InvoiceNumber is immutable meaning its internal values can’t be changed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | // src/Acme/Invoice/InvoiceNumber.php
namespace Acme\Invoice;
final class InvoiceNumber
{
private $year;
private $number;
private function __construct(int $year, int $number)
{
$this->year = $year;
$this->number = $number;
}
public static function createFromString(string $input): self
{
if (!preg_match('/^(?P<year>\d{4})-(?P<number>\d+)$/s', $input, $matches)) {
throw new \InvalidArgumentException('This not a valid invoice number.');
}
return new InvoiceNumber((int) $matches['year'], (int) ltrim($matches['number'], '0'));
}
public function equals(InvoiceNumber $input): bool
{
return $input == $this;
}
public function isHigher(InvoiceNumber $input): bool
{
if ($this->year > $input->year) {
return true;
}
if ($input->year === $this->year && $this->number > $input->number) {
return true;
}
return false;
}
public function isLower(InvoiceNumber $input): bool
{
if ($this->year < $input->year) {
return true;
}
if ($input->year === $this->year && $this->number < $input->number) {
return true;
}
return false;
}
public function __toString(): string
{
// Return the invoice number with leading zero
return sprintf('%d-%04d', $this->year, $this->number);
}
}
|
Tip
See How to Use Data Transformers on how to transform a user input to
an InvoiceNumber
.
Creating the Comparator¶
Create an InvoiceNumberComparator
class - this class will be responsible
for comparing values for equality and lower/higher InvoiceNumber
objects:
// src/Acme/Invoice/Search/ValueComparator/InvoiceNumberComparator.php
namespace Acme\Invoice\Search\ValueComparator;
use Acme\Invoice\InvoiceNumber;
use Rollerworks\Component\Search\ValueComparator;
final class InvoiceNumberComparator implements ValueComparator
{
public function isHigher($higher, $lower, array $options): bool
{
return $higher->isHigher($lower);
}
public function isLower($lower, $higher, array $options): bool
{
return $lower->isLower($higher);
}
public function isEqual($value, $nextValue, array $options): bool
{
return $value->equals($nextValue);
}
}
Tip
A comparison method will only receive values that are returned by the field’s data transformer.
You don’t have to check if the input is what you expect, but if the input is invalid you need look at the configured data transformers.
Note
When isLower()
and isHigher()
are not supported, then both
methods should be return false
.
Using the Comparator¶
Now that you have the Comparator built, you need to add it to your invoice field type:
// src/Acme/Invoice/Search/Type/InvoiceNumberType.php
namespace Acme\Invoice\Search\Type;
use Acme\Invoice\Search\DataTransformer\InvoiceNumberTransformer;
use Rollerworks\Component\Search\AbstractFieldType;
use Rollerworks\Component\Search\Exception\InvalidConfigurationException;
use Rollerworks\Component\Search\FieldConfigInterface;
use Rollerworks\Component\Search\Value\{Compare, Range};
final class InvoiceNumberType extends AbstractFieldType
{
private $valueComparator;
public function __construct()
{
$this->valueComparator = new InvoiceNumberComparator();
}
public function buildType(FieldConfigInterface $config, array $options)
{
$config->setValueComparator($this->valueComparator);
$config->setValueTypeSupport(Compare::class, true);
$config->setValueTypeSupport(Range::class, true);
$config->addViewTransformer(new InvoiceNumberTransformer());
}
}
Cool, you’re done! Input processors can now validate the bounds of ranges and optimizers can optimize the generated search condition.
Optimizing incremented values¶
Now that your type supports comparing values, you can extend the Comparator with the ability to calculate increments.
Calculating increments helps with optimizing single incremented values.
For example: 1, 2, 3, 4, 5
can be converted to a 1 ~ 5
range which will
simplify the search condition and speed-up the search operation.
Note
Optimizing incremented values is done by the
:class:Rollerworks\\Component\\Search\\ConditionOptimizer\\ValuesToRange
optimizer. So make sure its enabled.
Instead of implementing the ValueComparator
interface, you implement the
ValueIncrementer
interface (which extends the ValueComparator
interface)
and add the getIncrementedValue
method for calculating increments:
// src/Acme/Invoice/Search/ValueComparison/InvoiceNumberComparison.php
namespace Acme\Invoice\Search\ValueComparison;
use Acme\Invoice\InvoiceNumber;
use Rollerworks\Component\Search\ValueIncrementerInterface;
class InvoiceNumberComparison implements ValueIncrementerInterface
{
public function isHigher($higher, $lower, array $options): bool
{
return $higher->isHigher($lower);
}
public function isLower($lower, $higher, array $options): bool
{
return $lower->isLower($higher);
}
public function isEqual($value, $nextValue, array $options): bool
{
return $value->equals($nextValue);
}
public function getIncrementedValue($value, array $options, int $increments = 1)
{
return new InvoiceNumber($value->getYear(), $value->getNumber() + $increments);
}
}
Note
Technically it’s possible to optimize 2015-099, 2015-100, 2015-000"
to 2015-099 ~ 2015-0001
, but only when we know if “2015-100” is the last
invoice for the year 2015.
Cool, you’re done! The new InvoiceNumberComparison
can be set as your
field’s ValueComparator.