icanboogie/activerecord

ActiveRecord 对象关系映射

v5.0.0 2021-06-01 19:08 UTC

README

Release Code Quality Code Coverage Downloads

连接模型活动记录是数据库访问和管理的基石。它们用于建立数据库连接、管理表及其可能的关联,以及管理这些表的记录。利用面向对象编程,模型和活动记录的实例可以在业务逻辑中继承属性、获取/设置器和行为。

使用查询接口,您无需编写原始SQL,管理表关系,或担心注入。

最后,使用提供者,您可以在一个地方定义所有连接和模型。连接在需要时建立,模型被实例化,因此您可以自由地定义数百个。

安装

composer require icanboogie/activerecord

致谢

查询接口的实现大量灵感来自Ruby On Rails的Active Record查询接口

入门

除非您已使用ICanBoogieicanboogie/bind-activerecord包将其绑定,否则您需要绑定原型方法Model::lazy_get_activerecord_cacheActiveRecord::validate

以下代码应该能解决问题

<?php

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\Validate\ValidateActiveRecord;
use ICanBoogie\ActiveRecord\ActiveRecordCache\RuntimeActiveRecordCache;
use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\Prototype;

Prototype::configure([

    ActiveRecord::class => [

        'validate' => function(ActiveRecord $record) {

            static $validate;

            $validate ??= new ValidateActiveRecord;

            return $validate($record);

        }

    ],

    Model::class => [

        'lazy_get_activerecord_cache' => fn(Model $model) =>
            new RuntimeActiveRecordCache($model),

    ]

]);

建立数据库连接

使用Connection实例创建数据库连接。

以下代码演示了如何将连接到MySQL数据库和SQLite临时数据库

<?php

use ICanBoogie\ActiveRecord\Connection;

# a connection to a MySQL database
$connection = new Connection('mysql:dbname=example', 'username', 'password');

# a connection to a SQLite temporary database stored in memory
$connection = new Connection('sqlite::memory:');

Connection类扩展了PDO。它接受相同的参数,并可以使用驱动程序选项提供自定义选项,以指定表名前缀、指定连接的字符集和校对或其时区。

定义数据库表的前缀

ConnectionAttributes::$table_name_prefix指定连接所有表名的表前缀。因此,如果定义了icybee前缀,则nodes表重命名为icybee_nodes

在查询中用前缀替换{table_name_prefix}占位符

<?php

/* @var $connection \ICanBoogie\ActiveRecord\Connection */

$statement = $connection('SELECT * FROM `{table_name_prefix}nodes` LIMIT 10');

定义要使用的字符集和校对

ConnectionAttributes::$charset_and_collate指定连接的字符集和校对为一个字符串,例如“utf8/general_ci”表示“utf8”字符集和“utf8_general_ci”校对。

在查询中用{charset}{collate}占位符替换

<?php

/* @var $connection \ICanBoogie\ActiveRecord\Connection */

$connection('ALTER TABLE nodes CHARACTER SET "{charset}" COLLATE "{collate}"');

指定时区

ConnectionAttributes::$time_zone指定连接的时区。

模型概述

一个模型是数据库表或一组表的对象表示。模型用于创建、更新、删除和查询记录。模型是Model类的实例,通常实现特定的业务逻辑。

<?php

namespace App\Modules\Nodes;

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\ConnectionCollection;
use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\ActiveRecord\ModelCollection;
use ICanBoogie\ActiveRecord\Schema\Character;
use ICanBoogie\ActiveRecord\Schema\Id;
use ICanBoogie\ActiveRecord\Schema\Integer;
use ICanBoogie\ActiveRecord\Schema\Serial;

/**
 * @extends Model<int, Node>
 */
 #[Model\Record(Node:::class)]
class NodeModel extends Model
{
}

/**
 * @extends ActiveRecord<int>
 */
class Node extends ActiveRecord
{
    #[Id, Serial]
    public int $id;

    #[Character(80)]
    public string $title;

    #[Integer]
    public int $number;

    // …
}

$config = (new ActiveRecord\ConfigBuilder())
    ->use_attributes()
    ->add_connection(/*...*/)
    ->add_record(NodeModel::class)
    ->build();

/* @var $connections ConnectionCollection */

$models = new ModelCollection($connections, $config->models);
$models->install();

$node_model = $models->model_for_record(Node::class);

$node = new Node($node_model);
//               ^^^^^^^^^^^
// because we don't use a model provider yet, we need to specify the model to the active record

