tobento/service-repository-storage

Storage repository implementation.

1.0.3 2024-02-23 17:01 UTC

This package is auto-updated.

Last update: 2024-04-23 17:23:21 UTC


README

Storage repository implementation.

Table of Contents

Getting started

Add the latest version of the repository storage service project running this command.

composer require tobento/service-repository-storage

Requirements

  • PHP 8.0 or greater

Highlights

  • Framework-agnostic, will work with any project
  • Decoupled design

Documentation

Check out the Storage Service - Storages for its available storages.

Check out the Repository Service for its documentation.

Storage Repository

To create a storage repository simply extend the StorageRepository::class:

You may also check out the Repository With Columns section to create the repository using columns.

use Tobento\Service\Repository\RepositoryInterface;
use Tobento\Service\Repository\ReadRepositoryInterface;
use Tobento\Service\Repository\WriteRepositoryInterface;
use Tobento\Service\Repository\Storage\StorageRepository;
use Tobento\Service\Repository\Storage\StorageEntityFactoryInterface;
use Tobento\Service\Storage\InMemoryStorage;
use Tobento\Service\Storage\Tables\Tables;

class ProductRepository extends StorageRepository
{
    //
}

$repository = new ProductRepository(
    storage: new  InMemoryStorage(
        items: [],
        tables: (new Tables())->add('products', ['id', 'sku', 'price'], 'id')
    ),
    table: 'products', // specify which storage table should be used.
    entityFactory: null, // null|StorageEntityFactoryInterface
);

var_dump($repository instanceof RepositoryInterface);
// bool(true)

var_dump($repository instanceof ReadRepositoryInterface);
// bool(true)

var_dump($repository instanceof WriteRepositoryInterface);
// bool(true)

By default, the read and write methods will return the following types depending on the method called:

You may specify a custom Storage Entity Factory to return custom entities by the entityFactory parameter.

Storage Read Repository

To create a storage read repository simply extend the StorageReadRepository::class:

You may also check out the Repository With Columns section to create the repository using columns.

use Tobento\Service\Repository\ReadRepositoryInterface;
use Tobento\Service\Repository\Storage\StorageReadRepository;
use Tobento\Service\Repository\Storage\StorageEntityFactoryInterface;
use Tobento\Service\Storage\InMemoryStorage;
use Tobento\Service\Storage\Tables\Tables;

class ProductReadRepository extends StorageReadRepository
{
    // adding custom find methods
}

$repository = new ProductReadRepository(
    storage: new  InMemoryStorage(
        items: [
            'products' => [
                1 => ['id' => 1, 'sku' => 'paper', 'price' => 1.2],
                2 => ['id' => 2, 'sku' => 'pen', 'price' => 1.8],
                3 => ['id' => 3, 'sku' => 'pencil', 'price' => 1.5],
            ],
        ],
        tables: (new Tables())->add('products', ['id', 'sku', 'price'], 'id')
    ),
    table: 'products',
    entityFactory: null, // null|StorageEntityFactoryInterface
);

var_dump($repository instanceof ReadRepositoryInterface);
// bool(true)

By default, the find methods will return the following types depending on the method called:

You may specify a custom Storage Entity Factory to return custom entities by the entityFactory parameter.

findById

use Tobento\Service\Storage\ItemInterface;

$entity = $repository->findById(id: 2);

var_dump($entity instanceof ItemInterface);
// bool(true)

var_dump($entity->get('sku'));
// string(3) "pen"

$entity = $repository->findById(id: 5);

var_dump($entity);
// NULL

You may check out the Storage Service - ItemInterface for its documentation.

findByIds

use Tobento\Service\Storage\ItemsInterface;
use Tobento\Service\Storage\ItemInterface;

$entities = $repository->findByIds(1, 2, 8);

var_dump($entities instanceof ItemsInterface);
// bool(true)

var_dump($entities->count());
// int(2)

foreach($entities as $entity) {
    var_dump($entity instanceof ItemInterface);
    // bool(true)
}

You may check out the Storage Service - ItemsInterface for its documentation.

