Persistence 
This bundle provides a persistence feature, which is used to save data between requests. For example, it can be used to persist applied filters or pagination, per user.
Toggling the feature 
Persistence can be toggled per feature with its own configuration:
Persistence adapters 
Adapters are classes that allow writing (to) and reading (from) the persistent data source.
Built-in cache adapter 
The bundle has a built-in cache adapter, which uses the Symfony Cache component.
It is registered as an abstract service in the service container:
$ bin/console debug:container kreyu_data_table.persistence.adapter.cacheThe adapters are then created based on the abstract definition:
$ bin/console debug:container kreyu_data_table.pagination.persistence.adapter.cache
$ bin/console debug:container kreyu_data_table.sorting.persistence.adapter.cache
$ bin/console debug:container kreyu_data_table.filtration.persistence.adapter.cache
$ bin/console debug:container kreyu_data_table.personalization.persistence.adapter.cacheThe bundle adds a kreyu_data_table.persistence.cache.default cache pool, which uses the cache.adapter.filesystem adapter, with tags enabled.
It is recommended to use tag-aware cache adapter!
The built-in cache persistence clearer requires tag-aware cache to clear persistence data.
Creating custom adapters 
To create a custom adapter, create a class that implements PersistenceAdapterInterface.
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceAdapterInterface;
class DatabasePersistenceAdapter implements PersistenceAdapterInterface
{
    public function __construct(
        private EntityManagerInterface $entityManager,
        private string $prefix,
    ) {
    }
    
    public function read(DataTableInterface $dataTable, PersistenceSubjectInterface $subject): mixed
    {
        // ...
    }
    public function write(DataTableInterface $dataTable, PersistenceSubjectInterface $subject, mixed $data): void
    {
        // ...
    }
}Recommended namespace for the column type classes is App\DataTable\Persistence\.
The recommended way of creating those classes is accepting a prefix argument in the constructor. This prefix will be different for each feature, for example, personalization persistence will use personalization prefix.
Now, register it in the container as an abstract service:
services:
  app.data_table.persistence.database:
    class: App\DataTable\Persistence\DatabasePersistenceAdapter
    abstract: true
    arguments:
      - '@doctrine.orm.entity_manager'use App\DataTable\Persistence\DatabasePersistenceAdapter;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator) {
    $configurator->services()
        ->set('app.data_table.persistence.database', DatabasePersistenceAdapter::class)
            ->args([service('doctrine.orm.entity_manager')])
            ->abstract()
    ;Now, create as many adapters as you need, based on the abstract definition. For example, let's create an adapter for personalization feature, using the personalization prefix:
services:
  app.data_table.personalization.persistence.database:
    parent: app.data_table.persistence.database
    arguments:
      $prefix: personalization
    tags:
      - { name: kreyu_data_table.proxy_query.factory }use App\DataTable\Persistence\DatabasePersistenceAdapter;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator) {
    $configurator->services()
        ->set('app.data_table.personalization.persistence.database')
            ->parent('app.data_table.persistence.database')
            ->arg('$prefix', 'personalization')
    ;The data tables can now be configured to use the new persistence adapter for the personalization feature:
kreyu_data_table:
  defaults:
    personalization:
      persistence_adapter: app.data_table.personalization.persistence.databaseuse Symfony\Config\KreyuDataTableConfig;
return static function (KreyuDataTableConfig $config) {
    $config->defaults()
        ->personalization()
            ->persistenceAdapter('app.data_table.personalization.persistence.database')
    ;
};use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceAdapterInterface;
use Kreyu\Bundle\DataTableBundle\Type\AbstractDataTableType;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductDataTableType extends AbstractDataTableType
{
    public function __construct(
        #[Autowire(service: 'app.data_table.personalization.persistence.database')]
        private PersistenceAdapterInterface $persistenceAdapter,
    ) {
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'personalization_persistence_adapter' => $this->persistenceAdapter,
        ]);
    }
}use App\DataTable\Type\ProductDataTableType;
use Kreyu\Bundle\DataTableBundle\DataTableFactoryAwareTrait;
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceAdapterInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class ProductController extends AbstractController
{
    use DataTableFactoryAwareTrait;
    
    public function __construct(
        #[Autowire(service: 'app.data_table.personalization.persistence.database')]
        private PersistenceAdapterInterface $persistenceAdapter,
    ) {
    }
    
    public function index()
    {
        $dataTable = $this->createDataTable(
            type: ProductDataTableType::class, 
            query: $query,
            options: [
                'personalization_persistence_adapter' => $this->persistenceAdapter,
            ],
        );
    }
}Persistence subjects 
Persistence subject can be any object that implements PersistenceSubjectInterface.
The value returned in the getDataTablePersistenceIdentifier() is used in persistence adapters to associate persistent data with the subject.
Subject providers 
Persistence subject providers are classes that allow retrieving the persistence subjects.
 Those classes contain provide method, that should return the subject, or throw an PersistenceSubjectNotFoundException.