$node->title = "My first node";
$node->number = 123;
$id = $node->save();
# or
$id = $node->id;

echo "Saved node, got id: $id\n";

定义表名

$name参数指定表名。如果连接定义了表前缀,则使用它作为表名的限定符。表的nameunprefixed_name属性返回表的前缀名称和原始名称。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

echo "table name: {$model->name}, original: {$model->unprefixed_name}.";

在查询中,{self}占位符会被name属性替换

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$stmt = $model('SELECT * FROM `{self}` LIMIT 10');

定义模型的模式

指定模式时,建议使用ActiveRecord类上的属性

或者,您也可以使用$schema_builder手动构建模式

<?php

/* @var \ICanBoogie\ActiveRecord\SchemaBuilder $schema */

$schema
    ->add_serial('id', primary: true)
    ->add_character('title', 80)
    ->add_integer('number')

创建与模型关联的表

一旦定义了模型,就可以使用install()方法轻松地创建其关联的表。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->install();

is_installed()方法检查模型是否已经被安装。

注意:此方法仅检查相应的表是否存在,而不检查其模式是否正确。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

if ($model->is_installed()) {
    echo "The model is already installed.";
}

占位符

以下占位符在模型查询中被替换

  • {alias}:表的别名。
  • {prefix}:连接的表名前缀。
  • {primary}:表的主键。
  • {self}:表名。
  • {self_and_related}:表名和可能的JOIN子句的转义名。

定义模型之间的关系

扩展另一个模型

模型可以扩展另一个,就像PHP中的类可以扩展另一个一样。字段被继承,父类的主键用于记录之间的链接。当查询模型时,表会被连接。当插入或更新值时,它们会被分割以更新各种表。此外,父模型的连接也被继承。

<?php

namespace App;

use ICanBoogie\ActiveRecord\ConfigBuilder;
use ICanBoogie\ActiveRecord\SchemaBuilder;
use ICanBoogie\DateTime;

/* @var ConfigBuilder $config */

$config
    ->add_record(
        record_class: Node::class,
        schema_builder: fn(SchemaBuilder $b) => $b
            ->add_serial('nid', primary: true)
            ->add_character('title'),
    )
    ->add_record(
        record_class: Article::class,
        schema_builder: fn(SchemaBuilder $b) => $b
            ->add_character('body')
            ->add_date('date'),
    );

// …

$article = Article::from([
    'title' => "My Article",
    'body' => "Testing",
    'date' => DateTime::now()
]);

$article->save();

与表不同,如果模型扩展了另一个模型,则不需要定义模式,但它们可能以不同的父类结束。

在以下示例中,news的父nodes,但其父模型contents。这是因为news没有定义模式,因此继承了父模型的模式和某些属性。

<?php

/* @var $news \ICanBoogie\ActiveRecord\Model */

echo $news->parent::class;       // NodeModel
echo $news->parent_model::class; // ContentModel

一对一关系(属于)

模型中的记录可以属于其他模型的记录。例如,一篇属于用户的新闻文章。关系通过BELONGS_TO属性指定。当指定了“属于”关系时,会自动向记录的原型中添加一个getter。例如,如果news模型的记录属于users模型的记录,那么将向news模型的记录原型中添加get_user getter。然后可以使用魔术属性user获取新闻记录的用户。

<?php

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\ActiveRecord\Schema\Id;
use ICanBoogie\ActiveRecord\Schema\Serial;
use ICanBoogie\ActiveRecord\Schema\BelongsTo;
use ICanBoogie\ActiveRecord\Schema\Character;
use ICanBoogie\ActiveRecord\ModelCollection;
use ICanBoogie\ActiveRecord\SchemaBuilder;

class Article extends ActiveRecord
{
    #[Id, Serial]
    public int $id;

    #[BelongsTo(User::class)]
    public int $uid;
}

class User extends ActiveRecord
{
    #[Id, Serial]
    public int $uid;

    #[Character]
    public string $name;
}

// …

/* @var $news Model */

$record = $news->query()->one;

echo "{$record->title} belongs to {$record->user->name}.";

一对多关系(拥有多个)

可以在两个模型之间建立一对多关系。例如,一篇文章可能有多个评论。关系通过HasMany属性指定。当访问模型的活动记录类时,会添加一个getter,并返回一个Query实例。

以下示例演示了如何在创建模型时在“articles”和“comments”模型之间建立一对多关系

