Versioning entities

Overview

In this guide you'll learn how to version your entities. The entity versioning system in Shopware gives you the opportunity to create multiple versions of an entity, which could be used to save drafts for example.

Prerequisites

In order to add your own versioned entities for your plugin, you first need a plugin as base. Therefore, you can refer to the Plugin Base Guide.

Furthermore you should have a look at our Adding custom complex data guide, since this guide is built upon it.

Adjust migration

First of all, we have to add a new column to our table: version_id which in union with the id field replaces the primary key.

So your SQL command could look like this:

ALTER TABLE `swag_example`
ADD `version_id` BINARY(16) NOT NULL AFTER `id`,
ADD PRIMARY KEY `id_version_id` (`id`, `version_id`),
DROP INDEX `PRIMARY`;

Adjust definition

After we've added the new field to our table, we also have to add it to our definition. For this we use a Shopware\Core\Framework\DataAbstractionLayer\Field\VersionField which is always required, if we want to version our entity.

<plugin root>/src/Core/Content/Example/ExampleDefinition.php
protected function defineFields(): FieldCollection
{
return new FieldCollection([
new VersionField(),
...
]);
}

Create and merge version

In this section we will create a new version of our entity which will create a new entry in the database with our updated values. When we merge a particular version, all versions before the merged version are deleted. In the example below, we are using a service where we injected a swag_example.repository.

<plugin root>/src/
public function exampleVersioning(Context $context): void
{
$exampleId = Uuid::randomHex();
$this->exampleRepository->create([[
'id' => $exampleId,
'name' => 'Example',
'description' => 'This is an example',
'active' => true,
]], $context);
// Create new version of our entity
$versionId = $this->exampleRepository->createVersion($exampleId, $context);
// Update the context with our version
$versionContext = $context->createWithVersionId($versionId);
// Update our new entity version
$this->exampleRepository->update([
[
'id' => $exampleId,
'description' => 'This is our new description',
],
], $versionContext);
// Our first entity will be found
$exampleOne = $this->exampleRepository->search(new Criteria([$exampleId]), $context)->first();
// Updated entity will be found
$exampleTwo = $this->exampleRepository->search(new Criteria([$exampleId]), $versionContext)->first();
$this->exampleRepository->merge($versionId, $context);
// Our updated entity will be found now
$exampleThree = $this->exampleRepository->search(new Criteria([$exampleId]), $context)->first();
}

As you can see above, we first created a new ExampleEntity with the description 'This is an example'.

Then we created a new version of our entity with the appropriate repository method createVersion and as arguments the id of our created entity and the context. This method returns the id of our new entity version, which we have stored in a variable.

Next, we used the createWithVersionId method of our Context to create a new context with our new versionId assigned to it. This new Context is used to update the ExampleEntity. In our case we have updated the description to 'This is our new description'. By using the updated context with the new versionId, the DAL knows that we want to update this version of our entity.

Subsequently, we searched the repository with the original context and our new versioned context. In the first search result, using the original context, we get the first version of our entity, which we created at the beginning. With the second search result we get the updated entity, using our new versioned context.

Lastly, we used the repository method merge with our versionId, which deletes all versions before this one. The merged version is now our new live version. From now on we can find it without using a versioned context.

Versioning with foreign keys

If you have an entity with foreign keys, your foreign keys also need to be versioned. In this example we're using an inherited field. If you are not familiar with inheritance, head over to our Field inheritance guide.

Migration

In this step we have to additionally add a foreign key constraint for your parent_id and parent_version_id refrencing to our id and version_id. The same pattern applies to other entities.

ALTER TABLE `swag_example`
ADD `version_id` BINARY(16) NOT NULL AFTER `id`,
ADD `parent_version_id` BINARY(16) NOT NULL,
ADD PRIMARY KEY `id_version_id` (`id`, `version_id`),
DROP INDEX `PRIMARY`,
CONSTRAINT `fk.swag_example.parent_id` FOREIGN KEY (`parent_id`, `parent_version_id`)
REFERENCES `swag_example` (`id`, `version_id`) ON DELETE CASCADE ON UPDATE CASCADE

Definition

After we've added the new field to our table, we also have to add it to our definition. For this we use a Shopware\Core\Framework\DataAbstractionLayer\Field\ReferenceVersionField which references to our entity by using self::class and the related field parent_version_id.

<plugin root>/src/Core/Content/Example/ExampleDefinition.php
protected function defineFields(): FieldCollection
{
return new FieldCollection([
new VersionField(),
(new ReferenceVersionField(self::class, 'parent_version_id')),
...
]);
}