DataProvider for SmartContent

DataProviders are used to load data for SmartContent. It returns data filtered by a configuration array. This array can be configured with an overlay in the backend form.

This configuration array includes following values:

Name Description
dataSource Additional constraint - like page-“folder”.
tags Multiple selection of tags, which a item should have.
tagOperator The item has any or all of the selected tags.
categories Multiple selection of categories, which a item should have.
categoryOperator The item has any or all of the selected categories.
types Multiple selection of types (e.g. templates), which a item should have

Tags (websiteTags) and Categories (websiteCategories) can also be “injected” by GET parameters from the website. This can be handled separately from the admin-selected. Also different operators (websiteTagsOperator and websiteCategoryOperator) are available.

Additional features, which can be provided with a DataProvider:

Name Description
presentAs Value can be used in the website for display options - like one or two column - these values can be freely configured by developers.
page & pageSize Pagination of items.
limit Maximum items for (if pagination is active) over all pages or overall.

How to create a custom DataProvider?

To create a custom data provider you have to create a service which implements the Interface DataProviderInterface. This Interface provides functions to resolve the configured filters for the backend API with standardized objects and for the website with array and entity access. Additionally the DataProvider returns a configuration object to enable or disable features.

There exists an abstraction layer for ORM DataProviders. This layer provides the implementation of basic DataProvider functionality and Database query.

If you want to create a DataProvider for the ExampleEntity you have todo following steps.

1. Repository

The repository has to implement the DataProviderRepositoryInterface and provide the findByFilters function. If the default implementation is good enough, you can include the trait DataProviderRepositoryTrait, which needs the functions createQueryBuilder (is default in repositories) and appendJoins where you are able to configure eager loading for the entity.

The rest of the functionality and Query generation is done in the Trait.

<?php

  use Sulu\Component\SmartContent\Orm\DataProviderRepositoryInterface;
  use Sulu\Component\SmartContent\Orm\DataProviderRepositoryTrait;

/**
 * Repository for the ExampleEntities.
 */
class ExampleRepository extends EntityRepository implements DataProviderRepositoryInterface
{
    use DataProviderRepositoryTrait;

    /**
     * {@inheritdoc}
     */
    public function appendJoins(QueryBuilder $queryBuilder, $alias, $locale)
    {
        $queryBuilder->addSelect('type')->leftJoin($alias . '.type', 'type');
    }
}

Note

Be sure that the returned entities has valid serialization configuration for JMSSerializer.

There are the following hooks to influence the query generation. These are functions which are optional to override in the repository.

Name Description
append(QueryBuilder $queryBuilder, $alias, $locale, $options = []) Additional select, where or joins can be added to the query to match given options. The options can be generated by the data-provider and can contain for example additional filter parameter.
appendTagsRelation(QueryBuilder $queryBuilder, $alias) If your entity is not directly connected to the tags (entity.tags) you can append here all needed joins and return the path to the tag relation.
appendCategoriesRelation(QueryBuilder $queryBuilder, $alias) Same as tags.
appendTypeRelation(QueryBuilder $queryBuilder, $alias) Same as tags.
appendDatasource($datasource, $includeSubFolders, QueryBuilder $queryBuilder, $alias) If your dataprovider can handle datasources you can add the functionality to filter by the datasource here.

2. DataItem

The DataItem will be used in the backend to display the filtered items. This class implements the Interface ItemInterface.

<?php

use Sulu\Component\SmartContent\ItemInterface;

/**
 * Represents example item in example data provider.
 *
 * @ExclusionPolicy("all")
 */
class ExampleDataItem implements ItemInterface
{
    /**
     * @var Example
     */
    private $entity;

    public function __construct(Example $entity)
    {
        $this->entity = $entity;
    }

    /**
     * {@inheritdoc}
     *
     * @VirtualProperty
     */
    public function getId()
    {
        return $this->entity->getId();
    }

    /**
     * {@inheritdoc}
     *
     * @VirtualProperty
     */
    public function getTitle()
    {
        return $this->entity->getTitle();
    }

    /**
     * {@inheritdoc}
     *
     * @VirtualProperty
     */
    public function getImage()
    {
        return $this->entity->getImage();
    }

    /**
     * {@inheritdoc}
     */
    public function getResource()
    {
        return $this->entity;
    }
}

Note

If you return an image within the getImage function it will be displayed in the admin ui. You should be sure that the image is not bigger than 50x50.

3. DataProvider

The DataProvider is mostly abstracted by the SmartContent component. For further optimization, you can disable or enable the form-elements in the configuration to avoid filtering for these values.

