PHP code example of tobento / service-repository-storage

1. Go to this page and download the library: Download tobento/service-repository-storage library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

tobento / service-repository-storage example snippets


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)

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)

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

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)
}

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

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)

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)

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) }

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) }

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)
}

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) }

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)
}

$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'],
]);

$entities = $repository->findAll(where: [
    'title' => ['like' => '%foo%', 'or like' => '%bar%'],
    
    // or
    'title' => ['like' => '%foo%'],
    'price' => ['or >' => 1.5],
    
    // or using an array
    'id' => ['in' => [2,5,6]],
    [
        'title' => ['or like' => '%foo%'],
        'price' => ['or >' => 1.5],
    ]
]);

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" }

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 $action): string => ucfirst($value)),
        Column\Boolean::new('active'),
    ],
);

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 $action): string => ucfirst($value)),
            Column\Boolean::new('active'),
        ];
    }
}

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

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);

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

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

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

$column = Text::new(name: 'name')
    ->write(fn (string $value, array $attributes, string $action): string => ucfirst($value));
    // $action = the action name processed such as 'create' or 'update'

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

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

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

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

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

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

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

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);

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);

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

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

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

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

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

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

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

$column = Datetime::new(name: 'updated_at')->autoUpdate();

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);

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],
    );

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);

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

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

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

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');

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 $action, 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 $action, string $locale): array => $value);

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)

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

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

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

$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',
]);

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

// 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',
]);

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' => '[email protected]'],
                ],

                // you may set if items should be created using a boolean:
                createItems: true, // default
                
                // or using a closure:
                createItems: fn ($repo): bool => is_null($repo->findOne()),
            ),
            
            // 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' => '[email protected]'],
                ],
                
                createItems: true, // default
            ),
            
            // 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' => '[email protected]'],
                ],
                
                createItems: true, // default
            ),
        );
    }

    /**
     * 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',
            ),
        );
    }
}
array