Skip to main content
Version: 2024.4

Data Providers

A data provider is a service implementing the DataProviderInterface. Components (e.g. conditions) which implement the DataProviderDependentInterface can define a set of data providers they depend on, triggering the data provider to load its data before the component is used.

A data provider does not directly return its value, but is expected to set it on the VisitorInfo instance instead. As best practice, the core data providers expose their storage key as constant. This constant is used to store and retrieve the data from the VisitorInfo storage. As example: the GeoIP data provider defines the GeoIP::PROVIDER_KEY constant which is used when storing and retrieving the data.

Implementing a Data Provider

A data provider is simply a class implementing the DataProviderInterface which is registered as service. Basically a data provider can do anything, however the core data providers do the following:

  • They store their information on a storage key which is exposed as constant
  • They always set their content key. If no data can be resolved (e.g. GeoIP is unable to resolve a location), null is set.
  • Before loading data, core providers check if there already is an entry for the own storage key and abort loading if that is the case.

As an example let's assume the DateTime used in the TimeOfTheDay condition (as implemented on the Conditions page) is more complex than a simple new DateTime(), i.e. because the date is fetched from a third party or involves calculation logic. Instead of creating it inside the condition which does not have access to services we move it to a reusable DateTime data provider which 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 service:

services:
_defaults:
autowire: true
autoconfigure: true
public: false

App\Targeting\DataProvider\DateTime: ~

And register the provider to the targeting engine with its provider key:

pimcore_personalization:
targeting:
data_providers:
datetime: App\Targeting\DataProvider\DateTime

Consuming a Data Provider

To consume a data provider, implement the DataProviderDependentInterface in your components and specify a list of data providers to use. As an example, let's update the TimeOfTheDay condition to fetch the current DateTime from our 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;
}
}

As you can see, instead of creating a new DateTime instance, the condition now expects an instance on the DateTime::PROVIDER_KEY storage on the VisitorInfo. The targeting engine takes care of loading every provider the condition depends on before starting to match.

The DataProviderDependentInterface can not only be used from conditions, but also from action handlers and other data providers (a data provider can depend on another data providers' data).