承接 adachsoft/dynamic-table-contract 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

adachsoft/dynamic-table-contract

Composer 安装命令:

composer require adachsoft/dynamic-table-contract

包简介

Contract for dynamic table - interfaces, DTOs, query language

README 文档

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.

NewTableDto (added in Unreleased)

Namespace: AdachSoft\DynamicTableContract\Dto\NewTableDto

Immutable input DTO used for create() and update() operations on tables.

use AdachSoft\DynamicTableContract\Collection\ColumnCollection;
use AdachSoft\DynamicTableContract\Dto\NewTableDto;

$newTable = new NewTableDto(
    id: 'clients',
    columns: new ColumnCollection([...])
);

Properties:

  • string $id – desired table identifier.
  • ColumnCollection $columns – column schema (rows are managed separately via RowRepositoryInterface).

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.

TableCollection (added in Unreleased)

Namespace: AdachSoft\DynamicTableContract\Collection\TableCollection

Mutable collection of TableDto objects (extends AbstractCollection<TableDto>).

use AdachSoft\DynamicTableContract\Collection\TableCollection;

$tables = $repository->getAll(); // returns TableCollection
foreach ($tables->all() as $table) {
    // $table is TableDto
}
  • all(): TableDto[] – returns all tables.
  • Used by TableRepositoryInterface::getAll().

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
}

Query Layer (added in v0.4.0)

Namespace: AdachSoft\DynamicTableContract\Query

Provides a complete, immutable contract for complex queries (filtering, sorting, pagination, column projection and computed expressions).

Key patterns:

  • Composite Pattern for filters (AndFilter, OrFilter, NotFilter + leaf ConditionInterface implementations).
  • Value Objects – all classes are final readonly.
  • Named constructors (::column(), ::expression(), ::of(), ::asc(), ::desc()) for fluent and readable API.
  • Expression system – allows computed values in conditions, sorting and projections (concatenation, templates, JSON extraction, column references).

Main contracts

  • QueryableRowRepositoryInterface – extends RowRepositoryInterface with findBy(string $tableId, QueryParametersDto $query): QueryResultDto.
  • QueryParametersDto – aggregates filter, sorts, pagination and selected columns (null = select all).
  • QueryResultDto – returns RowCollection, total count and optional pagination info.
  • FilterInterface + ConditionInterface – tree of logical filters.
  • ExpressionInterface – computed values (used in conditions, projections, sorting).

Example usage

use AdachSoft\DynamicTableContract\Query\Dto\QueryParametersDto;
use AdachSoft\DynamicTableContract\Query\Expression\ColumnReferenceVo;
use AdachSoft\DynamicTableContract\Query\Expression\ConcatExpressionVo;
use AdachSoft\DynamicTableContract\Query\Filter\AndFilter;
use AdachSoft\DynamicTableContract\Query\Filter\Condition\GreaterThanOrEqualCondition;
use AdachSoft\DynamicTableContract\Query\Filter\Condition\IsNotNullCondition;
use AdachSoft\DynamicTableContract\Query\Filter\Condition\LikeCondition;
use AdachSoft\DynamicTableContract\Query\Pagination\PaginationVo;
use AdachSoft\DynamicTableContract\Query\Sort\SortCollection;
use AdachSoft\DynamicTableContract\Query\Sort\SortVo;

$ownerFullName = new ConcatExpressionVo([
    new ColumnReferenceVo('owners.name'),
    new ColumnReferenceVo('owners.surname'),
], ' ');

$filter = AndFilter::of(
    GreaterThanOrEqualCondition::column('visit_date', '2025-01-01'),
    LikeCondition::expression($ownerFullName, '%Kowalski%'),
    IsNotNullCondition::column('pet_owner_id'),
    LikeCondition::column('action', '%szczepienie%'),
);

$query = new QueryParametersDto(
    filter: $filter,
    sorts: new SortCollection([SortVo::desc('visit_date')]),
    pagination: new PaginationVo(page: 1, perPage: 25),
    selectedColumns: [
        'visit_date',
        'owner' => $ownerFullName,
        'action',
    ],
);

$result = $queryableRowRepository->findBy('pet_visits', $query);

See src/Query/ directory for all classes (SortVo, PaginationVo, all Condition*, all Expression*, collections and interfaces).

All classes are immutable, fully type-safe and come with unit tests.

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 (updated in Unreleased)

Namespace: AdachSoft\DynamicTableContract\Repository\TableRepositoryInterface

Now provides full CRUD in a single interface (consistent with RowRepositoryInterface).

