neat/object

Neat Object 组件

0.11.17 2023-02-20 16:04 UTC

README

Stable Version Build Status codecov

Neat object 组件在 Neat 数据库组件之上添加了一个简单直观的 ORM 层。

入门指南

要安装此包,请在命令行中简单执行 composer

composer require neat/object

然后初始化对象管理器

<?php

// Initialize the manager using a database connection and an object policy
$pdo        = new PDO('mysql:host=localhost;charset=utf8mb4;dbname=test', 'username', 'password');
$connection = new Neat\Database\Connection($pdo);
$policy     = new Neat\Object\Policy();
$manager    = new Neat\Object\Manager($connection, $policy);

// If you want easy access to static methods, set the Manager instance
Neat\Object\Manager::set($manager);

// Or set a factory that connects to the database only when needed
Neat\Object\Manager::setFactory(function () {
    $pdo        = new PDO('dsn', 'username', 'password');
    $connection = new Neat\Database\Connection($pdo);
    $policy     = new Neat\Object\Policy();

    return new Neat\Object\Manager($connection, $policy);
});

创建实体

实体可以是普通的 PHP 对象

class User
{
    /** @var int */
    public $id;

    /** @var string */
    public $name;
}

要将这些实体持久化到数据库中,我们可以使用存储库

$repository = Neat\Object\Manager::get()->repository(User::class);

$user = new User();
$user->name = 'John';

$repository->store($user);

echo $user->id; // 1

按标识符查找

如果您知道实体的标识符,您可以使用 hasget 方法来访问它。

$repository = Neat\Object\Manager::get()->repository(User::class);

// Get the user at once
$user = $repository->get(1); // Returns user with id 1 or null if not found

// Or just check if it exists
if (!$repository->has(1)) {
    throw new Exception('boohoo');
}

要使用复合主键从表中查找实体,应将标识符作为数组传递。

使用查询查找

存储库允许您以多种方式查询实体

  • one 返回一个实体(如果没有匹配则返回 null)
  • all 返回与查询匹配的所有实体作为数组
  • collection 返回包含匹配实体的集合实例
  • iterate 返回一个生成器,允许您遍历匹配的实体
  • select 返回一个可变的查询构建器,允许链式调用上述任何方法
  • sql 返回一个使用手写 SQL 查询(作为字符串提供)的查询对象

这些方法中的每一种都可以以多种方式传递查询

$repository = Neat\Object\Manager::get()->repository(User::class);

// Find one user with name John (note the [key => value] query array)
$user = $repository->one(['name' => 'John']);

// Find all users that have been deleted (the query is an SQL where clause)
$user = $repository->all('deleted = 1');

// Find all users using a complex query
$administrators = $repository
    ->select('u')
    ->innerJoin('user_group', 'ug', 'u.id = ug.user_id')
    ->innerJoin('group', 'g', 'g.id = ug.group_id')
    ->where('g.name = ?', 'administrators')
    ->orderBy('u.name')
    ->all();

// Get one user using your own SQL query
$user = $repository->sql('SELECT * FROM users WHERE id = ?', 1)->one();

// Or multiple in an array
$active = $repository->sql('SELECT * FROM users WHERE deleted = 0')->all();

使用静态访问查找

为了防止在代码中到处都是管理器和存储库实例,您可以使用 Storage 特性以允许静态存储库访问

class User
{
    use Neat\Object\Storage;

    /** @var int */
    public $id;

    /** @var string */
    public $name;
}

// The Storage trait gives you static access to repository methods
$user = User::get(1);
$users = User::all();
$latest = User::select()->orderBy('created_at DESC')->one();
foreach (User::iterate() as $user) {
    $user->greet();
}

关系

如果您需要关系,只需使用 Relations 特性,它提供了一组用于 hasOne/-Many 和 belongsToOne/-Many 关系的工厂函数。

class User
{
    use Neat\Object\Storage;
    use Neat\Object\Relations;

    public function address(): Neat\Object\Relations\One
    {
        return $this->hasOne(Address::class);
    }
}

$user = User::one(...);

// Returns the address object for the user or null
$address = $user->address()->get();

// Relations are automatically stored when the parent model is stored:
$address = new Address();
$user->address()->set($address);
$user->store();
// Stores the user
// Sets the Address::$userId
// Stores the address

当您对同一类有多个关系时,请确保使用第二个参数为每个关系分配一个唯一角色,以避免它们之间的冲突

class Appointment
{
    use Neat\Object\Storage;
    use Neat\Object\Relations;

    public function createdBy(): Neat\Object\Relations\One
    {
        return $this->belongsToOne(User::class, 'creator');
    }

    public function updatedBy(): Neat\Object\Relations\One
    {
        return $this->belongsToOne(User::class, 'updater');
    }
}

引用

用于每个关系的列名和表名使用由 Policy 确定的默认值。当这些默认值不适用时,您可以通过将配置闭包作为第三个参数传递给您选择的任何关系方法来覆盖它们

