adachsoft/dynamic-table-contract

Contract library for dynamic table system

Maintainers

Package info

gitlab.com/a.adach/dynamic-table-contract

Issues

pkg:composer/adachsoft/dynamic-table-contract

Statistics

Installs: 0

Dependents: 0

Suggesters: 0

Stars: 0

v0.1.0 2026-04-11 10:13 UTC

This package is not auto-updated.

Last update: 2026-04-11 11:03:17 UTC


README

PHP contract library for a dynamic table system (schema + data) shared between validator, storage, UI/form generator and API layers.

Goal

This package is contract-only. It defines immutable DTOs, collections and interfaces that describe a dynamic table system, without providing any business logic or persistence implementation.

It is designed to be used by multiple cooperating packages, for example:

  • validator – validates TableDto and RowDto using ColumnTypeInterface implementations,
  • storage (JSON / SQL / other) – persists tables and rows via repository interfaces,
  • form / UI generator – builds forms and widgets from column definitions and relation configs,
  • API layer – exposes and consumes tables and rows using the same contracts.

The contracts live here so that all these layers can evolve independently while still speaking the same language.

Requirements

  • PHP: >= 8.2
  • Strict types: every PHP file in this package uses declare(strict_types=1);
  • Only external runtime dependency:

Installation

composer require adachsoft/dynamic-table-contract

The package is autoloaded under the root namespace:

AdachSoft\DynamicTableContract\

What this package is not

This library intentionally does not contain:

  • business logic or validation logic,
  • repository implementations (no database / JSON / cache code),
  • framework integration (no Symfony / Laravel specific code),
  • service containers, factories or registries.

It only defines contracts that other packages are expected to implement.

Overview of contracts

DTOs (immutable data structures)

All DTOs are readonly classes and are meant to be simple data carriers.

TableDto

Namespace: AdachSoft\DynamicTableContract\Dto\TableDto

Represents a complete table: schema (columns) + data (rows).

use AdachSoft\DynamicTableContract\Collection\ColumnCollection;
use AdachSoft\DynamicTableContract\Collection\RowCollection;
use AdachSoft\DynamicTableContract\Dto\TableDto;

$table = new TableDto(
    id: 'clients',
    columns: new ColumnCollection([...]),
    rows: new RowCollection([...]),
);

Properties:

  • string $id – unique table identifier (e.g. "clients", "orders").
  • ColumnCollection $columns – collection of column definitions describing the table schema.
  • RowCollection $rows – collection of rows associated with this table.

ColumnDto

Namespace: AdachSoft\DynamicTableContract\Dto\ColumnDto

Describes a single column in a dynamic table.

use AdachSoft\DynamicTableContract\Dto\ColumnDto;
use AdachSoft\DynamicTableContract\Type\ColumnType;

$column = new ColumnDto(
    name: 'status',
    type: ColumnType::STRING,
    required: true,
    config: [
        'allowed_values' => ['new', 'active', 'archived'],
    ],
);
  • string $name – unique column name within a single table (e.g. "id", "name").
  • string $type – column type identifier. Typically one of the built‑in ColumnType::* constants, but can also be a custom string handled by external ColumnTypeInterface implementations.
  • bool $required – whether a value is required for this column.
  • array<string,mixed> $config – type‑specific configuration options.
    • For type relation this MUST contain a serialized representation of RelationConfigDto.

RowDto

Namespace: AdachSoft\DynamicTableContract\Dto\RowDto

Represents a single data row.

use AdachSoft\DynamicTableContract\Dto\RowDto;

$row = new RowDto([
    'id' => 1,
    'name' => 'Alice',
    'status' => 'active',
]);
  • array<string,mixed> $values – map from column name (ColumnDto::$name) to a raw value. No validation is performed at this level.

RelationConfigDto

Namespace: AdachSoft\DynamicTableContract\Dto\RelationConfigDto

Configuration for a column of type relation. Instances of this DTO are expected to be serialized into an array and stored in ColumnDto::$config.

use AdachSoft\DynamicTableContract\Dto\RelationConfigDto;

$relationConfig = new RelationConfigDto(
    targetTableId: 'clients',
    valueColumn: 'id',
    labelColumn: 'name',
    multiple: false,
);

