gforces/active-record

使用PHP8属性实现的简单Active Record

v1.1.0 2024-04-03 13:49 UTC

This package is auto-updated.

Last update: 2024-09-04 11:24:42 UTC


README

A new implementation of the Active Record pattern using Attributes available since PHP8. No magic methods, no configuration just actually defined properties in models.

主要功能

  • 没有魔法属性
  • 属性的实际类型而不是字符串
  • 不需要setter和getter
  • 延迟加载的关系
  • 快速

用法

要使用此实现,任何代表数据库表中表的模型都必须继承自 Gforces\ActiveRecord\Base 类。

属性

所有与表中列对应的属性都应该通过添加注释 #[Column] 来标记。您可以根据需要定义属性类型及其可见性。
就这么多!

use Gforces\ActiveRecord\Base;
use Gforces\ActiveRecord\Column;

class Vehicle extends Base
{
    #[Column] public int $id;
    #[Column] public string $make;
    #[Column] public string $model;
}

$vehicle = Vehicle::find(1);
$vehicle->make = 'BMW';
$vehicle->model = 'X1';
$vehicle->save();

内置属性类型

支持所有标量内置类型。

DateTime

\DateTime 属性以格式化的字符串 'Y-m-d H:i:s' 存储在数据库中,并在检索时转换为 \DateTime。数据库中的列不一定是 DATE 或 DATETIME 类型,但在将无效值转换为 DateTime 对象时可能会抛出错误。

单元枚举

如果属性是枚举,则将其存储在数据库中为 case 名称的字符串。数据库中的列可能是枚举类型或不是枚举类型。当从数据库检索时,它将转换为枚举 case 或在具有无效值时抛出错误。

enum Status
{
    case online;
    case offline;
}

class User extends Base
{
    #[Column] 
    public int $id;
    #[Column] 
    public Status $status;
}

后置枚举

如果属性是枚举,则将其存储在数据库中为枚举 case 的值。数据库中的列可能是枚举类型或不是枚举类型。当从数据库检索时,它将转换为枚举值或在具有无效值时抛出错误。

关系

关系与属性一样简单。通过指定属性的类型和可见性以及添加一个属性来指示关系的类型来自然地定义。

class Vehicle extends Base
{
    #[Column] 
    public int $id;
    #[Column]
    public int $owner_id;

    #[BelongsTo]
    public Owner $owner;
}

class Owner extends Base
{
    #[Column] 
    public int $id;
    #[Column] 
    public string $name;

    #[HasMany]
    #[ArrayShape([Vehicle::class])]
    public array $vehicles;
}

$vehicle = new Vehicle;
$vehicle->owner = new Owner();

$owner = new Owner();
$owner->vehicles = [new Vehicle()];
$owner->vehicles[0]->make = 'BMW';
$owner->save(); // or $owner->vehicles[0]->save(); 

验证器

目前,只实现了两个简单的验证器。请随意添加带有新验证器的拉取请求。为了使用验证器,您必须在属性中添加另一个属性。

class Vehicle extends Base
{
    #[Column]
    #[Required]
    public int $id;
    
    #[Column]
    #[Length(max: 30, message: 'Make is too long')]
    public string $make;
    
    #[Column]
    #[Length(min: 10, max: 30)]
    public string $model;
}

设置连接

ActiveRecord 使用 PDO 连接。有两种方法可以配置与数据库的连接

直接设置连接

use Gforces\ActiveRecord\Base;
use Gforces\ActiveRecord\Connection;
Base::setConnection(new Connection($dsn, $username, $password));

它将为所有模型设置相同的连接。您仍然可以为特定模型设置不同的连接

use Gforces\ActiveRecord\Connection);
Vehicle::setConnection(new Connection($dsn, $username, $password));

使用 ConnectionProvider

如果您想仅在需要时创建连接,则最好使用 ConnectionProvider。您可以编写自己的提供程序或使用默认的一个

Base::setConnectionProvider(new Dsn($dsn, $username, $password));

查找器

以下查找器已实现,因为到目前为止需要这些。

Vehicle::find($id);
Vehicle::findAll($criteria, $orderBy, $limit, $offset, $select);
Vehicle::findFirst($criteria, $orderBy);
Vehicle::findFirstByAttribute($attribute, $value);
Vehicle::findAllBySql($query);

