Data Providers
A data provider is a service implementing
DataProviderInterface.
Components (for example conditions) that implement
DataProviderDependentInterface
declare the data providers they depend on, which triggers those providers to load their data before the component
runs.
A data provider doesn't return its value directly; it sets it on the VisitorInfo instance instead. As a best
practice, the shipped data providers expose their storage key as a constant, used to store and retrieve the data
from the VisitorInfo. For example, the
GeoIp data
provider defines the GeoIp::PROVIDER_KEY constant used when storing and retrieving its data.
Shipped Data Providers
The bundle registers the following data providers by default (config/pimcore/default.yaml):
| Provider key | Class |
|---|---|
device | Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\Device |
geoip | Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoIp |
geolocation | Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\GeoLocation |
targeting_storage | Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\TargetingStorage |
visited_pages_counter | Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\VisitedPagesCounter |
device parses the user-agent string with the DeviceDetector library and backs the Hardware Platform and
Operating System conditions. geoip and geolocation back the Country and Geo Point conditions.
Implementing a Data Provider
A data provider is a class implementing DataProviderInterface, registered as a service. A data provider can do
anything, but the shipped data providers follow this pattern:
- They store their information under a storage key exposed as a constant.
- They always set their content key. If they can't resolve any data (for example, GeoIP can't resolve a location),
they set
null. - Before loading data, they check whether an entry already exists for their own storage key and abort if it does.
As an example, assume the DateTime used in the TimeOfTheDay condition (as implemented in the
Conditions chapter) is more complex than a simple new DateTime(), for example because the
date comes from a third party or involves calculation logic. Instead of creating it inside the condition, which
doesn't have access to services, move it to a reusable DateTime data provider that stores the current DateTime
on the VisitorInfo.
<?php
// src/Targeting/DataProvider/DateTime.php
namespace App\Targeting\DataProvider;
use Pimcore\Bundle\PersonalizationBundle\Targeting\DataProvider\DataProviderInterface;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;
class DateTime implements DataProviderInterface
{
const PROVIDER_KEY = 'datetime';
public function load(VisitorInfo $visitorInfo): void
{
if ($visitorInfo->has(self::PROVIDER_KEY)) {
// abort if there already is data for this provider
return;
}
// assume creating the date is more complex (e.g. involves other services
// which are injected via DI)
$visitorInfo->set(self::PROVIDER_KEY, new \DateTimeImmutable());
}
}
Next, register your new data provider as a service:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
App\Targeting\DataProvider\DateTime: ~
Register the provider with the targeting engine under its provider key:
pimcore_personalization:
targeting:
data_providers:
datetime: App\Targeting\DataProvider\DateTime
Consuming a Data Provider
To consume a data provider, implement DataProviderDependentInterface in your component and list the data
providers it needs. As an example, update the TimeOfTheDay condition to fetch the current DateTime from the new
provider:
<?php
// src/Targeting/Condition/TimeOfTheDay.php
namespace App\Targeting\Condition;
use App\Targeting\DataProvider\DateTime;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Condition\AbstractVariableCondition;
use Pimcore\Bundle\PersonalizationBundle\Targeting\DataProviderDependentInterface;
use Pimcore\Bundle\PersonalizationBundle\Targeting\Model\VisitorInfo;
class TimeOfTheDay extends AbstractVariableCondition implements DataProviderDependentInterface
{
// ...
public function getDataProviderKeys(): array
{
return [DateTime::PROVIDER_KEY];
}
public function match(VisitorInfo $visitorInfo): bool
{
$dateTime = $visitorInfo->get(DateTime::PROVIDER_KEY);
if (!$dateTime) {
// provider did not provide a valid date - nothing to match against
return false;
}
$hour = (int)$dateTime->format('H');
if ($hour >= $this->hour) {
$this->setMatchedVariable('hour', $hour);
return true;
}
return false;
}
}
Instead of creating a new DateTime instance, the condition now expects one on the DateTime::PROVIDER_KEY storage
of the VisitorInfo. The targeting engine takes care of loading every provider a component depends on before
matching starts.
DataProviderDependentInterface isn't limited to conditions: action handlers and other data providers can use it
too, so a data provider can depend on another data provider's data.