You may check out the Storage Service - ItemInterface for its documentation.

findOne

use Tobento\Service\Storage\ItemInterface;

$entity = $repository->findOne(where: [
    'sku' => 'pen',
]);

var_dump($entity instanceof ItemInterface);
// bool(true)

var_dump($entity->get('sku'));
// string(3) "pen"

$entity = $repository->findOne(where: [
    'sku' => 'foo',
]);

var_dump($entity);
// NULL

You may check out the Where Parameters for its supported parameters.

You may check out the Storage Service - ItemInterface for its documentation.

findAll

use Tobento\Service\Storage\ItemsInterface;
use Tobento\Service\Storage\ItemInterface;

$entities = $repository->findAll(where: [
    'price' => ['>' => 1.3],
]);

var_dump($entities instanceof ItemsInterface);
// bool(true)

var_dump($entities->count());
// int(2)

foreach($entities as $entity) {
    var_dump($entity instanceof ItemInterface);
    // bool(true)
}

$entities = $repository->findAll(
    where: [
        'price' => [
            '>' => 1.3,
            '<' => 1.6,
        ],
        'sku' => ['like' => 'pe%'],
    ],
    orderBy: [
        'sku' => 'DESC', // or 'ASC'
    ],
    limit: 20, // (number)
    // limit: [20, 5], // [20(number), 5(offset)]
);

var_dump($entities->count());
// int(1)

You may check out the Where Parameters for its supported parameters.

You may check out the Storage Service - ItemsInterface for its documentation.

You may check out the Storage Service - ItemInterface for its documentation.

Storage Write Repository

To create a storage write repository simply extend the StorageWriteRepository::class:

You may also check out the Repository With Columns section to create the repository using columns.

use Tobento\Service\Repository\WriteRepositoryInterface;
use Tobento\Service\Repository\Storage\StorageWriteRepository;
use Tobento\Service\Repository\Storage\StorageEntityFactoryInterface;
use Tobento\Service\Storage\InMemoryStorage;
use Tobento\Service\Storage\Tables\Tables;

class ProductWriteRepository extends StorageWriteRepository
{
    // you may add custom write methods
}

$repository = new ProductWriteRepository(
    storage: new  InMemoryStorage(
        items: [
            'products' => [
                1 => ['id' => 1, 'sku' => 'paper', 'price' => 1.2],
                2 => ['id' => 2, 'sku' => 'pen', 'price' => 1.8],
                3 => ['id' => 3, 'sku' => 'pencil', 'price' => 1.5],
            ],
        ],
        tables: (new Tables())->add('products', ['id', 'sku', 'price'], 'id')
    ),
    table: 'products',
    entityFactory: null, // null|StorageEntityFactoryInterface
);

var_dump($repository instanceof WriteRepositoryInterface);
// bool(true)

By default, the methods will return the following types depending on the method called:

You may specify a custom Storage Entity Factory to return custom entities by the entityFactory parameter.

create

use Tobento\Service\Storage\ItemInterface;

$createdEntity = $repository->create(attributes: [
    'sku' => 'scissors',
]);

var_dump($createdEntity instanceof ItemInterface);
// bool(true)

var_dump($createdEntity->all());
// array(2) { ["sku"]=> string(8) "scissors" ["id"]=> int(4) }

You may check out the Storage Service - ItemInterface for its documentation.

updateById

use Tobento\Service\Storage\ItemInterface;
use Tobento\Service\Repository\RepositoryUpdateException;

$updatedEntity = $repository->updateById(
    id: 2,
    attributes: [
        'price' => 2.5,
    ]
);

var_dump($updatedEntity instanceof ItemInterface);
// bool(true)

var_dump($updatedEntity->all());
// array(2) { ["sku"]=> string(8) "scissors" ["id"]=> int(4) }

This method will throw a RepositoryUpdateException::class exception if the storage table has no primary key specified or the entity to update does not exist.

You may check out the Storage Service - ItemInterface for its documentation.

update

use Tobento\Service\Storage\ItemsInterface;
use Tobento\Service\Storage\ItemInterface;