Built-in token storage subject provider 
The bundle has a built-in token storage subject provider, which uses the Symfony Security component to retrieve currently logged-in user. This provider uses the UserInterface getUserIdentifier() method to retrieve the persistence identifier.
The persistence identifier must be unique per user!
Otherwise, multiple users will override each other's data, like applied filters or current page.
You can manually provide the persistence identifier by implementing the PersistenceSubjectInterface interface on your User entity used by the security:
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectInterface;
class User implements PersistenceSubjectInterface
{
    private Uuid $uuid;
    
    public function getDataTablePersistenceIdentifier(): string
    {
        return (string) $this->uuid;
    }
}Persistence "cache tag contains reserved characters" error?
If your User entity returns email address in getUserIdentifier() method, this creates a conflict when using the cache adapter, because the @ character cannot be used as a cache key.
For more information, see troubleshooting section.
Creating custom subject providers 
To create a custom subject provider, create a class that implements PersistenceSubjectProviderInterface:
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectProviderInterface;
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectInterface;
class CustomPersistenceSubjectProvider implements PersistenceSubjectProviderInterface
{
    public function provide(): PersistenceSubjectInterface
    {
        // ...
    }
}Subject providers must be registered as services and tagged with the kreyu_data_table.persistence.subject_provider tag. If you're using the default services.yaml configuration, this is already done for you, thanks to autoconfiguration.
When using the default container configuration, that provider should be ready to use.
 If not, consider tagging this class as kreyu_data_table.persistence.subject_provider:
services:
  app.data_table.persistence.subject_provider.custom:
    class: App\DataTable\Persistence\CustomPersistenceSubjectProvider
    tags:
      - { name: kreyu_data_table.persistence.subject_provider }use App\DataTable\Persistence\CustomPersistenceSubjectProvider;
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
return static function (ContainerConfigurator $configurator) {
    $configurator->services()
        ->set('app.data_table.persistence.database', CustomPersistenceSubjectProvider::class)
            ->tag('kreyu_data_table.persistence.subject_provider')
    ;
}The data tables can now be configured to use the new persistence subject provider for any feature. For example, for personalization feature:
kreyu_data_table:
  defaults:
    personalization:
      persistence_subject_provider: app.data_table.persistence.subject_provider.customuse Symfony\Config\KreyuDataTableConfig;
return static function (KreyuDataTableConfig $config) {
    $config->defaults()
        ->personalization()
            ->persistenceSubjectProvider('app.data_table.persistence.subject_provider.custom')
    ;
};use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceSubjectProviderInterface;
use Kreyu\Bundle\DataTableBundle\Type\AbstractDataTableType;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
use Symfony\Component\OptionsResolver\OptionsResolver;
class ProductDataTableType extends AbstractDataTableType
{
    public function __construct(
        #[Autowire(service: 'app.data_table.persistence.subject_provider.custom')]
        private PersistenceSubjectProviderInterface $persistenceSubjectProvider,
    ) {
    }
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'personalization_persistence_subject_provider' => $this->persistenceSubjectProvider,
        ]);
    }
}use App\DataTable\Type\ProductDataTableType;
use Kreyu\Bundle\DataTableBundle\DataTableFactoryAwareTrait;
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceAdapterInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\DependencyInjection\Attribute\Autowire;
class ProductController extends AbstractController
{
    use DataTableFactoryAwareTrait;
    
    public function __construct(
        #[Autowire(service: 'app.data_table.personalization.persistence.database')]
        private PersistenceAdapterInterface $persistenceAdapter,
    ) {
    }
    
    public function index()
    {
        $dataTable = $this->createDataTable(
            type: ProductDataTableType::class, 
            query: $query,
            options: [
                'personalization_persistence_adapter' => $this->persistenceAdapter,
            ],
        );
    }
}Persistence clearers 
Persistence data can be cleared using persistence clearers, which are classes that implement PersistenceClearerInterface. Those classes contain a clear() method, which accepts a persistence subject as an argument.
Because the bundle has a built-in cache adapter, it also provides a cache persistence clearer:
$ bin/console debug:container kreyu_data_table.persistence.clearer.cacheLet's assume, that the user has a "Clear data table persistence" button, somewhere on the "settings" page. Handling this button in controller is very straightforward:
use App\Entity\User;
use Kreyu\Bundle\DataTableBundle\Persistence\PersistenceClearerInterface;
use Symfony\Component\Routing\Annotation\Route;
class UserController
{
    #[Route('/users/{id}/clear-persistence')]
    public function clearPersistence(User $user, PersistenceClearerInterface $persistenceClearer)
    {
        $persistenceClearer->clear($user);
        
        // Flash with success, redirect, etc...
    }
}