适用于现代PHP应用程序的简单、低开销的关系型数据库工具包

dev-main 2022-11-04 12:12 UTC

This package is auto-updated.

Last update: 2024-09-04 16:34:05 UTC


README

适用于现代PHP应用程序的简单、低开销的关系型数据库工具包。

php-workflow code-coverage

快速入门

您可以通过标记 SQLX\Entity 属性将任何类映射为实体,并通过标记 SQLX\Id 属性标记id字段。所有其他内容都可以在运行时猜测。

当然,您也可以通过传递 SQLX\Field 注解来明确指定。

<?php

use MNC\SQLX\Engine\Metadata as SQLX;

#[SQLX\Entity]
class Account
{
    #[SQLX\Id]
    private int $id;
    #[SQLX\Field('user_name')]
    private string $username;
    private string $email;
    private string $password;
    private \DateTimeImmutable $createdAt;
    
    public function __construct(string $username, string $email, string $password)
    {
        $this->username = $username;
        $this->email = $email;
        $this->password = $password;
        $this->createdAt = new DateTimeImmutable();    
    }
    
    public function getId(): int
    {
        return $this->id;
    }
    
    public function changePassword(string $password): void
    {
        $this->password = $newPassword;
    }
}

然后,在您的引导代码中,您可以初始化引擎。这里我们使用简单的 PDOWrapper 连接实例进行配置。我们还添加了一个 Namer 策略,将所有没有明确列名的属性转换为下划线。因此,createdAt 属性将被映射到 created_at 列。

<?php

use MNC\SQLX\SQL\Connection\PDOWrapper;
use MNC\SQLX\Engine;
use Castor\Context;

$conn = PDOWrapper::from(new PDO('sqlite::memory'));
$engine = Engine::configure($conn)
    ->withNamer(new Engine\Namer\Underscore())
    ->build()
;

一旦您引导了引擎,将其注入到您的服务中并使用其公开API就很容易了。目前这是完整的公开API。其他所有内容都被视为内部内容。

$ctx = Context\nil();

// You can work with your objects in the domain layer.
$account = new Account('jdoe', 'jdoe@example.com', 'secret');

// Persisting a new object causes an insert:
// INSERT INTO account (user_name, email, password, created_at) VALUES ('jdoe', 'jdoe@example.com', 'secret')
$engine->persist($ctx, $account);

// Upon insertion, we can fetch the last inserted id.
// We grab it automatically for you if the driver supports it.
echo $account->getId(); // (int) 1 

// Is easy to find records:
// SELECT FROM account * WHERE id = 1;
$account = $engine->find($ctx, Account::class)->andWhere('id = ?', 1)->one();

$account->changePassword('secret2');

// Persisting an existing or "known" object causes an update:
// UPDATE account SET user_name = 'jdoe', email = 'jdoe@example.com', password = 'secret2' WHERE id = 1
$engine->persist($ctx, $account);

// You can delete an object of course
// DELETE FROM account WHERE id = 1
$engine->delete($ctx, $account);

注意:所有查询都已正确转义和参数化。

注意事项

这不是一个ORM

这不是一个对象关系映射器,因为它不执行关系。关系不受支持,目前也没有计划。可以说它是一个对象映射器:将数据库中的记录映射到对象,但这也有些牵强。我更喜欢“数据库工具包”这个术语。

适当的关系支持是使ORM复杂化的最大因素之一。跟踪嵌套对象的生存周期、检测它们的更改以及与关系相关的其他事情可能会带来巨大的性能损失。此外,关系被过度评价:在大多数情况下不需要关系,而且常常让经验不足的开发者遇到各种bug(N+1和双向关联)。

即使是 Doctrine最佳实践 也提示应避免不必要的关联,并列出了一些其他主题,其中关系的约束可能会影响性能。

因此,我目前避免提供完整的关系支持。

需要更多的测试

尽管代码库得到了相当程度的测试,并且关键程序得到了很好的覆盖,但我仍然需要编写更多的测试用例,在不同驱动程序、不同查询和边缘情况下进行测试。

构建这样一个成熟的测试套件需要时间,但如果您有兴趣改进特定驱动程序或引擎的支持,我将非常乐意接受PR。 FunctionalTestCase 提供了您设置连接并开始针对特定引擎进行测试所需的一切。我主要对数据库如何接收某些数据类型(日期、二进制大对象)、处理保留关键字、标识符引号以及其他事情感兴趣。当然,find、persist和delete的主API也需要正常工作。