$updatedEntities = $repository->update(
    where: [
        'id' => ['>' => 1],
    ],
    attributes: [
        'price' => 2.5,
    ],
);

var_dump($updatedEntities instanceof ItemsInterface);
// bool(true)

var_dump($updatedEntities->count());
// int(2)

foreach($updatedEntities as $entity) {
    var_dump($entity instanceof ItemInterface);
    // bool(true)
}

You may check out the Where Parameters for its supported parameters.

You may check out the Storage Service - ItemsInterface for its documentation.

You may check out the Storage Service - ItemInterface for its documentation.

deleteById

use Tobento\Service\Storage\ItemInterface;
use Tobento\Service\Repository\RepositoryDeleteException;

$deletedEntity = $repository->deleteById(id: 2);

var_dump($deletedEntity instanceof ItemInterface);
// bool(true)

var_dump($deletedEntity->all());
// array(3) { ["id"]=> int(2) ["sku"]=> string(3) "pen" ["price"]=> float(1.8) }

This method will throw a RepositoryDeleteException::class exception if the storage table has no primary key specified or the entity to delete does not exist.

You may check out the Storage Service - ItemInterface for its documentation.

delete

use Tobento\Service\Storage\ItemsInterface;
use Tobento\Service\Storage\ItemInterface;

$deletedEntities = $repository->delete(where: [
    'id' => ['>' => 1],
]);

var_dump($deletedEntities instanceof ItemsInterface);
// bool(true)

var_dump($deletedEntities->count());
// int(2)

foreach($deletedEntities as $entity) {
    var_dump($entity instanceof ItemInterface);
    // bool(true)
}

You may check out the Where Parameters for its supported parameters.

You may check out the Storage Service - ItemsInterface for its documentation.

You may check out the Storage Service - ItemInterface for its documentation.

Where Parameters

The following where clauses are supported (for all read/write methods with where parameter):

$entities = $repository->findAll(where: [
    'sku' => 'pen',
    // is equal to:
    'sku' => ['=' => 'pen'],
    
    'sku' => ['!=' => 'pen'],
    
    'sku' => ['null'],
    'sku' => ['not null'],

    'price' => ['>' => 1.5],
    'price' => ['<' => 1.5],
    'price' => ['>=' => 1.5],
    'price' => ['<=' => 1.5],
    'price' => ['<>' => 1.5],
    'price' => ['<=>' => 1.5],
    'price' => ['between' => [2, 5]],
    'price' => ['not between' => [2, 5]],
    'id' => ['in' => [2,5,6]],
    'id' => ['not in' => [2,5,6]],    
    
    // Finds any values that (not) start with "a"
    'title' => ['like' => 'a%'],
    'title' => ['not like' => 'a%'],
    
    // Finds any values that (not) end with "a"
    'title' => ['like' => '%a'],
    'title' => ['not like' => '%a'],
    
    // Finds any values that have (not) "a" in any position
    'title' => ['like' => '%a%'],
    'title' => ['not like' => '%a%'],
    
    // Json specific:
    'options->color' => 'blue',
    
    'options->colors' => ['contains' => 'blue'],
    'options->colors' => ['contains' => ['blue']],
    
    'options->color' => ['contains key'],
]);

Storage Entity Factory

You may create a custom entity factory to return custom entities by the Storage Repository, Storage Read Repository or Storage Write Repository.

To create a custom entity factory simply extend the EntityFactory::class and adjust the createEntityFromArray method:

use Tobento\Service\Repository\Storage\EntityFactory;
use Tobento\Service\Repository\Storage\StorageEntityFactoryInterface;
use Tobento\Service\Repository\EntityFactoryInterface;
use Tobento\Service\Storage\ItemInterface;

class ProductFactory extends EntityFactory
{
    public function createEntityFromArray(array $attributes): Product
    {
        // Process the columns reading:
        $attributes = $this->columns->processReading($attributes);
        
        // Create entity:
        return new Product(
            id: $attributes['id'] ?? 0,
            sku: $attributes['sku'] ?? '',
        );
    }
}

