表对象

class Cake\ORM\Table

表对象提供对存储在特定表中的实体集合的访问。 您的应用程序中的每个表都应有一个关联的表类,用于与给定表进行交互。 如果您不需要自定义给定表的行为,CakePHP 会为您生成一个表实例以供使用。

在尝试使用表对象和 ORM 之前,您应该确保已配置您的 数据库连接.

基本用法

要开始,请创建一个表类。 这些类位于 **src/Model/Table** 中。 表是一种特定于关系数据库的模型集合,是 CakePHP ORM 中与数据库交互的主要接口。 最基本的表类如下所示

// src/Model/Table/ArticlesTable.php
namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
}

请注意,我们没有告诉 ORM 为我们的类使用哪个表。 按照惯例,表对象将使用与类名的小写和下划线版本匹配的表。 在上面的示例中,将使用 articles 表。 如果我们的表类名为 BlogPosts,您的表应名为 blog_posts。 您可以使用 setTable() 方法指定要使用的表

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setTable('my_table');
    }
}

指定表时不会应用任何词形变化规则。 按照惯例,ORM 还期望每个表都具有名为 id 的主键。 如果您需要修改它,可以使用 setPrimaryKey() 方法

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setPrimaryKey('my_id');
    }
}

自定义表使用的实体类

默认情况下,表对象使用基于命名约定创建的实体类。 例如,如果您的表类名为 ArticlesTable,则实体将是 Article。 如果表类是 PurchaseOrdersTable,则实体将是 PurchaseOrder。 但是,如果您想使用不遵循约定的实体,可以使用 setEntityClass() 方法进行更改

class PurchaseOrdersTable extends Table
{
    public function initialize(array $config): void
    {
        $this->setEntityClass('App\Model\Entity\PO');
    }
}

如以上示例所示,表对象有一个 initialize() 方法,该方法在构造函数结束时调用。 建议您使用此方法进行初始化逻辑,而不是重写构造函数。

获取表类的实例

在您可以查询表之前,您需要获取表的实例。 您可以使用 TableLocator 类来完成此操作

// In a controller

$articles = $this->fetchTable('Articles');

TableLocator 提供构建表的各种依赖项,并维护所有已构建的表实例的注册表,从而更轻松地构建关系和配置 ORM。 有关更多信息,请参见 使用 TableLocator.

如果您的表类位于插件中,请确保使用表类的正确名称。 否则会导致验证规则或回调没有被触发,因为使用的是默认类而不是您的实际类。 要正确加载插件表类,请使用以下方法

// Plugin table
$articlesTable = $this->fetchTable('PluginName.Articles');

// Vendor prefixed plugin table
$articlesTable = $this->fetchTable('VendorName/PluginName.Articles');

生命周期回调

如您所见,表对象触发了许多事件。 如果您想在不子类化或覆盖方法的情况下钩入 ORM 并添加逻辑,事件很有用。 事件监听器可以在表类或行为类中定义。 您还可以使用表的事件管理器绑定监听器。

使用回调方法时,在 initialize() 方法中附加的行为将先于表回调方法触发其监听器。 这与控制器和组件的顺序相同。

要将事件监听器添加到表类或行为中,只需按照下面描述的实现方法签名即可。 有关如何使用事件子系统的更多详细信息,请参见 事件系统

// In a controller
$articles->save($article, ['customVariable1' => 'yourValue1']);

// In ArticlesTable.php
public function afterSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
    $customVariable = $options['customVariable1'];  // 'yourValue1'
    $options['customVariable2'] = 'yourValue2';
}

public function afterSaveCommit(Event $event, EntityInterface $entity, ArrayObject $options)
{
    $customVariable = $options['customVariable1'];  // 'yourValue1'
    $customVariable = $options['customVariable2'];  // 'yourValue2'
}

事件列表

  • Model.initialize

  • Model.beforeMarshal

  • Model.afterMarshal

  • Model.beforeFind

  • Model.buildValidator

  • Model.buildRules

  • Model.beforeRules

  • Model.afterRules

  • Model.beforeSave

  • Model.afterSave

  • Model.afterSaveCommit

  • Model.beforeDelete

  • Model.afterDelete

  • Model.afterDeleteCommit

初始化

Cake\ORM\Table::initialize(EventInterface $event, ArrayObject $data, ArrayObject $options)

Model.initialize 事件在构造函数和初始化方法被调用后触发。 Table 类默认情况下不会监听此事件,而是使用 initialize 钩子方法。

要响应 Model.initialize 事件,您可以创建一个实现 EventListenerInterface 的监听器类