use AdachSoft\DynamicTableContract\Collection\TableCollection;
use AdachSoft\DynamicTableContract\Dto\NewTableDto;
use AdachSoft\DynamicTableContract\Dto\TableDto;
use AdachSoft\DynamicTableContract\Exception\DuplicateTableExceptionInterface;
use AdachSoft\DynamicTableContract\Exception\TableNotFoundExceptionInterface;

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

    /**
     * @throws TableNotFoundExceptionInterface
     */
    public function get(string $id): TableDto;

    /**
     * Creates a new table. Throws DuplicateTableExceptionInterface if ID exists.
     */
    public function create(NewTableDto $newTableDto): TableDto;

    /**
     * Returns all tables (empty collection if none exist).
     */
    public function getAll(): TableCollection;

    /**
     * Updates table (supports rename). Throws exceptions on missing table or ID conflict.
     */
    public function update(string $id, NewTableDto $newTableDto): TableDto;

    /**
     * Deletes a table. Throws TableNotFoundExceptionInterface if not found.
     */
    public function delete(string $id): void;
}

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

QueryableRowRepositoryInterface (new)

Extends RowRepositoryInterface with powerful query capabilities (see Query Layer section above).

Exception contracts

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

Hierarchy (updated):

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

DuplicateTableExceptionInterface (added in Unreleased)

Namespace: AdachSoft\DynamicTableContract\Exception\DuplicateTableExceptionInterface

Thrown when attempting to create or rename a table using an ID that already exists.

namespace AdachSoft\DynamicTableContract\Exception;

interface DuplicateTableExceptionInterface extends TableExceptionInterface
{
}

Other exceptions remain unchanged.

Type Self-Description System (new)

This release introduces a self-describing type system using PHP 8 attributes.

#[TypeMetadata]

Namespace: AdachSoft\DynamicTableContract\Type\Attribute\TypeMetadata

Every ColumnTypeInterface implementation should be decorated with this attribute to provide human-readable metadata and configuration documentation.

use AdachSoft\DynamicTableContract\Type\Attribute\TypeMetadata;
use AdachSoft\DynamicTableContract\Type\Attribute\ConfigOption;

#[TypeMetadata(
    label: 'Data i czas',
    description: 'Handles datetime values...',
    configOptions: [
        new ConfigOption('format', 'Input format', 'string', false, 'Y-m-d H:i:s'),
    ],
    displayConfigOptions: [
        new ConfigOption('format', 'Display format', 'string', false, 'Y-m-d H:i:s'),
    ]
)]
final class DateTimeType implements ColumnTypeInterface { ... }

Supporting classes

  • TypeDescriptorReader – uses Reflection to extract TypeMetadata and converts it to DTOs. Throws MissingTypeMetadataException if attribute is missing.
  • ColumnTypeDescriptionCollection – readonly collection of TypeDescriptionDto indexed by type name. Provides get(), find(), all(), has().
  • TypeDescriptionDto and ConfigOptionDto – immutable data carriers for metadata.

New built-in types

  • TimestampType (timestamp) – Unix timestamp (int).
  • DateTimeType (datetime) – Full datetime support with configurable formats.

All existing built-in types have also been decorated with appropriate #[TypeMetadata] attributes.

Usage example

$reader = new \AdachSoft\DynamicTableContract\Type\TypeDescriptorReader();
$collection = new \AdachSoft\DynamicTableContract\Type\Collection\ColumnTypeDescriptionCollection(
    ...array_map(
        fn($type) => $reader->read($type),
        $allTypeInstances
    )
);

$description = $collection->get('datetime'); // TypeDescriptionDto

This system allows external tools (UI generators, documentation generators, admin panels) to discover available types, their options and documentation without hard-coded knowledge.

All new exceptions implement TableExceptionInterface.

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.
    • Use NewTableDto + TableRepositoryInterface::create() or update().
  2. Data input (e.g. API or form submission):

    • Accept raw data and create RowDto instances.
    • Use a validator package that loads TableDto via repository, inspects columns, etc.
  3. Querying data (new):

    • Build QueryParametersDto with filters, sorting, pagination and projections using the Query Layer.
    • Call findBy() on QueryableRowRepositoryInterface.

(The rest of the flow remains consistent with previous version.)

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.

adachsoft/dynamic-table-contract 适用场景与选型建议

adachsoft/dynamic-table-contract 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 24 次下载、GitHub Stars 达 0, 最近一次更新时间为 2026 年 04 月 11 日, 在 PHP 生态内属于活跃度较高的组件。

它主要适用于以下技术方向: 「adachsoft」 「dynamic-table」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。

我们在过去多个企业项目中使用过 adachsoft/dynamic-table-contract 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。

围绕 adachsoft/dynamic-table-contract 我们能提供哪些服务?
定制开发 / 二次开发

基于 adachsoft/dynamic-table-contract 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。

BUG 修复 & 性能优化

线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。

项目外包 & 长期维护

承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。

yvsm@zunyunkeji.com QQ:316430983 微信:yvsm316 西安尊云信息科技 · 专注 PHP / Go / 分布式系统研发

统计信息

  • 总下载量: 24
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 26
  • 依赖项目数: 3
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-04-11