class Product
{
    public function __construct(
        public readonly int $id,
        public readonly string $sku,
    ) {}
}

$productFactory = new ProductFactory();
$productFactory->setColumns([]); // will be set by the storage

var_dump($productFactory instanceof StorageEntityFactoryInterface);
// bool(true)

var_dump($productFactory instanceof EntityFactoryInterface);
// bool(true)

$product = $productFactory->createEntityFromArray([
    'id' => 1,
    'sku' => 'pen',
]);

var_dump($product);
// object(Product)#4 (2) { ["id"]=> int(1) ["sku"]=> string(3) "pen" }

Repository With Columns

Creating a storage repository with columns has the following advantages:

  • casts values to primitive types on reading and writing
  • specify a reader and writer to handle casting
  • create database migration based on columns
use Tobento\Service\Repository\Storage\StorageRepository;
use Tobento\Service\Repository\Storage\Column;
use Tobento\Service\Storage\InMemoryStorage;

class ProductRepository extends StorageRepository
{
    //
}

$repository = new ProductRepository(
    storage: new  InMemoryStorage(items: []),
    table: 'products',
    
    // specify the columns:
    columns: [
        Column\Id::new(),
        Column\Text::new('sku'),
        Column\Text::new('title')
            ->read(fn (string $value, array $attributes): string => ucfirst($value))
            ->write(fn (string $value, array $attributes): string => ucfirst($value)),
        Column\Bool::new('active'),
    ],
);

You may prefer to specify the columns on its class instead by using the configureColumns method:

use Tobento\Service\Repository\Storage\StorageRepository;
use Tobento\Service\Repository\Storage\Column\ColumnsInterface;
use Tobento\Service\Repository\Storage\Column\ColumnInterface;
use Tobento\Service\Repository\Storage\Column;
use Tobento\Service\Storage\InMemoryStorage;

class ProductRepository extends StorageRepository
{
    /**
     * Returns the configured columns.
     *
     * @return iterable<ColumnInterface>|ColumnsInterface
     */
    protected function configureColumns(): iterable|ColumnsInterface
    {
        return [
            Column\Id::new(),
            Column\Text::new('sku'),
            Column\Text::new('title')
                ->read(fn (string $value, array $attributes): string => ucfirst($value))
                ->write(fn (string $value, array $attributes): string => ucfirst($value)),
            Column\Bool::new('active'),
        ];
    }
}

$repository = new ProductRepository(
    storage: new  InMemoryStorage(items: []),
    table: 'products',
);

Columns Common Methods

Type

The parameters set on the type method are used for Migration purpose only.

use Tobento\Service\Repository\Storage\Column\Text;
use Tobento\Service\Repository\Storage\Column\Int;

$column = Text::new(name: 'name')
    ->type(length: 150, nullable: false, default: 'foo', parameters: ['charset' => 'utf8mb4']);
    
$column = Int::new(name: 'name')
    ->type(
        length: 20,
        unsigned: true,
        index: ['name' => 'index_name', 'column' => 'name', 'unique' => true, 'primary' => true],
    );
    
$column = Float::new(name: 'name', type: 'decimal')
    ->type(precision: 10, scale: 0);

Check out the Service Database - Column Factory createColumnFromArray method for more detail.

Check out the Service Database - Index Factory createIndexFromArray method for more detail.

Read

You may use the read method to specify a reader (callable). The reader will automatically be called by the repository when attempting to retrieve the value.

use Tobento\Service\Repository\Storage\Column\Text;

$column = Text::new(name: 'name')
    ->read(fn (string $value, array $attributes): string => ucfirst($value));

The value is casted to its column type before being passed to the reader!

Write

You may use the write method to specify a writer (callable). The writer will automatically be called by the repository when attempting to write the value.

use Tobento\Service\Repository\Storage\Column\Text;

$column = Text::new(name: 'name')
    ->write(fn (string $value, array $attributes): string => ucfirst($value));

The value is casted to its column type before being passed to the writer!

Storable

use Tobento\Service\Repository\Storage\Column\Text;

$column = Text::new(name: 'name')
    ->storable(false);