<?php

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\ActiveRecord\Schema\Id;
use ICanBoogie\ActiveRecord\Schema\Serial;
use ICanBoogie\ActiveRecord\Schema\BelongsTo;
use ICanBoogie\ActiveRecord\Schema\Character;
use ICanBoogie\ActiveRecord\Schema\HasMany;
use ICanBoogie\ActiveRecord\ModelCollection;
use ICanBoogie\ActiveRecord\SchemaBuilder;

class Article extends ActiveRecord
{
    #[Id, Serial]
    public int $id;

    #[BelongsTo(User::class)]
    public int $uid;
}

#[HasMany(Article::class)]
class User extends ActiveRecord
{
    #[Id, Serial]
    public int $uid;

    #[Character]
    public string $name;
}

// …

/* @var $user User */

foreach ($user->articles as $article) {
    echo "User {$user->name} has article {$article->id}.";
}

活动记录

活动记录是数据库中记录的对象表示。通常,表列是其公共属性,并且类实现getter/setter和业务逻辑方法并不罕见。

如果实例创建时未指定管理记录的模型,则在需要时将使用StaticModelResolver来解析模型。

<?php

namespace App;

use ICanBoogie\ActiveRecord;

class Node extends ActiveRecord
{
    // …

    protected function get_next()
    {
        return $this->model->own->visible->where('date > ?', $this->date)->order('date')->one;
    }

    protected function get_previous()
    {
        return $this->model->own->visible->where('date < ?', $this->date)->order('date DESC')->one;
    }

    // …
}

实例化活动记录

活动记录与任何其他对象一样实例化,但通常更倾向于使用更简短的表示法from()方法。

<?php

$record = Article::from([

    'title' => "An example",
    'body' => "My first article",
    'language' => "en",
    'is_online' => true

]);

验证活动记录

validate()方法用于验证活动记录,并在验证失败时返回一个ValidationErrors实例,在成功时返回一个空数组。您的活动记录类应实现create_validation_rules()方法以提供验证规则。

以下示例演示了如何实现User活动记录类的create_validation_rules()方法以验证其属性。

<?php

use ICanBoogie\ActiveRecord;

// …

class User extends ActiveRecord
{
    use ActiveRecord\Property\CreatedAtProperty;
    use ActiveRecord\Property\UpdatedAtProperty;

    #[Id, Serial]
    public int $id;
    #[Character]
    public string $username;
    #[Character(unique: true)]
    public string $email;

    // …

    /**
     * @inheritdoc
     */
    public function create_validation_rules()
    {
        return [

            'username' => 'required|max-length:32|unique',
            'email' => 'required|email|unique',
            'created_at' => 'required|datetime',
            'updated_at' => 'required|datetime',

        ];
    }
}

// ...

$user = new User;
$errors = $user->validate();

if ($errors)
{
    // …
}

保存活动记录

活动记录的大多数属性都是持久的。使用save()方法将活动记录保存到数据库中。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$record = $model->find(10);
$record->is_online = false;
$record->save();

在保存记录之前,使用validate()方法进行验证,如果验证失败,则抛出一个带有验证错误的RecordNotValid异常。

<?php

use ICanBoogie\ActiveRecord\RecordNotValid;

try
{
    $record->save();
}
catch (RecordNotValid $e)
{
    $errors = $e->errors;

    // …
}

可以使用SAVE_SKIP_VALIDATION选项跳过验证,但结果可能不可预测,因此请谨慎使用此选项。

<?php

use ICanBoogie\ActiveRecord;

$record->save([ ActiveRecord::SAVE_SKIP_VALIDATION => true ]);

调用alter_persistent_properties()以修改将发送到模型的所有属性。可以扩展该方法以添加、删除或修改属性,而无需修改实例本身。

删除活动记录

delete()方法用于从数据库中删除活动记录。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$record = $model->find(190);
$record->delete();

日期时间属性

该包包含三个特质属性,特别设计用于处理DateTime实例:DateTimePropertyCreatedAtPropertyUpdatedAtProperty。使用这些属性,您可以确保始终获取一个DateTime实例,无论设置日期和时间时使用什么值类型。

<?php

namespace App;

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\Property\CreatedAtProperty;
use ICanBoogie\ActiveRecord\Property\UpdatedAtProperty;

class Node extends ActiveRecord
{
    public $title;

    use CreatedAtProperty;
    use UpdatedAtProperty;
}

$node = new Node;

echo get_class($node->created_at);   // ICanBoogie\Datetime
echo $node->created_at->is_empty;    // true
$node->created_at = 'now';
echo $node->created_at;              // 2014-02-21T15:00:00+0100

查询接口

