定制 tcds-io/orm 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

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

tcds-io/orm

最新稳定版本:1.0.0

Composer 安装命令:

composer require tcds-io/orm

包简介

Simple ORM based which avoid coupling entities to database models

README 文档

README

License: MIT

A lightweight, type-safe Object-Relational Mapper (ORM) for modern PHP (8.4+), designed to be expressive and easy to extend. It emphasizes strict typing and modern PHP features while providing a flexible mapping interface between database rows and PHP objects.

🚀 Installation

Install via Composer:

composer require tcds-io/orm

🧠 Features

  • Typed mapping of database rows to PHP objects
  • Support for Records and Entities
  • Fine-grained control over columns (e.g. enums, dates, nullable values)
  • Lazy-loading support
  • Extendable repository base classes
  • Injectable mappers
  • PHP 8.4+ only, leveraging modern language features

📦 Usage

There are two main types of mappers:

  • RecordMapper<Type> — for mapping simple, immutable data records
  • EntityRecordMapper<Type, PrimaryKeyType> — for mapping richer domain entities, with support for repositories or lazy-loading

These mappers are designed to be injected wherever data transformation is needed, such as in services, repositories, or controllers.

Record Mapper Example

/**
 * @extends RecordMapper<Address>
 */
final class AddressMapper extends RecordMapper
{
    private IntegerColumn $id;
    private StringColumn $street;
    private FloatColumn $number;
    private IntegerColumn $floor;
    private BoolColumn $active;
    private EnumColumn $type;
    private DateTimeColumn $createdAt;
    private DateTimeImmutableColumn $deletedAt;

    public function __construct()
    {
        $this->id = $this->integer('id', fn(Address $entry) => $entry->id);
        $this->street = $this->string('street', fn(Address $entity) => $entity->street);
        $this->number = $this->numeric('number', fn(Address $entity) => $entity->number);
        $this->floor = $this->integer('floor', fn(Address $entity) => $entity->floor);
        $this->active = $this->boolean('active', fn(Address $entity) => $entity->active);
        $this->type = $this->enum(AddressType::class, 'type', fn(Address $entity) => $entity->type);
        $this->createdAt = $this->datetime('created_at', fn(Address $entity) => $entity->createdAt);
        $this->deletedAt = $this->datetimeImmutable('deleted_at', fn(Address $entity) => $entity->deletedAt);
    }

    public function map(array $row): Address
    {
        return new Address(
            id: $this->id->value($row),
            street: $this->street->value($row),
            number: $this->number->value($row),
            floor: $this->floor->value($row),
            active: $this->active->value($row),
            type: $this->type->value($row),
            createdAt: $this->createdAt->value($row),
            deletedAt: $this->deletedAt->nullable($row),
        );
    }
}

Entity Mapper Example

/**
 * @extends EntityRecordMapper<User, int>
 */
final class UserMapper extends EntityRecordMapper
{
    /** @var LazyBuffer<string, Address> */
    private LazyBuffer $addressLoader;

    public function __construct(
        private readonly AddressRepository $addressRepository,
    ) {
        parent::__construct($this->string('id', fn(User $entity) => $entity->id));

        $this->string('name', fn(User $entity) => $entity->name);
        $this->date('date_of_birth', fn(User $entity) => $entity->dateOfBirth);
        $this->integer('address_id', fn(User $entity) => $entity->address->id);

        $this->addressLoader = lazyBufferOf(Address::class, function (array $ids) {
            return listOf($this->addressRepository->loadAllByIds($ids))
                ->indexedBy(fn(Address $address) => $address->id)
                ->entries();
        });
    }

    #[Override] public function map(array $row): User
    {
        return new User(
            id: $row['id'],
            name: $row['name'],
            dateOfBirth: new DateTime($row['date_of_birth']),
            address: $this->addressLoader->lazyOf($row['address_id']),
        );
    }
}

Nullable Support