Columns

Boolean

use Tobento\Service\Repository\Storage\Column\Boolean;

$column = Boolean::new(name: 'active');

$column = Boolean::new(name: 'active')
    ->type(default: true);

Datetime

use Tobento\Service\Repository\Storage\Column\Datetime;

$column = Datetime::new(name: 'created_at');

// with datetime type (default):
$column = Datetime::new(name: 'created_at', type: 'datetime')
    ->type(nullable: true);

// with date type:
$column = Datetime::new(name: 'created_at', type: 'date')
    ->type(nullable: true);

// with time type:
$column = Datetime::new(name: 'created_time', type: 'time')
    ->type(nullable: true);

// with timestamp type:
$column = Datetime::new(name: 'created_ts', type: 'timestamp')
    ->type(nullable: true);

read

You may use the read method to cast your value. Without specifying a read method, the value will be casted to a string only.

use Tobento\Service\Repository\Storage\Column\Datetime;
use Tobento\Service\Dater\DateFormatter;
use DateTimeImmutable;

$column = Datetime::new(name: 'created_at');

$read = fn (mixed $value, array $attributes, DateFormatter $df)
    : DateTimeImmutable => $df->toDateTime(value: $value);

$column = Datetime::new(name: 'created_at')->read($read);

Check out the Dater Service - DateFormatter for more detail.

write

You may use the write method to cast your value. Without specifying a write method, the value will be casted to the following formats:

  • datetime type: Y-m-d H:i:s
  • date type: Y-m-d
  • time type: H:i:s
  • timestamp type: timestamp string
use Tobento\Service\Repository\Storage\Column\Datetime;
use Tobento\Service\Dater\DateFormatter;

$column = Datetime::new(name: 'created_at');

$write = fn (mixed $value, array $attributes, DateFormatter $df)
    : string => $df->format(value: $value, format: 'H:i:s');

$column = Datetime::new(name: 'created_at')->read($read);

Check out the Dater Service - DateFormatter for more detail.

Float

use Tobento\Service\Repository\Storage\Column\FloatCol;

$column = FloatCol::new(name: 'name');

// with float type (default):
$column = FloatCol::new(name: 'name', type: 'float')
    ->type(nullable: false, default: 0.5);

// with double type:
$column = FloatCol::new(name: 'name', type: 'double')
    ->type(nullable: false, default: 0.5);

// with decimal type:
$column = FloatCol::new(name: 'name', type: 'decimal')
    ->type(nullable: false, default: 0.5, precision: 10, scale: 0);

Id

use Tobento\Service\Repository\Storage\Column\Id;

$column = Id::new();

// with name (default is id):
$column = Id::new(name: 'some_id');

// with bigPrimary type (default):
$column = Id::new(type: 'bigPrimary')
    ->type(
        length: 18,
        unsigned: true,
        index: ['name' => 'index_name', 'primary' => true],
    );

// with primary:
$column = Id::new(type: 'primary')
    ->type(
        length: 5,
        unsigned: true,
        index: ['name' => 'index_name', 'primary' => true],
    );

Integer

use Tobento\Service\Repository\Storage\Column\Integer;

$column = Integer::new(name: 'name');

// with int type (default):
$column = Integer::new(name: 'name', type: 'int')
    ->type(length: 11, unsigned: true, nullable: false, default: 0);

// with tinyInt type:
$column = Integer::new(name: 'name', type: 'tinyInt')
    ->type(length: 5, unsigned: true, nullable: false, default: 0);

// with bigInt type:
$column = Integer::new(name: 'name', type: 'bigInt')
    ->type(length: 200, unsigned: true, nullable: false, default: 0);

Json

use Tobento\Service\Repository\Storage\Column\Json;

$column = Json::new(name: 'name');

$column = Integer::new(name: 'name')
    ->type(nullable: false, default: ['foo', 'bar']);

Text

use Tobento\Service\Repository\Storage\Column\Text;

$column = Text::new(name: 'sku');

// with string type (default):
$column = Text::new(name: 'sku', type: 'string')
    ->type(length: 100, nullable: false, default: '');