条件

条件可以是包含 SQL 表达式的字符串或仅包含属性及其值的关联数组。

User::findAll(['name' => 'Phil', 'male' => Sex::male, 'diabled' => false]);
User::findAll("`name` = 'Phil' AND `sex` = 'male' AND `disabled` = 0");

当使用关联数组时,默认情况下使用 AND 操作符构建带引号的 SQL 表达式。对于数组值,使用 IN 操作符,对于空值使用 IS。

User::findAll(['name' => 'Phil', 'male' => [Sex::male, Sex::female], 'diabled' => null]);
User::findAll("`name` = 'Phil' AND `sex` IN ('male', 'female') AND `disabled` IS NULL");

属性表达式

要获得其他比较,您可以使用如下所示的 AttributeExpression

use \Gforces\ActiveRecord\PropertyExpression;
User::findAll([
    'name' => PropertyExpression::eq('Phil'), // `name` = 'Phil'  
    'male' => PropertyExpression::ne([Sex::male, Sex::female]), // NOT IN 
    'diabled' => PropertyExpression::ne(null), // IS NOT NULL
    'age' => PropertyExpression::gt(21), // `age` > 21
    'weight' => PropertyExpression::le(50), // `weight` <= 50
    'verified' => PropertyExpression::ge(new DateTime('-2 week')),
]);

您也可以使用更短的语法

use function \Gforces\ActiveRecord\PropertyExpressions\eq;
use function \Gforces\ActiveRecord\PropertyExpressions\ne;
use function \Gforces\ActiveRecord\PropertyExpressions\gt;
use function \Gforces\ActiveRecord\PropertyExpressions\le;
use function \Gforces\ActiveRecord\PropertyExpressions\ge;

User::findAll([
    'name' => eq('Phil'), // `name` = 'Phil'  
    'male' => ne([Sex::male, Sex::female]), // NOT IN 
    'diabled' => ne(null), // IS NOT NULL
    'age' => ge(21), // `age` >= 21
    'weight' => le(50), // `weight` <= 50
    'verified' => gt(new DateTime('-2 week')),
]);

SQL 表达式

您可以组合关联值和自定义 SQL 表达式

use Gforces\ActiveRecord\Expression;
use Gforces\ActiveRecord\Expressions\Simple;
use function \Gforces\ActiveRecord\PropertyExpressions\gt;
use function \Gforces\ActiveRecord\PropertyExpressions\le;

User::findAll([
    'name' => 'Phil', 
    Expression::or([
        'age' => gt(21),
        'weight' => le(50),
    ]),
    '1 = 1',
]);

isNew 属性

这是一个内置属性,用于确定对象是否存储在数据库中。

访问修改后的属性

每个模型上设置了一个特殊属性 $keepAttributeChanges,它决定对象是否应该保留原始值。出于性能考虑,此功能默认禁用。如果启用,每个对象都可以访问从数据库加载的原始值。此外,它还优化了只包含更改值的 UPDATE 查询,如果没有任何值更改,则不会执行任何操作。

use Gforces\ActiveRecord\Base;

class Vehicle extends Base 
{
    protected static bool $keepAttributeChanges = true;
    
    #[Column]
    public string $make;
    
    public function isMakeChanged(): bool
    {
        return $this->isAttributeChanged('make');
    }
}

$vehicle = Vehicle::find($id);
$vehicle->save(); // UPDATE query is not executed
$vehicle->isMakeChanged(); // false
$vehicle->make = 'VW';
$vehicle->isMakeChanged(); // true
$vehicle->save() // UPDATE query executed

静态方法

您可以在多个静态方法中使用关联数组语法,与 Criteria 相同,用于您的模型。

use function \Gforces\ActiveRecord\PropertyExpressions\ge;

Product::insert(['name' => 'Bill', 'age' => 21]);
Product::updateAll(['adult' => true], criteria: ['age' => ge(18)]);
Product::deleteAll(['age' => ge(18)]);
Product::count(['age' => 21]);
Product::exists(['name' => 'Bill', 'adult' => true]);

已知限制

  • 主键尚未完全实现。在有些关系型中,仍然需要 'id' 列。
  • 并非所有关系都完全实现。
  • 仅实现了一些示例验证器。
  • 没有文档,但代码具有自解释性。

被使用于