Conditions
Conditions are logical blocks that combine with other conditions inside a targeting rule. A condition implements a
match() method that always returns a boolean.
Shipped Conditions
The bundle registers the following conditions by default (config/pimcore/default.yaml); the type key is what
appears in rule configuration and in the pimcore_personalization.targeting.conditions map:
| Type key | Class |
|---|---|
browser | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Browser |
country | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Country |
geopoint | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\GeoPoint |
hardwareplatform | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\HardwarePlatform |
language | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Language |
operatingsystem | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\OperatingSystem |
referringsite | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\ReferringSite |
searchengine | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\SearchEngine |
target_group | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\TargetGroup |
timeonsite | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\TimeOnSite |
url | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\Url |
visitedpagesbefore | Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\VisitedPagesBefore |
To add a custom condition, implement two parts:
- A PHP class implementing
ConditionInterface. See the shipped conditions for reference implementations. - A Pimcore Studio dynamic type so the condition appears in the targeting rule editor. See Registering in Pimcore Studio below.
Implementing a Condition
The most important method on ConditionInterface is match(), which receives the current VisitorInfo instance
and returns a boolean indicating whether the condition matches.
As an example, build a condition that matches once the current time of day is later than a configured hour. If
configured to 15, the condition starts matching at 15:00 and keeps matching until midnight.
Start with a condition class implementing ConditionInterface. For simplicity, this only checks the current hour,
not the full time:
<?php
// src/Targeting/Condition/TimeOfTheDay.php
namespace App\Targeting\Condition;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\ConditionInterface;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;
class TimeOfTheDay implements ConditionInterface
{
private ?int $hour;
public function __construct(?int $hour = null)
{
$this->hour = $hour;
}
public static function fromConfig(array $config): self
{
$hour = $config['hour'] ?? null;
if (!empty($hour)) {
$hour = (int)$hour;
}
// build an instance from the config as configured
// in the targeting rule
return new self($hour);
}
public function canMatch(): bool
{
// basic validation if the condition is able to match
return null !== $this->hour && $this->hour >= 0 && $this->hour <= 23;
}
public function match(VisitorInfo $visitorInfo): bool
{
$hour = (int)(new \DateTime())->format('H');
return $hour >= $this->hour;
}
}
After implementing your condition, register it with the following configuration. The identifier timeoftheday
is later reused by the Studio dynamic type, so choose a unique name and keep it consistent between the PHP class
and the frontend registration.
pimcore_personalization:
targeting:
conditions:
timeoftheday: App\Targeting\Condition\TimeOfTheDay
Building a Condition Instance
When building an instance of your condition, by default the
ConditionFactory
calls the static fromConfig() method with the data configured on the targeting rule. Avoid injecting services or
custom data into your condition; use the data provider system instead to add data to the VisitorInfo. If you need
more control over how your condition is built, either:
- Override the
ConditionFactoryservice definition (not recommended) and implement your own logic instead of callingfromConfig(). - Handle the
TargetingEvents::BUILD_CONDITIONevent and set an instance of your condition on the event. TheBuildConditionEventcarries everything needed to build a condition instance (type, class name, config data). If you set a condition on the event viasetCondition(), the standard logic is skipped and your condition is used instead. See Events for the full event reference.
Condition Data
If your condition needs outside data, implement
DataProviderDependentInterface
and list the data provider keys that need to be set on the VisitorInfo before matching. The
Data Providers chapter enhances this TimeOfTheDay condition with one. For further
examples, see the
shipped conditions.
If your condition needs to run logic before or after matching (for example to trigger a side effect through the
event dispatcher), implement
EventDispatchingConditionInterface
and its preMatch() / postMatch() methods; the ConditionMatcher calls them around every match.
Condition Variables
Another important part is
variable conditions,
which support the session_with_variables rule matching scope. A condition implementing this interface returns an
array of the variables that led to the match from getMatchedVariables(). Pimcore uses this data to determine
whether the rule already ran with the exact same data.
Implement this interface whenever possible. Use
AbstractVariableCondition
as a starting point; it contains helper methods to collect variable data. Build your data deterministically (for
example, keep the same key order in an array, or the same serialization format), since Pimcore hashes this data to
compare it against previous evaluations.
For example, the country condition sets the ISO country code that led to the match as its data (based on
GeoLocation). If a rule runs in the session_with_variables scope and the country condition is the only condition
on that rule, it won't run twice for the same resolved country.
The TimeOfTheDay condition can easily be enhanced to store variables. The variable to store is the resolved
current hour.
<?php
namespace App\Targeting\Condition;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\AbstractVariableCondition;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;
class TimeOfTheDay extends AbstractVariableCondition
{
// ...
public function match(VisitorInfo $visitorInfo): bool
{
$hour = (int)(new \DateTime())->format('H');
if ($hour >= $this->hour) {
$this->setMatchedVariable('hour', $hour);
return true;
}
return false;
}
}
Registering in Pimcore Studio
Registering the PHP condition makes it usable in targeting rules, but it won't appear in the Pimcore Studio rule editor until you also register a matching Studio dynamic type.
The rule builder has no global condition registry. Its RuleConditions component takes a registry instance as a
prop, so each consuming bundle owns its own. This bundle creates an instance of the SDK class
DynamicTypeRuleConditionRegistry and binds it in the Studio DI container under the service ID
PersonalizationBundle/ConditionRegistry. The Conditions tab renders only the types held by that instance, so
your dynamic type has to be registered on it. Registering against any other instance leaves it invisible here.
To add a Studio dynamic type for timeoftheday, build a small Studio UI plugin bundle that:
- implements a dynamic type extending
DynamicTypeRuleConditionAbstractfrom@pimcore/studio-ui-bundle/modules/rule-builder, decorated with@injectable(), usingtimeofthedayas its type key so it matches the PHP registration, - binds that class in the container in the plugin's
onInit, - resolves
PersonalizationBundle/ConditionRegistryand callsregisterDynamicType()on it in the plugin'sonStartup.
import { container } from '@pimcore/studio-ui-bundle/app'
import { type DynamicTypeRuleConditionRegistry } from '@pimcore/studio-ui-bundle/modules/rule-builder'
export const MyPlugin: IAbstractPlugin = {
name: 'my-targeting-plugin',
onInit: ({ container }) => {
container.bind('MyBundle/DynamicTypes/Condition/TimeOfTheDay')
.to(DynamicTypeTargetingConditionTimeOfTheDay).inSingletonScope()
},
onStartup: () => {
const registry = container.get<DynamicTypeRuleConditionRegistry>('PersonalizationBundle/ConditionRegistry')
registry.registerDynamicType(container.get('MyBundle/DynamicTypes/Condition/TimeOfTheDay'))
}
}
Resolve the registry in onStartup, not in your plugin's onInit. The plugin system runs every plugin's onInit
before any onStartup, so this bundle's binding may not exist yet while your onInit is running.
See this bundle's own condition dynamic types for concrete examples (for instance the URL condition), and the Studio UI Bundle's Dynamic Types and Getting Started with Your First Plugin guides for the general plugin and dynamic type mechanism.