Properties:

  • string $targetTableId – identifier of the target table (e.g. "clients", "dict_status").
  • string $valueColumn – column name in the target table that holds the foreign key value (e.g. "id").
  • string $labelColumn – column name in the target table that should be displayed to the user (e.g. "name").
  • bool $multiple – whether multiple records can be selected for this relation.

Collections

Collections are built on top of adachsoft/collection and provide strongly‑typed containers for DTOs.

ColumnCollection

Namespace: AdachSoft\DynamicTableContract\Collection\ColumnCollection

  • Extends AdachSoft\Collection\AbstractCollection<ColumnDto>.
  • Stores ColumnDto instances indexed by column name (ColumnDto::$name).

Key semantics:

  • Keys are always strings equal to the column name.
  • Adding a column with the same name overwrites the previous definition.

Important methods:

  • add(ColumnDto $column): void – adds a column and indexes it by its name.
  • find(string $name): ?ColumnDto – returns the column definition for the given name, or null when not found.
  • all(): array<string,ColumnDto> – returns all column definitions indexed by name.

Typical usage:

use AdachSoft\DynamicTableContract\Collection\ColumnCollection;
use AdachSoft\DynamicTableContract\Dto\ColumnDto;
use AdachSoft\DynamicTableContract\Type\ColumnType;

$columns = new ColumnCollection();

$columns->add(new ColumnDto('id', ColumnType::INT, true, []));
$columns->add(new ColumnDto('name', ColumnType::STRING, true, []));
$columns->add(new ColumnDto('status', ColumnType::STRING, false, []));

$idColumn = $columns->find('id');        // ColumnDto|null
$allColumns = $columns->all();          // array<string, ColumnDto>

RowCollection

Namespace: AdachSoft\DynamicTableContract\Collection\RowCollection

  • Extends AdachSoft\Collection\AbstractCollection<RowDto>.
  • Stores RowDto instances using sequential numeric keys in insertion order.

Important methods:

  • add(RowDto $row): void (inherited from AbstractCollection).
  • all(): RowDto[] – returns all rows as a numerically indexed list.

Example:

use AdachSoft\DynamicTableContract\Collection\RowCollection;
use AdachSoft\DynamicTableContract\Dto\RowDto;

$rows = new RowCollection([
    new RowDto(['id' => 1, 'name' => 'Alice']),
    new RowDto(['id' => 2, 'name' => 'Bob']),
]);

foreach ($rows->all() as $row) {
    // $row is a RowDto
}

Column type system

ColumnType

Namespace: AdachSoft\DynamicTableContract\Type\ColumnType

A set of string constants representing built‑in column types:

final class ColumnType
{
    public const string STRING   = 'string';
    public const string INT      = 'int';
    public const string FLOAT    = 'float';
    public const string BOOL     = 'bool';
    public const string DATE     = 'date';
    public const string NUMERIC  = 'numeric';
    public const string MONEY    = 'money';
    public const string JSON     = 'json';
    public const string RELATION = 'relation';

    private function __construct() {}
}

Important: this is not an enum on purpose. The type system is open – external packages may define additional types by implementing ColumnTypeInterface and returning their own getName() values.

ColumnTypeInterface

Namespace: AdachSoft\DynamicTableContract\Type\ColumnTypeInterface

Contract for all column type implementations. This package does not provide any concrete types.

interface ColumnTypeInterface
{
    public function getName(): string;

    /**
     * @param array<string, mixed> $config
     */
    public function validate(mixed $value, array $config = []): bool;

    /**
     * @param array<string, mixed> $config
     */
    public function normalize(mixed $value, array $config = []): mixed;
}

Guidelines:

  • getName() should return a string that matches either a value from ColumnType or a custom value used across your system.
  • validate() should not throw exceptions; it returns true/false.
  • normalize() converts the raw value into the desired PHP type (e.g. stringDateTimeImmutable, stringMoney object, etc.).

A typical validator package would:

  1. Resolve a ColumnTypeInterface implementation based on ColumnDto::$type.
  2. Call validate($value, $config) to check the value.
  3. Call normalize($value, $config) to convert it for further processing or storage.