class AgendaLine
{
    use Neat\Object\Storage;

    /** @var int */
    public $id;

    /** @var int */
    public $appointmentId;

    /** @var string */
    public $description;
}

class User
{
    use Neat\Object\Storage;

    /** @var int */
    public $id;

    /** @var int */
    public $alternativeId;
}

class Appointment
{
    use Neat\Object\Storage;
    use Neat\Object\Relations;

    /** @var int */
    public $id;

    /** @var int */
    public $createdBy;

    public function creator(): Neat\Object\Relations\One
    {
        // Pass reference configuration to belongsToOne as
        // callable(LocalKeyBuilder)
        return $this->belongsToOne(User::class, 'creator', function (Neat\Object\Relations\Reference\LocalKeyBuilder $builder) {
            // Use the local property name
            $builder->setLocalKey('createdBy');

            // Or alternatively, the local column name
            $builder->setLocalKeyColumn('created_by');

            // Set the remote property name
            $builder->setRemoteKey('alternativeId');

            // Or alternatively, the remote column name
            $builder->setRemoteKeyColumn('alternative_id');
        });
    }

    public function agendaLines(): Neat\Object\Relations\Many
    {
        // Pass reference configuration to hasOne and hasMany as
        // callable(RemoteKeyBuilder)
        return $this->hasMany(AgendaLine::class, 'agenda', function (Neat\Object\Relations\Reference\RemoteKeyBuilder $builder) {
            // The same local and remote key setters as with belongsToOne
            // can be used with hasMany and hasOne relations.
        });
    }

    public function attendees(): Neat\Object\Relations\Many
    {
        // Pass reference configuration to belongsToMany as
        // callable(JunctionTableBuilder)
        return $this->belongsToMany(User::class, 'attendees', function (Neat\Object\Relations\Reference\JunctionTableBuilder $builder) {
            // Set the junction table name and column names in addition to
            // the same local and remote key setters as with belongsToOne.
            $builder->setJunctionTable('appointment_attendee');
            $builder->setJunctionTableLocalKeyColumn('appointment_id');
            $builder->setJunctionTableRemoteKeyColumn('attendee_id');
            // Please note that the junction table doesn't have an entity
            // class. Therefore you cannot use class and property names.
        });
    }
}

访问器

访问器方法允许您直接在实体对象上调用 addallhasgetremoveselectset 等方法

class UserAccount
{
    use Neat\Object\Storage;
    use Neat\Object\Relations;

    // Use the Accessors trait to add accessor methods
    use Neat\Object\Accessors;

    public function address(): Neat\Object\Relations\One
    {
        return $this->belongsToOne(Address::class);
    }

    public function roles(): Neat\Object\Relations\Many
    {
        return $this->belongsToMany(Role::class);
    }
}

$user = UserAccount::one(...);

$user->getAddress(); // same as $user->address()->get();
$user->setAddress(...); // same as $user->address()->set(...);

$user->addRole(...); // same as $user->roles()->add(...);
$user->hasRole(...); // same as $user->roles()->has(...);
$user->deleteRole(...); // same as $user->roles()->delete(...);
$user->getRoles(); // same as $user->roles()->get();
$user->selectRoles(); // same as $user->roles()->select();

$user->addRole() 转换为 $user->roles()->add() 是由 Policy 完成的。在其构造函数中,您可以为提供复数化函数,以允许进行适当的转换

// Use the Policy with custom $pluralize function to initialize your Manager
$policy = new Neat\Object\Policy(null, function (string $singular): string {
    return $singular . 's'; // lousy way of pluralizing relation names
});

集合

集合包装了多个项的数组,并提供了一种链式方式来使用多个操作访问这些项。对类多个实例的关系(hasMany 和 belongsToMany)提供了相同的 Collectible API

class User
{
    use Neat\Object\Storage;
    use Neat\Object\Relations;

    public function roles(): Neat\Object\Relations\Many
    {
        return $this->belongsToMany(Role::class);
    }
}

$user = User::one(...);

// Both of these offer the Collectible API
$roles = Role::collection();
$roles = $user->roles();

// Get all roles, the first or the last role
$all = $user->roles()->all();
$first = $user->roles()->first();
$last = $user->roles()->last();

// Count roles
$count = $user->roles()->count();

// Get a filtered collection of roles
$filtered = $user->roles()->filter(function (Role $role) {
    return !$role->invisible;
});

// Get a sorted collection of roles
$sorted = $user->roles()->sort(function (Role $a, Role $b) {
    return $a->name <=> $b->name;
});

// Map roles and get the results in a collection
$names = $user->roles()->map(function (Role $role) {
    return $role->name;
});

// Or get the values of a single property in a collection
$names = $user->roles()->column('name');

// Chain multiple collection functions, then get an array of roles
$result = $user->roles()->filter(...)->sort(...)->all();