Skip to content

Create data migration action

To create an action that is performed after a migration step, you need:

  • An action class, to store any additional data that you might require.
  • An action denormalizer, to convert YAML definition into your action class.
  • An action executor, to handle the action.

The following example shows how to create an action that assigns a content item to a Section.

First, create an action class, in src/Migrations/Action/AssignSection.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

declare(strict_types=1);

namespace App\Migrations\Action;

use Ibexa\Migration\ValueObject\Step\Action;

final class AssignSection implements Action
{
    public const TYPE = 'assign_section';

    /** @var string */
    private $sectionIdentifier;

    public function __construct(string $sectionIdentifier)
    {
        $this->sectionIdentifier = $sectionIdentifier;
    }

    /**
     * @return string
     */
    public function getValue(): string
    {
        return $this->sectionIdentifier;
    }

    public function getSupportedType(): string
    {
        return self::TYPE;
    }
}

Then you need a denormalizer to convert data that comes from YAML into an action object, in src/Migrations/Action/AssignSectionDenormalizer.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?php

declare(strict_types=1);

namespace App\Migrations\Action;

use Ibexa\Contracts\Migration\Serializer\Denormalizer\AbstractActionDenormalizer;
use Webmozart\Assert\Assert;

final class AssignSectionDenormalizer extends AbstractActionDenormalizer
{
    protected function supportsActionName(string $actionName, string $format = null): bool
    {
        return $actionName === AssignSection::TYPE;
    }

    /**
     * @param array<mixed> $data
     * @param string $type
     * @param string|null $format
     * @param array<mixed> $context
     *
     * @return \App\Migrations\Action\AssignSection
     */
    public function denormalize($data, string $type, string $format = null, array $context = []): AssignSection
    {
        Assert::keyExists($data, 'value');

        return new AssignSection($data['value']);
    }
}

Then, tag the action denormalizer so it is recognized by the serializer used for migrations.

1
2
3
4
5
services:
    App\Migrations\Action\AssignSectionDenormalizer:
        autoconfigure: false
        tags:
            - { name: 'ibexa.migrations.serializer.normalizer' }

And finally, add an executor to perform the action, in src/Migrations/Action/AssignSectionExecutor.php:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php declare(strict_types=1);

namespace App\Migrations\Action;

use Ibexa\Contracts\Core\Repository\ContentService;
use Ibexa\Contracts\Core\Repository\SectionService;
use Ibexa\Contracts\Core\Repository\Values\ValueObject as APIValueObject;
use Ibexa\Migration\StepExecutor\ActionExecutor\ExecutorInterface;
use Ibexa\Migration\ValueObject;

final class AssignSectionExecutor implements ExecutorInterface
{
    /** @var \Ibexa\Contracts\Core\Repository\SectionService */
    private $sectionService;

    /** @var \Ibexa\Contracts\Core\Repository\ContentService */
    private $contentService;

    public function __construct(
        ContentService $contentService,
        SectionService $sectionService
    ) {
        $this->sectionService = $sectionService;
        $this->contentService = $contentService;
    }

    /**
     * @param \App\Migrations\Action\AssignSection $action
     * @param \Ibexa\Contracts\Core\Repository\Values\Content\Content $content
     */
    public function handle(ValueObject\Step\Action $action, APIValueObject $content): void
    {
        $contentInfo = $this->contentService->loadContentInfo($content->id);
        $section = $this->sectionService->loadSectionByIdentifier($action->getValue());
        $this->sectionService->assignSection($contentInfo, $section);
    }
}

Tag the executor with ibexa.migrations.executor.action.<type> tag, where <type> is the "type" of the step that executor works with (content, content_type, location, and so on). The tag has to have a key property with the action type.

1
2
3
    App\Migrations\Action\AssignSectionExecutor:
        tags:
            - { name: 'ibexa.migrations.executor.action.content', key: !php/const App\Migrations\Action\AssignSection::TYPE }