Repository interfaces

The repository interfaces describe how table definitions and rows can be persisted and loaded. This package does not provide any implementations.

TableRepositoryInterface

Namespace: AdachSoft\DynamicTableContract\Repository\TableRepositoryInterface

use AdachSoft\DynamicTableContract\Dto\TableDto;
use AdachSoft\DynamicTableContract\Exception\TableNotFoundExceptionInterface;

interface TableRepositoryInterface
{
    public function save(TableDto $table): void;

    /**
     * @throws TableNotFoundExceptionInterface
     */
    public function get(string $id): TableDto;
}
  • save(TableDto $table): void – persists a table definition.
  • get(string $id): TableDto – loads a table definition by id or throws TableNotFoundExceptionInterface if not found.

RowRepositoryInterface

Namespace: AdachSoft\DynamicTableContract\Repository\RowRepositoryInterface

use AdachSoft\DynamicTableContract\Collection\RowCollection;
use AdachSoft\DynamicTableContract\Dto\RowDto;

interface RowRepositoryInterface
{
    public function add(string $tableId, RowDto $row): void;

    public function getAll(string $tableId): RowCollection;
}
  • add(string $tableId, RowDto $row): void – stores a new row for the given table.
  • getAll(string $tableId): RowCollection – returns all rows for the given table.

Typical storage packages (JSON, SQL, etc.) implement these interfaces and use the DTOs and collections from this contract.

Exception contracts

All exceptions in the dynamic table system share a common root interface.

Hierarchy:

TableExceptionInterface  (extends \Throwable)
├── TableNotFoundExceptionInterface
├── ValidationExceptionInterface
├── ColumnNotFoundExceptionInterface
└── UnknownTypeExceptionInterface

Namespaces:

  • AdachSoft\DynamicTableContract\Exception\TableExceptionInterface
  • AdachSoft\DynamicTableContract\Exception\TableNotFoundExceptionInterface
  • AdachSoft\DynamicTableContract\Exception\ValidationExceptionInterface
  • AdachSoft\DynamicTableContract\Exception\ColumnNotFoundExceptionInterface
  • AdachSoft\DynamicTableContract\Exception\UnknownTypeExceptionInterface

Intended usage:

  • TableExceptionInterface – base marker interface for all table‑related exceptions.
  • TableNotFoundExceptionInterface – thrown when a table cannot be found in a repository.
  • ValidationExceptionInterface – thrown when table structure or row data fails validation.
  • ColumnNotFoundExceptionInterface – thrown when an expected column definition is missing.
  • UnknownTypeExceptionInterface – thrown when a column type identifier is not recognized.

Storage, validator and UI packages are expected to implement concrete exception classes that implement these interfaces.

Putting it together – example flow

A typical high‑level flow using this contract might look like this:

  1. Schema definition (e.g. configuration, admin UI):

    • Build ColumnDto instances for each column.
    • Use ColumnCollection to group them.
    • Save the resulting TableDto via TableRepositoryInterface.
  2. Data input (e.g. API or form submission):

    • Accept raw data and create RowDto instances.
    • Use a validator package that:
      • loads TableDto via TableRepositoryInterface,
      • inspects TableDto::$columns to know what is required and which types to use,
      • for each column resolves a ColumnTypeInterface,
      • calls validate() and normalize() for every value,
      • throws ValidationExceptionInterface or more specific exceptions on error.
  3. Persistence of data:

    • After successful validation, pass RowDto instances to an implementation of RowRepositoryInterface.
  4. Rendering / UI:

    • A UI package can inspect ColumnCollection (names, types, configs) to build forms or grids,
    • and use RowCollection to render data rows.

This package stays focused on these contracts so that other packages can implement the actual behavior independently.

Contributing

This repository is designed as a thin contract layer. When contributing, please keep in mind:

  • Do not introduce business logic or storage logic into this package.
  • Any new public contract (DTO / interface / collection) must be:
    • immutable where applicable (readonly),
    • fully documented in PHPDoc and in this README.md,
    • consistent with existing naming and namespace conventions.

Bug reports and suggestions should focus on clarifying or extending contracts rather than on specific implementations.