// with char type:
$column = Text::new(name: 'locale', type: 'char')
    ->type(length: 5, nullable: false, default: 'en');

// with text type:
$column = Text::new(name: 'desc', type: 'text')
    ->type(nullable: false, default: 'lorem ipsum');

Translatable

use Tobento\Service\Repository\Storage\Column\Translatable;

$column = Translatable::new(name: 'name');

// with string subtype (default):
$column = Translatable::new(name: 'name', subtype: 'string')
    ->type(nullable: false)
    ->read(fn (string $value, array $attributes, string $locale): string => strtoupper($value))
    ->write(fn (string $value, array $attributes, string $locale): string => strtoupper($value));
    
// with array subtype:
$column = Translatable::new(name: 'name', subtype: 'array')
    ->type(nullable: false)
    ->read(fn (array $value, array $attributes, string $locale): array => $value)
    ->write(fn (array $value, array $attributes, string $locale): array => $value);

Read Attribute

After reading, a StringTranslations::class or ArrayTranslations::class is being created depending on its column subtype string or array.

use Tobento\Service\Repository\Storage\Attribute\StringTranslations;

$repository->locale('en');
$repository->locales('en', 'de', 'fr');
$repository->localeFallbacks(['de' => 'en']);

$entity = $repository->findById(id: 2);

var_dump($entity->get('title') instanceof StringTranslations);
// bool(true)

// The title on the current locale set on the repository:
$title = (string)$entity->get('title');

// or:
$title = $entity->get('title')->get();

// specific locale:
$title = $entity->get('title')->get(locale: 'de');

// specific locale with default value
// if fallback locale value does not exist:
$title = $entity->get('title')->get(locale: 'fr', default: 'title');

// check if translation exists:
var_dump($entity->get('title')->has(locale: 'de'));
// bool(true)