use Cake\Event\EventListenerInterface;
class ModelInitializeListener implements EventListenerInterface
{
    public function implementedEvents()
    {
        return [
            'Model.initialize' => 'initializeEvent',
        ];
    }

    public function initializeEvent($event): void
    {
        $table = $event->getSubject();
        // do something here
    }
}

并将监听器附加到下面的 EventManager

use Cake\Event\EventManager;
$listener = new ModelInitializeListener();
EventManager::instance()->attach($listener);

这将在构造任何 Table 类时调用 initializeEvent

beforeMarshal

Cake\ORM\Table::beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options)

Model.beforeMarshal 事件在请求数据被转换为实体之前触发。 有关更多信息,请参见 在构建实体之前修改请求数据 文档。

afterMarshal

Cake\ORM\Table::afterMarshal(EventInterface $event, EntityInterface $entity, ArrayObject $data, ArrayObject $options)

Model.afterMarshal 事件在请求数据被转换为实体后触发。 事件处理程序将获得转换后的实体、原始请求数据以及提供给 patchEntity()newEntity() 调用的选项。

beforeFind

Cake\ORM\Table::beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, boolean $primary)

在每次查找操作之前,都会触发Model.beforeFind 事件。通过停止事件并为查询提供自定义结果集,您可以完全绕过查找操作。

public function beforeFind(EventInterface $event, SelectQuery $query, ArrayObject $options, $primary)
{
    if (/* ... */) {
        $event->stopPropagation();
        $query->setResult(new \Cake\Datasource\ResultSetDecorator([]));

        return;
    }
    // ...
}

在这个例子中,不会在相关表或其附加的行为上触发任何进一步的beforeFind 事件(尽管行为事件通常会在其默认优先级之前被调用),并且查询将返回通过SelectQuery::setResult() 传递的空结果集。

$query 实例进行的任何更改都将保留在查找的剩余时间内。 $primary 参数指示这是否是根查询,还是关联查询。所有参与查询的关联都会触发一个Model.beforeFind 事件。对于使用联接的关联,将提供一个虚拟查询。在您的事件监听器中,您可以设置额外的字段、条件、联接或结果格式化程序。这些选项/功能将复制到根查询中。

在早期版本的 CakePHP 中,有一个afterFind 回调,现在已被使用 Map/Reduce 修改结果 功能和实体构造函数取代。

buildValidator

Cake\ORM\Table::buildValidator(EventInterface $event, Validator $validator, $name)

当创建$name 验证器时,会触发Model.buildValidator 事件。行为可以使用此钩子添加验证方法。

buildRules

Cake\ORM\Table::buildRules(EventInterface $event, RulesChecker $rules)

在创建规则实例后,以及在调用表的buildRules() 方法后,会触发Model.buildRules 事件。

beforeRules

Cake\ORM\Table::beforeRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $operation)

在对实体应用规则之前,会触发Model.beforeRules 事件。通过停止此事件,您可以停止规则检查并设置应用规则的结果。

afterRules

Cake\ORM\Table::afterRules(EventInterface $event, EntityInterface $entity, ArrayObject $options, $result, $operation)

在对实体应用规则之后,会触发Model.afterRules 事件。通过停止此事件,您可以返回规则检查操作的最终值。

beforeSave

Cake\ORM\Table::beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在保存每个实体之前,都会触发Model.beforeSave 事件。停止此事件将中止保存操作。当事件停止时,将返回事件的结果。

afterSave

Cake\ORM\Table::afterSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在保存实体后,会触发Model.afterSave 事件。

afterSaveCommit

Cake\ORM\Table::afterSaveCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在保存操作所包装的交易提交后,会触发Model.afterSaveCommit 事件。它也会在非原子保存的情况下触发,其中数据库操作隐式提交。此事件仅对直接调用save() 的主表触发。如果在调用 save 之前启动了事务,则不会触发该事件。

beforeDelete

Cake\ORM\Table::beforeDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在删除实体之前,会触发Model.beforeDelete 事件。通过停止此事件,您将中止删除操作。当事件停止时,将返回事件的结果。

afterDelete

Cake\ORM\Table::afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在删除实体后,会触发Model.afterDelete 事件。

afterDeleteCommit

Cake\ORM\Table::afterDeleteCommit(EventInterface $event, EntityInterface $entity, ArrayObject $options)

在删除操作所包装的交易提交后,会触发Model.afterDeleteCommit 事件。它也会在非原子删除的情况下触发,其中数据库操作隐式提交。此事件仅对直接调用delete() 的主表触发。如果在调用 delete 之前启动了事务,则不会触发该事件。

停止表事件

要阻止保存继续,只需在您的回调中停止事件传播

public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
    if (...) {
        $event->stopPropagation();
        $event->setResult(false);

        return;
    }
    ...
}