查询接口提供了不同的方法来从数据库中检索数据。使用查询接口,您可以找到使用各种方法和条件记录;指定顺序、字段、分组、限制或要连接的表;使用动态或范围过滤器;检查记录的存在或特定记录;执行各种计算。

查询通常从模型开始,在以下示例中,$model变量是对管理节点的模型的引用。

从数据库中检索记录

使用query()where()方法开始构建一个Query。可以创建复杂的查询,而无需编写任何原始SQL。

可以通过各种方式检索记录,特别是使用allonepairsrc魔法属性。用于检索单个记录或一组记录的find()方法是最直接的。

检索记录

使用find()方法检索一个或多个记录。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$article = $model->find(10);

使用主键检索记录集也非常简单。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$articles = $model->find([ 10, 32, 89 ]);

# or

$articles = $model->find(10, 32, 89);

当找不到记录时,会抛出RecordNotFound异常。其records属性可用于了解哪些记录可以找到,哪些找不到。

注意:集合的记录将按照请求的顺序返回,这也适用于RecordNotFound 异常的 records 属性。

记录缓存

使用 find() 查询检索的记录会被缓存,后续调用会重用这些记录。这也适用于多个记录。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$article = $model->find(12); // '12' retrieved from database
$articles = $model->find(11, 12, 13); // '11' and '13' retrieved from database, '12' is reused.

条件

where() 方法指定用于筛选记录的条件。它代表 SQL 语句中的 WHERE 部分。条件可以是字符串、参数列表或数组。

以字符串形式指定的条件

将条件添加到查询可以非常简单,如 $model->where('is_online = 1');。这将返回所有 is_online 字段等于 "1" 的记录。

警告:自己构建作为字符串的条件可能会使您容易受到 SQL 注入攻击。例如,$model->where('is_online = ' . $_GET['online']); 是不安全的。在可能的情况下,始终使用占位符。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where('is_online = ?', $_GET['online']);

当然,您可以使用多个条件

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where('is_online = ? AND is_home_excluded = ?', $_GET['online'], false);

and()where() 的别名,当添加条件时应优先使用。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where('is_online = ?', $_GET['online'])->and('is_home_excluded = ?', false);

以数组(或参数列表)形式指定的条件

条件也可以指定为数组

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'is_online' => $_GET['online'], 'is_home_excluded' => false ]);

子集条件

可以使用数组作为条件值检索属于子集的记录

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'orders_count' => [ 1,3,5 ] ]);

这将生成类似 ... WHERE (orders_count IN (1,3,5)) 的查询。

修饰符

当条件指定为数组时,可以修改比较函数。使用感叹号作为字段名前缀将使用 不等于 操作符。

以下示例演示了如何搜索 order_count 字段不等于 "2" 的记录

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ '!order_count' => 2 ]);
… WHERE `order_count` != 2

这同样适用于子集

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ '!order_count' => [ 1,3,5 ] ]);
… WHERE `order_count` NOT IN(1, 3, 5)

作用域

作用域可以看作是模型定义的过滤器。模型可以定义自己的过滤器,从父类继承过滤器并覆盖它们。例如,可以这样定义 similar_sitesimilar_languagevisible 作用域

<?php

namespace Website\Nodes;

use ICanBoogie\ActiveRecord\Query;

class Model extends \ICanBoogie\ActiveRecord\Model
{
    // …

    protected function scope_similar_site(Query $query, $site_id = null)
    {
        return $query->and('site_id = 0 OR site_id = ?', $site_id !== null ? $site_id : $this->current_site_id);
    }

    protected function scope_similar_language(Query $query, $language = null)
    {
        return $query->and('language = "" OR language = ?', $language !== null ? $language : $this->current_language);
    }

    protected function scope_visible(Query $query, $visible = true)
    {
        return $query->similar_site->similar_language->filter_by_is_online($visible);
    }

    // …
}

现在您可以轻松地检索网站上可见的前十个记录

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->visible->limit(10);

或者检索前十个法语记录

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->similar_language('fr')->limit(10);

排序

order() 方法按特定顺序检索记录。

以下示例演示了如何按创建日期的升序获取记录

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->order('created');

可以指定一个方向

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->order('created ASC');
# or
$query->order('created DESC');

在排序时可以使用多个字段

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->order('created DESC, title');

记录也可以按字段排序

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'nid' => [ 1, 2, 3 ] ])->order('nid', [ 2, 3, 1 ]);
# or
$model->where([ 'nid' => [ 1, 2, 3 ] ])->order('nid', 2, 3, 1);