// returns all translations:
var_dump($entity->get('title')->all();
// array(2) {["en"]=> string(5) "Title" ["de"]=> string(5) "Titel"}
use Tobento\Service\Repository\Storage\Attribute\ArrayTranslations;

$repository->locale('en');
$repository->locales('en', 'de', 'fr');
$repository->localeFallbacks(['de' => 'en']);

$entity = $repository->findById(id: 2);

var_dump($entity->get('meta') instanceof ArrayTranslations);
// bool(true)

// The meta on the current locale set on the repository:
$meta = $entity->get('meta')->get();
// array(1) {["color"]=> string(3) "red"}

// specific locale:
$meta = $entity->get('meta')->get(locale: 'de');
// array(1) {["color"]=> string(3) "rot"}

// specific locale with default value
// if fallback locale value does not exist:
$meta = $entity->get('meta')->get(locale: 'fr', default: ['color' => 'rot']);
// array(1) {["color"]=> string(3) "red"}

// check if translation exists:
var_dump($entity->get('title')->has(locale: 'de'));
// bool(true)

// returns all translations:
var_dump($entity->get('meta')->all();
// array(2) {["en"]=> array(1) {["color"]=> string(3) "red"} ["de"]=> array(1) {["color"]=> string(3) "rot"}}

// The meta color on the current locale set on the repository:
$color = $entity->get('meta')->get(key: 'color');
// string(3) "red"

// specific locale:
$color = $entity->get('meta')->get(locale: 'de', key: 'color');
// string(3) "rot"

// specific locale with default value
// if fallback locale value does not exist:
$color = $entity->get('meta')->get(locale: 'fr', key: 'color', default: ['color' => 'rot']);
// string(3) "red"

// check if translation exists:
var_dump($entity->get('title')->has(locale: 'de', key: 'color'));
// bool(true)

Translations

Confiure the locales for the repository:

// current locale:
$repository->locale('en');

// only the defined locales are used:
$repository->locales('en', 'de', 'fr');

// fallbacks:
$repository->localeFallbacks(['de' => 'en']);

Where Parameters Translations

Where clauses for translation columns (for all read/write methods with where parameter):

$entities = $repository->findAll(where: [

    // query current locale set on the repository:
    'title' => ['like' => 'pe%'],
    
    // query specific locale using json syntax:
    'title->de' => ['like' => 'pe%'],
    
    // Array translations:
    // query current locale set on the repository:
    'options->color' => 'red', // same as: options->en->color
    
    // query specific locale using json syntax:
    'options->de->color' => 'red',
]);

Write Translations

create

$createEntity = $repository->create([
    'title' => [
        'en' => 'Title',
        'de' => 'Titel',
    ],
]);

update

// updates all:
$updatedEntity = $repository->updateById(2, [
    'title' => [
        'en' => 'Title',
        'de' => 'Titel',
    ],
]);

// updates specific locale using json syntax:
$updatedEntity = $repository->updateById(2, [
    'title->de' => 'Title',
]);

// Array translations:
// updates specific locale using json syntax:
$updatedEntity = $repository->updateById(2, [
    'options->de->color' => 'red',
]);

Migration

If you have set up your Repository With Columns, you might use the migration RepositoryAction::class and RepositoryDeleteAction::class to create your database migration from the columns defined.

First, you will need to install:

  • composer require tobento/service-database-storage
  • composer require tobento/service-migration

Example of migration class

use Tobento\Service\Repository\Storage\Migration\RepositoryAction;
use Tobento\Service\Repository\Storage\Migration\RepositoryDeleteAction;
use Tobento\Service\Migration\MigrationInterface;
use Tobento\Service\Migration\ActionsInterface;
use Tobento\Service\Migration\Action;
use Tobento\Service\Migration\Actions;

class UserMigration implements MigrationInterface
{
    public function __construct(
        protected UserRepository $userRepository,
    ) {}
    
    /**
     * Return a description of the migration.
     *
     * @return string
     */
    public function description(): string
    {
        return 'Users migration';
    }
    
    /**
     * Return the actions to be processed on install.
     *
     * @return ActionsInterface
     */
    public function install(): ActionsInterface
    {
        // you might check if repository is supported for the action:
        if (RepositoryAction::isSupportedRepository($this->userRepository)) {
            // create action
        }
        
        return new Actions(
            new RepositoryAction(
                repository: $this->userRepository,
                description: 'User migration',
                
                // you might set items to be migrated
                items: [
                    ['email' => 'demo@example.com'],
                ],
            ),
            
            // you might use the newOrNull method
            // if the defined repository is of any type.
            // If it is an unsupported repository
            // a Action\NullAction::class is created.
            RepositoryAction::newOrNull(
                repository: $this->userRepository,
                description: 'User migration',
                
                // you might set items to be migrated
                items: [
                    ['email' => 'demo@example.com'],
                ],
            ),
            
            // you might use the newOrFail method
            // if the defined repository is of any type.
            // If it is an unsupported repository
            // a Action\Fail::class is created.
            RepositoryAction::newOrFail(
                repository: $this->userRepository,
                description: 'User migration',
                
                // you might set items to be migrated
                items: [
                    ['email' => 'demo@example.com'],
                ],
            ),
        );
    }

    /**
     * Return the actions to be processed on uninstall.
     *
     * @return ActionsInterface
     */
    public function uninstall(): ActionsInterface
    {
        // you might check if repository is supported for the action:
        if (RepositoryDeleteAction::isSupportedRepository($this->userRepository)) {
            // create action
        }
        
        return new Actions(
            new RepositoryDeleteAction(
                repository: $this->userRepository,
                description: 'User migration',
            ),
            
            // you might use the newOrNull method
            // if the defined repository is of any type.
            // If it is an unsupported repository
            // a Action\NullAction::class is created.
            RepositoryDeleteAction::newOrNull(
                repository: $this->userRepository,
                description: 'User migration',
            ),
            
            // you might use the newOrFail method
            // if the defined repository is of any type.
            // If it is an unsupported repository
            // a Action\Fail::class is created.
            RepositoryDeleteAction::newOrFail(
                repository: $this->userRepository,
                description: 'User migration',
            ),
        );
    }
}

You may check out the Migration Service for more detail.

Credits