或者,您可以从回调中返回 false。这与停止事件传播的效果相同。

回调优先级

在表和行为上使用事件时,请注意优先级和附加监听器的顺序。行为事件是在表事件之前附加的。使用默认优先级意味着行为回调将在具有相同名称的表事件**之前**触发。

例如,如果您的表使用TreeBehaviorTreeBehavior::beforeDelete() 方法将在您的表的beforeDelete() 方法之前调用,并且您将无法在表的该方法中使用要删除的记录的子节点。

您可以通过以下几种方式管理事件优先级

  1. 使用priority 选项更改行为的监听器的priority。这将修改行为中**所有**回调方法的优先级

    // In a Table initialize() method
    $this->addBehavior('Tree', [
        // Default value is 10 and listeners are dispatched from the
        // lowest to highest priority.
        'priority' => 2,
    ]);
    
  2. 通过使用Model.implementedEvents() 方法,在您的Table 类中修改priority。这允许您为每个回调函数分配不同的优先级

    // In a Table class.
    public function implementedEvents()
    {
        $events = parent::implementedEvents();
        $events['Model.beforeDelete'] = [
            'callable' => 'beforeDelete',
            'priority' => 3
        ];
    
        return $events;
    }
    

行为

Cake\ORM\Table::addBehavior($name, array $options = [])

行为提供了一种方法来创建与表类相关的水平可重用逻辑片段。您可能想知道为什么行为是常规类而不是特性。主要原因是事件监听器。虽然特性允许可重用逻辑片段,但它们会使绑定事件变得复杂。

要向表格添加行为,您可以调用 addBehavior() 方法。通常,最佳做法是在 initialize() 方法中进行操作。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp');
    }
}

与关联一样,您可以使用 插件语法 并提供额外的配置选项。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public function initialize(array $config): void
    {
        $this->addBehavior('Timestamp', [
            'events' => [
                'Model.beforeSave' => [
                    'created_at' => 'new',
                    'modified_at' => 'always'
                ]
            ]
        ]);
    }
}

您可以在关于 行为 的章节中找到有关行为的更多信息,包括 CakePHP 提供的行为。

配置连接

默认情况下,所有表格实例都使用 default 数据库连接。如果您的应用程序使用多个数据库连接,您需要配置哪些表格使用哪些连接。这就是 defaultConnectionName() 方法的作用。

namespace App\Model\Table;

use Cake\ORM\Table;

class ArticlesTable extends Table
{
    public static function defaultConnectionName(): string {
        return 'replica_db';
    }
}

注意

defaultConnectionName() 方法**必须**是静态的。

使用 TableLocator

class Cake\ORM\TableLocator

如我们之前所见,TableLocator 类提供了一种使用工厂/注册表来访问应用程序表格实例的方法。它还提供了一些其他有用的功能。

配置表格对象

Cake\ORM\TableLocator::get($alias, $config)

从注册表加载表格时,您可以通过提供一个 $options 数组来定制它们的依赖项或使用模拟对象。

$articles = FactoryLocator::get('Table')->get('Articles', [
    'className' => 'App\Custom\ArticlesTable',
    'table' => 'my_articles',
    'connection' => $connectionObject,
    'schema' => $schemaObject,
    'entityClass' => 'Custom\EntityClass',
    'eventManager' => $eventManager,
    'behaviors' => $behaviorRegistry
]);

请注意连接和模式配置设置,它们不是字符串值,而是对象。连接将接受一个 Cake\Database\Connection 对象,而模式将接受一个 Cake\Database\Schema\Collection 对象。

注意

如果您的表格还在其 initialize() 方法中进行额外的配置,这些值将覆盖提供给注册表的值。

您还可以使用 setConfig() 方法预先配置注册表。配置数据按别名存储,并且可以被对象的 initialize() 方法覆盖。

FactoryLocator::get('Table')->setConfig('Users', ['table' => 'my_users']);

注意

您只能在第一次访问该别名之前或期间配置表格。在注册表填充后进行配置将无效。

刷新注册表

Cake\ORM\TableLocator::clear()

在测试用例中,您可能希望刷新注册表。当您使用模拟对象或修改表格的依赖项时,这样做通常很有用。

FactoryLocator::get('Table')->clear();

配置用于定位 ORM 类的命名空间

如果您没有遵循约定,您的表格或实体类可能无法被 CakePHP 检测到。为了解决这个问题,您可以使用 Cake\Core\Configure::write 方法设置命名空间。例如

/src
    /App
        /My
            /Namespace
                /Model
                    /Entity
                    /Table

将通过以下配置进行配置:

Cake\Core\Configure::write('App.namespace', 'App\My\Namespace');