For nullable fields, use the ->nullable(...) method on column definitions. This allows you to gracefully handle NULL values in your database rows.

Foreign keys and objects

The ORM does not resolve foreign keys and objects automatically. Instead, you must inject the object repository and load the object as needed:

return new User(
    ...,
    /** lazy load foreign object */
    address: lazyOf(Address::class, fn() => $addressRepository->loadById($row['address_id'])),
    /** eager load foreign object */
    address: $addressRepository->loadById($row['address_id']),
);

Lazy loading

Records can be lazy-loaded with the lazyOf function, which receives an initializer and loads the entry only when any of its properties are accessed:

/** lazy object */
$address = lazyOf(
    /** The class to be loaded */
    Address::class,
    /** The object initializer */
    fn() => $addressRepository->loadById($addressId),
);

/** loaded object */
$street = $address->street;

Solving N+1 problems

N+1 can be solved with the lazyBufferOf function, which manages buffered and loaded records. All buffered records are loaded at once when any of the entries are accessed, and all previously loaded records are returned immediately without additional loader calls.

$addressLoader = lazyBufferOf(
    /** The class to be loaded */
    Address::class,
    /** The object list loader */
    function (array $bufferedIds) {
        listOf($this->addressRepository->loadAllByIds($bufferedIds))
          ->indexedBy(fn(Address $address) => $address->id)
          ->entries();
    },
);

/** lazy object */
$address = $addressLoader->lazyOf($addressId);

/** loaded object */
$street = $address->street;

🗃️ Repositories

This library also provides base repository classes that you can extend to perform actual database operations.

Record Repository Example

/**
 * @extends RecordRepository<Address>
 */
class AddressRepository extends RecordRepository
{
    public function __construct(Connection $connection, RecordMapper $mapper)
    {
        parent::__construct($mapper, $connection, 'addresses');
    }

    public function loadById(int $id): Address
    {
        return $this->selectOneWhere(['id' => $id]) ?? throw new Exception('Address not found');
    }
}

Entity Repository Example

/**
 * @extends EntityRecordRepository<User, string>
 */
class UserRepository extends EntityRecordRepository
{
    public function __construct(Connection $connection, UserMapper $mapper)
    {
        parent::__construct($mapper, $connection, 'users');
    }

    public function loadById(string $id): User
    {
        return $this->selectEntityById($id) ?? throw new Exception('User not found');
    }
}

🔧 Repository Capabilities

📘 RecordRepository Provides core operations for working with raw records:

insertOne($entry)
selectOneWhere(where: ['id' => 10])
selectOneByQuery(selectQuery: 'SELECT * FROM table where id = :id', bindings: ['id' => 10])
selectManyWhere([where: 'deleted_at' => null], limit: 10, offset: 100)
selectManyByQuery(selectQuery: 'SELECT * FROM table where deleted_at is null', bindings: [])
existsWhere(where: ['id' => 10])
deleteWhere(where: ['id' => 10])
updateWhere(values: ['name' => 'Arthur Dent', 'date_of_birth' => '1990-01-01'], where: ['id' => 10])

📙 EntityRecordRepository extends RecordRepository with additional features for managing entity lifecycles:

selectEntityById(id: 10)
updateOne(entity: $user)
updateMany($user1, $user2, $user3, ...)
deleteOne(entity: $user)
deleteMany($user1, $user2, $user3, ...)

🤝 Contributing

Contributions are welcome! If you have ideas, find a bug, or want to improve the library, feel free to:

  • Fork the repo
  • Create a new branch
  • Submit a pull request

Please follow PSR-12 coding standards and ensure tests pass before submitting changes.

🚀 Next steps

  • Query builder
  • Extend where comparisons

📄 License

This project is open-sourced under the MIT license.

Happy Mapping! 🎉

统计信息

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

GitHub 信息

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

其他信息

  • 授权协议: MIT
  • 更新时间: 2023-04-01

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固