<?php

use Sulu\Component\Serializer\ArraySerializerInterface;
use Sulu\Component\SmartContent\Orm\BaseDataProvider;
use Sulu\Component\SmartContent\Orm\DataProviderRepositoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Sulu\Component\SmartContent\ItemInterface;

/**
 * Example DataProvider for SmartContent.
 */
class ExampleDataProvider extends BaseDataProvider
{
    /**
     * @var RequestStack
     */
    private $requestStack;

    public function __construct(DataProviderRepositoryInterface $repository, ArraySerializerInterface $serializer, RequestStack $requestStack)
    {
        parent::__construct($repository, $serializer);

        $this->requestStack = $requestStack;
    }

    public function getConfiguration()
    {
        if (!$this->configuration) {
            $this->configuration = self::createConfigurationBuilder()
                ->enableLimit()
                ->enablePagination()
                ->enablePresentAs()
                ->enableSorting([
                    ['column' => 'firstname', 'title' => 'sort-translation-key-1'],
                    ['column' => 'address.country', 'title' => 'sort-translation-key-2'],
                ])
                ->enableTypes([
                    ['type' => 'example-type-1', 'title' => 'type-translation-key-1'],
                    ['type' => 'example-type-2', 'title' => 'type-translation-key-2'],
                ])
                ->enableView('example.edit_form', ['id' => 'id', 'properties/webspaceKey' => 'webspace'])
                ->getConfiguration();
        }

        return $this->configuration;
    }

    /**
     * Decorates result as data item.
     *
     * @param array $data
     *
     * @return ItemInterface[]
     */
    protected function decorateDataItems(array $data)
    {
        return array_map(
            function ($item) {
                return new ExampleDataItem($item);
            },
            $data
        );
    }

    /**
     * Returns additional options for query creation.
     *
     * @param PropertyParameter[] $propertyParameter
     * @param array $options
     *
     * @return array
     */
    protected function getOptions(array $propertyParameter, array $options = []) {
        $request = $this->requestStack->getCurrentRequest();

        $result = [
            'type' => $request->get('type'),
        ];

        return array_filter($result);
    }
}

Note

The ConfigurationBuilder also has a enableDatasource function, which allows to choose a source for the request. This is very useful in tree structures, because it allows to filter e.g. only for pages below a certain page.

There are multiple enable… calls, which allow you to enable certain features in the administration interface:

Name Description
enableTags(bool $enable = true) Enables the tag filtering functionality.
enableTypes(array $types = []) Enables the type filtering functionality. The selectable types have to be passed into this method.
enableCategories(bool $enable = true) Enables the category filtering functionality.
enableLimit(bool $enable = true) Allows to limit the output items to a specified number.
enablePagination(bool $enable = true) Allows to enable pagination and specify items per page.
enablePresentAs(bool $enable = true) Allows to enable multiple options for the view. These options can be configured in the xml configuration of the SmartContent.
enableDatasource(string $resourceKey, string $listKey, string $adapter) Allows to choose a source for the request. This is useful in tree structures, because it allows to filter e.g. for pages below a certain parent page.
enableAudienceTargeting(bool $enable = true) Enables the filtering through the audience targeting.
enableSorting(array $sorting) Enables sorting functionality. The sorting options have to be passed into this method.
enableView(string $view, array $resultToView) Allows you to define to which View the application should navigate, when clicking on a resulting item. The first parameter describes the view defined in an Admin class and the second parameter is a mapping from a json pointer. The mapping defines how the values of the clicked item should be sent to the View’s path.

4. Service Definition

Define a service with your Repository and DataProvider and add the tag sulu.smart_content.data_provider with an alias to your DataProvider service definition.

<service id="sulu_example.example_repository" class="Sulu\Bundle\ExampleBundle\Entity\ExampleRepository"
         factory-method="getRepository" factory-service="doctrine">
    <argument>%sulu_example.example.entity%</argument>
</service>

<service id="sulu_example.smart_content.data_provider.example" class="Sulu\Bundle\ExampleBundle\SmartContent\ExampleDataProvider">
    <argument type="service" id="sulu_example.example_repository"/>
    <argument type="service" id="sulu_core.array_serializer"/>
    <argument type="service" id="request_stack"/>

    <tag name="sulu.smart_content.data_provider" alias="example"/>
</service>

Afterwards you can use your new DataProvider within a normal SmartContent property.

Note

Mind that the class property should set to a sensible value, but it has no influence in the actual result (see the Factory service documentation of Symfony for more details). So it is very important to set the repository class correct in the doctrine metadata for this to work.