分组数据

group() 方法指定 GROUP BY 子句。

以下示例演示了如何检索按天分组的记录的第一条记录

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->group('date(created)')->order('created');

可以过滤分组

having() 方法指定 HAVING 子句,它指定了 GROUP BY 子句的条件。

以下示例演示了如何检索过去一个月每天创建的第一条记录

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created');

限制和偏移量

limit() 方法限制要检索的记录数。

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->limit(10); // retrieves the first 10 records

使用两个参数,可以指定偏移量

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->limit(5, 10); // retrieves records from the 6th to the 16th

也可以使用 offset() 方法定义偏移量

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->offset(5); // retrieves records from the 6th to the last
$query->limit(10)->offset(5);

选择特定字段

默认情况下,选择所有字段(SELECT *),记录是模型定义的 [ActiveRecord][] 类的实例。select() 方法仅从结果集中选择子集字段,在这种情况下,结果集的每一行都返回为一个数组,除非定义了获取模式。

以下示例演示了如何获取记录的标识符、创建日期和标题

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->select('nid, created, title');

因为 SELECT 字符串被原样用于构建查询,因此可以使用复杂的 SQL 语句

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->select('nid, created, CONCAT_WS(":", title, language)');

连接表

《join()` 方法指定 JOIN 子句。可以使用原始字符串或模型标识符来指定连接。此方法可以多次使用以创建多个连接。

使用子查询连接表

可以将 Query 实例作为一个子查询来连接。以下选项可用

  • mode:指定连接模式。默认:INNER
  • as:子查询的别名。默认:与查询关联的模型的别名。
  • on:用于条件表达式的列。根据可用的列,该方法尝试在 ONUSING 之间确定最佳解决方案。

以下示例演示了如何获取用户并根据他们自去年以来发布的在线文章数量进行排序。我们使用连接模式 LEFT,以便即使没有发布文章的用户也会被检索。

<?php

/* @var $articles \ICanBoogie\ActiveRecord\Model */
/* @var $users \ICanBoogie\ActiveRecord\Model */

$online_article_count = $articles
    ->where([ 'type' => 'articles', 'created_at' => new DateTime('-1 year') ])
    ->select('user_id, COUNT(node_id) AS online_article_count')
    ->online
    ->group('user_id');

$users = $users
    ->query()
    ->join(query: $online_article_count, on: 'user_id', mode: 'LEFT')
    ->order('online_article_count DESC');

使用模型连接表

可以使用模型或模型标识符指定连接,在这种情况下,将使用该模型与查询关联的模型之间的关系来创建连接。以下选项可用

  • mode:指定连接模式。默认:INNER
  • as:连接模型的别名。默认:连接模型的别名。

使用冒号 ":" 字符区分模型标识符和原始片段。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */
/* @var $contents_model \ICanBoogie\ActiveRecord\Model */

$model->query()->join(with: ContentRecord::class);
$model->query()->join(with: ContentRecord::class, mode: 'LEFT', as: 'cnt');

注意:如果提供了模型标识符,则使用查询模型的模型集合来获取模型。

使用原始字符串连接表

最后,可以使用原始字符串来指定连接,该字符串将原样包含在最终的 SQL 语句中。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->join(expression: 'INNER JOIN `contents` USING(`nid`)');

检索数据

有许多种方法可以检索数据。我们已看到 find() 方法,它可以用来通过标识符检索记录。以下方法或魔术属性与条件一起使用。

通过迭代检索数据

Query 实例是可遍历的,这是检索结果集行最简单的方法。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

foreach ($model->where('is_online = 1') as $node)
{
    // …
}

检索完整结果集

魔术属性 all 将完整结果集作为数组检索。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$array = $model->query()->all;
$array = $model->visible->order('created DESC')->all;

all() 方法使用特定的检索模式检索完整结果集。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$array = $model->query()->all(\PDO::FETCH_ASSOC);
$array = $model->visible->order('created DESC')->all(\PDO::FETCH_ASSOC);

检索单个记录

魔术属性 one 检索单个记录。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$record = $model->query()->one;
$record = $model->visible->order('created DESC')->one;

one() 方法使用特定的检索模式检索单个记录。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$record = $model->query()->one(\PDO::FETCH_ASSOC);
$record = $model->visible->order('created DESC')->one(\PDO::FETCH_ASSOC);

注意:要检索的记录数自动限制为 1。

检索键/值对

魔术属性 pairs 在选择两个列时检索键/值对,第一列是键,第二列是值。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->select('nid, title')->pairs;

结果类似于以下示例

array
  34 => string 'Créer un nuage de mots-clé' (length=28)
  57 => string 'Générer à la volée des miniatures avec mise en cache' (length=56)
  307 => string 'Mes premiers pas de développeur sous Ubuntu 10.04 (Lucid Lynx)' (length=63)
  ...

检索第一行的第一列

魔术属性 rc 检索第一行的第一列。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$title = $model->query()->select('title')->rc;

注意:要检索的记录数自动限制为 1。

定义检索模式

通常由查询接口选择检索模式,但可以使用 mode 指定它。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->select('nid, title')->mode(\PDO::FETCH_NUM);

mode() 方法接受与 PDOStatement::setFetchMode 方法相同的参数。

如前例所示,在用 all()one() 方法获取数据时也可以指定检索模式。

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$array = $model->query()->order('created DESC')->all(\PDO::FETCH_ASSOC);
$record = $model->query()->order('created DESC')->one(\PDO::FETCH_ASSOC);

检查记录是否存在

exists() 方法检查记录是否存在,它查询数据库就像 find() 一样,但找到记录时返回 true,否则返回 false

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->exists(1);

如果接受多个标识符,则该方法返回所有记录都存在时返回 true,所有记录不存在时返回 false,否则返回数组。

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->exists(1, 2, 999);
# or
$query->exists([ 1, 2, 999 ]);

如果存在记录“1”和“2”,但不存在记录“999”,则该方法将返回以下结果。

array
  1 => boolean true
  2 => boolean true
  999 => boolean false

如果至少存在一条符合指定条件的记录,则exists魔法属性为true,否则为false

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'author' => 'Madonna' ])->exists;

计数

count魔法属性表示模型中的记录数量或与查询匹配的记录数量。

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->count;

或在查询中

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'firstname' => 'Ryan' ])->count;

当然,所有查询方法都可以组合使用

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'firstname' => 'Ryan' ])->join(with: Content::class)->and('YEAR(date) = 2011')->count;

count()方法返回一个数组,包含字段每个值的记录数

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->count('is_online');
array
  0 => string '35' (length=2)
  1 => string '145' (length=3)

在这个例子中,有35条在线记录和145条离线记录。

计算

average()minimum()maximum()sum()方法分别用于计算列的平均值、最小值、最大值和总和。

所有计算方法都直接在模型上工作

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->average('price');

和在查询中

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'category' => 'Toys' ])->average('price');

当然,所有查询方法都可以组合使用

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where([ 'category' => 'Toys' ])->join(with: Content::class)->and('YEAR(date) = 2011')->average('price');

一些有用的属性

以下属性可能很有用,尤其是在使用查询接口创建查询字符串用于另一个查询的子查询时

  • conditions:表示为字符串的条件。
  • conditions_args:条件的参数。
  • model:与查询关联的模型。

使用查询作为子查询

以下示例演示了如何将某些分类模型的查询用作子查询,以获取“音乐”类别中的在线文章

<?php

/* @var $taxonomy_terms_nodes \ICanBoogie\ActiveRecord\Model */
/* @var $articles \ICanBoogie\ActiveRecord\Model */

$taxonomy_query = $taxonomy_terms_nodes
    ->where([

        'termslug' => "music",
        'vocabularyslug' => "category",
        'constructor' => "articles"

    ])
    ->join(with: Vocabulary::class)
    ->join(with: VocabularyScope::class)
    ->select('nid');

$matches = $articles
    ->filter_by_is_online(true)
    ->and("nid IN ($taxonomy_query)", $taxonomy_query->conditions_args)
    ->all;

# or

$matches = $articles
    ->filter_by_is_online_and_nid(true, $taxonomy_query)
    ->all;

删除匹配查询的记录

可以使用delete()方法删除匹配查询的记录

<?php

/* @var $nodes \ICanBoogie\ActiveRecord\Model */

$nodes
    ->where([ 'is_deleted' => true, 'uid' => 123 ])
    ->limit(10)
    ->delete();

您可能需要连接表来决定删除哪些记录,在这种情况下,您可能需要在哪些表中定义要删除的记录。以下示例演示了如何删除属于用户123且标记为已删除的节点及其评论

<?php

/* @var $comments \ICanBoogie\ActiveRecord\Model */

$comments
    ->where([ 'is_deleted' => true, 'uid' => 123 ])
    ->join(with: Node::class)
    ->delete('comments, nodes');

当使用join()时,默认使用与查询关联的表。以下示例演示了如何删除缺少内容的节点

<?php

/* @var $nodes \ICanBoogie\ActiveRecord\Model */

$nodes
    ->query()
    ->join(with: Content::class, mode: 'LEFT')
    ->where('content.nid IS NULL')
    ->delete();

查询接口摘要

检索记录

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$record = $model->find(10);

$records = $model->find(10, 15, 19);
# or
$records = $model->find([ 10, 15, 19 ]);

条件

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->where('is_online = ?', true);
$model->where([ 'is_online' => true, 'is_home_excluded' => false ]);
$model->where('site_id = 0 OR site_id = ?', 1)->and('language = "" OR language = ?', "fr");

# Sets

$model->where([ 'order_count' => [ 1, 2, 3 ] ]);
$model->where([ '!order_count' => [ 1, 2, 3 ] ]); # NOT

# Query extensions

$model->query()->visible;
$model->query()->own->visible->ordered;

分组和排序

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->group('date(created)')->order('created');
$model->query()->group('date(created)')->having('created > ?', new DateTime('-1 month'))->order('created');

限制和偏移量

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->limit(10); // first 10 records
$query->limit(5, 10); // 6th to the 16th records

$query->offset(5); // from the 6th to the last
$query->offset(5)->limit(10);

字段选择

<?php

/* @var $query \ICanBoogie\ActiveRecord\Query */

$query->select('nid, created, title');
$query->select('nid, created, CONCAT_WS(":", title, language)');

连接

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->join(query: $subquery, on: 'nid');
$model->query()->join(with: Content::class);
$model->query()->join(expression: 'INNER JOIN contents USING(nid)');

检索数据

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->all;
$model->query()->order('created DESC')->all(PDO::FETCH_ASSOC);
$model->query()->order('created DESC')->mode(PDO::FETCH_ASSOC)->all;
$model->query()->order('created DESC')->one;
$model->query()->select('nid, title')->pairs;
$model->query()->select('title')->rc;

测试对象存在性

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->exists;
$model->query()->exists(1, 2, 3);
$model->query()->exists([ 1, 2, 3 ]);
$model->where('author = ?', 'madonna')->exists;

计算

<?php

/* @var $model \ICanBoogie\ActiveRecord\Model */

$model->query()->count;
$model->query()->count('is_online'); // count is_online = 0 and is_online = 1
$model->query()->average('score');
$model->query()->minimum('age');
$model->query()->maximum('age');
$model->query()->sum('comments_count');

提供自己的查询

默认情况下,查询对象是Query实例,但可以使用QUERY_CLASS属性指定查询的类。这通常用于向查询添加功能。

提供者

提供者用于管理连接和模型。

连接提供者

连接提供者管理数据库连接。

定义连接

在创建ConnectionCollection实例时可以指定连接定义。ConnectionCollection实现了ConnectionProvider接口,建议对接口进行类型检查并使用connection_for_id()方法。

<?php

use ICanBoogie\ActiveRecord\Config\ConnectionDefinition;
use ICanBoogie\ActiveRecord\ConnectionCollection;

$connections = new ConnectionCollection([

    new ConnectionDefinition(id: 'read', dsn: 'sqlite::memory:'),
    new ConnectionDefinition(id: 'write', dsn: 'mysql:dbname=my_database'),

]);

$connection = $connections->connection_for_id('read');

数据库连接按需创建,您可以定义一百个连接,但它们仅在需要时才会建立。

在尝试获取未定义的连接时抛出ConnectionNotDefined异常。

检查已定义的连接

<?php

/* @var $connections \ICanBoogie\ActiveRecord\ConnectionCollection */

if (isset($connections->definitions['one']))
{
    echo "The connection 'one' is defined.\n";
}

建立的连接

可以使用established魔法属性检索已建立的连接数组。该属性是只读的。

<?php

/* @var $connections \ICanBoogie\ActiveRecord\ConnectionCollection */

foreach ($connections->established as $id => $connection)
{
    echo "The connection '$id' is established.\n";
}

可以使用ConnectionCollection实例本身来遍历已建立的连接。

<?php

/* @var $connections \ICanBoogie\ActiveRecord\ConnectionCollection */

foreach ($connections as $id => $connection)
{
    echo "The connection '$id' is established.\n";
}

模型集合

使用模型集合来管理模型,它解析模型属性(如数据库连接)并实例化它们。

定义模型

在创建ModelCollection实例时可以指定模型定义。ModelCollection实现了ModelProvider接口,建议按照该接口类型编写代码,并使用model_for_id()方法从集合中检索模型。

注意:您不需要创建模型使用的Connection实例,可以使用它们的标识符,该标识符将在需要模型时得到解析。

注意:如果没有指定CONNECTION,则使用primary连接。

<?php

use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\ActiveRecord\ModelCollection;

/* @var $connections \ICanBoogie\ActiveRecord\ConnectionCollection */

$models = new ModelCollection($connections, [

    'nodes' => [

        // …
        Model::SCHEMA => [

            'nid' => 'serial',
            'title' => 'varchar'
            // …
        ]
    ],

    'contents' => [

        // …
        Model::EXTENDING => 'nodes'
    ]
]);

$model = $models->model_for_id('nodes');

创建ModelCollection实例后,可以修改或添加模型定义。

<?php

use ICanBoogie\ActiveRecord\Model;

/* @var $models \ICanBoogie\ActiveRecord\ModelCollection */

$models['new'] = [

    // …
    Model::EXTENDING => 'contents'
];

可以在模型实例化之前修改模型定义。如果尝试修改已实例化的模型的定义,将抛出ModelAlreadyInstantiated异常。

获取模型

ModelCollection实例用作数组,以获取Model实例。

<?php

/* @var $models \ICanBoogie\ActiveRecord\ModelCollection */

$nodes = $models['nodes'];

模型按需实例化,因此您可以定义一百个模型,它们只有在需要时以及它们的数据库连接才会被实例化。

在尝试获取未定义的模型时,将抛出ModelNotDefined异常。

已实例化的模型

可以使用instances魔术属性检索已实例化的模型数组。该属性是只读的。

<?php

/* @var $models \ICanBoogie\ActiveRecord\ModelCollection */

foreach ($models->instances as $class => $model)
{
    echo "The model '$class' has been instantiated.\n";
}

安装/卸载模型

可以使用install()uninstall()方法通过单个命令安装和卸载由提供者管理的所有模型。is_installed()方法返回一个键/值对的数组,其中是模型标识符,如果是已安装的模型则为true,否则为false

<?php

/* @var $models \ICanBoogie\ActiveRecord\ModelCollection */

$models->install();
var_dump($models->is_installed()); // [ "NodeModel" => true, "ContentModel" => true ]
$models->uninstall();
var_dump($models->is_installed()); // [ "NodeModel" => false, "ContentModel" => false ]

模型提供者

StaticModelProvider::model_for_record()被活动记录用于在需要时检索其模型,并在连接期间由查询使用。使用ModelProvider返回的模型集合来检索模型。

以下示例演示了如何定义模型提供者的工厂

<?php

use ICanBoogie\ActiveRecord;
use ICanBoogie\ActiveRecord\StaticModelProvider;

/* @var $provider ActiveRecord\ModelProvider */

StaticModelProvider::set(fn() => $provider);

$nodes = StaticModelProvider::model_for_record(Node::class);

注意:工厂只会被调用一次。

记录缓存

默认情况下,每个模型使用一个RuntimeActiveRecordCache实例来缓存其记录。此缓存存储请求期间记录,它是每个HTTP请求都是全新的。通过模型的原型功能使用魔术属性activerecord_cache来获取缓存。

第三方可以通过覆盖lazy_get_activerecord_cache方法或使用prototype配置片段来提供不同的缓存实例。

<?php

use ICanBoogie\ActiveRecord\Model;
use ICanBoogie\Prototype;

Prototype::from('ICanBoogie\ActiveRecord\Model')['lazy_get_activerecord_cache'] = function(Model $model) {

    return new MyActiveRecordCache($model);

};

或使用prototype配置片段

<?php

// config/prototype.php

return [

    'ICanBoogie\ActiveRecord\Model::lazy_get_activerecord_cache' => 'my_activerecord_cache_provider'

];

异常

该软件包定义的异常类实现了ICanBoogie\ActiveRecord\Exception接口,以便它们可以轻松地被识别

<?php

try
{
    // …
}
catch (\ICanBoogie\ActiveRecord\Exception $e)
{
    // an ActiveRecord exception
}
catch (\Exception $e)
{
    // some other exception
}

定义了以下异常:

持续集成

该项目由GitHub actions持续进行测试。

Tests Static Analysis Code Style

行为准则

本项目遵循贡献者行为准则。通过参与本项目及其社区,您被期望遵守此准则。

贡献

有关详细信息,请参阅CONTRIBUTING

许可证

icanboogie/activerecord遵循BSD3-Clause许可证发布。