表对象提供对存储在特定表中的实体集合的访问。 您的应用程序中的每个表都应有一个关联的表类,用于与给定表进行交互。 如果您不需要自定义给定表的行为,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
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
。
Model.beforeMarshal
事件在请求数据被转换为实体之前触发。 有关更多信息,请参见 在构建实体之前修改请求数据 文档。
Model.afterMarshal
事件在请求数据被转换为实体后触发。 事件处理程序将获得转换后的实体、原始请求数据以及提供给 patchEntity()
或 newEntity()
调用的选项。
在每次查找操作之前,都会触发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 修改结果 功能和实体构造函数取代。
当创建$name
验证器时,会触发Model.buildValidator
事件。行为可以使用此钩子添加验证方法。
在创建规则实例后,以及在调用表的buildRules()
方法后,会触发Model.buildRules
事件。
在对实体应用规则之前,会触发Model.beforeRules
事件。通过停止此事件,您可以停止规则检查并设置应用规则的结果。
在对实体应用规则之后,会触发Model.afterRules
事件。通过停止此事件,您可以返回规则检查操作的最终值。
在保存每个实体之前,都会触发Model.beforeSave
事件。停止此事件将中止保存操作。当事件停止时,将返回事件的结果。
在保存实体后,会触发Model.afterSave
事件。
在保存操作所包装的交易提交后,会触发Model.afterSaveCommit
事件。它也会在非原子保存的情况下触发,其中数据库操作隐式提交。此事件仅对直接调用save()
的主表触发。如果在调用 save 之前启动了事务,则不会触发该事件。
在删除实体之前,会触发Model.beforeDelete
事件。通过停止此事件,您将中止删除操作。当事件停止时,将返回事件的结果。
在删除实体后,会触发Model.afterDelete
事件。
在删除操作所包装的交易提交后,会触发Model.afterDeleteCommit
事件。它也会在非原子删除的情况下触发,其中数据库操作隐式提交。此事件仅对直接调用delete()
的主表触发。如果在调用 delete 之前启动了事务,则不会触发该事件。
要阻止保存继续,只需在您的回调中停止事件传播
public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options)
{
if (...) {
$event->stopPropagation();
$event->setResult(false);
return;
}
...
}
或者,您可以从回调中返回 false。这与停止事件传播的效果相同。
在表和行为上使用事件时,请注意优先级和附加监听器的顺序。行为事件是在表事件之前附加的。使用默认优先级意味着行为回调将在具有相同名称的表事件**之前**触发。
例如,如果您的表使用TreeBehavior
,TreeBehavior::beforeDelete()
方法将在您的表的beforeDelete()
方法之前调用,并且您将无法在表的该方法中使用要删除的记录的子节点。
您可以通过以下几种方式管理事件优先级
使用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,
]);
通过使用Model.implementedEvents()
方法,在您的Table
类中修改priority
。这允许您为每个回调函数分配不同的优先级
// In a Table class.
public function implementedEvents()
{
$events = parent::implementedEvents();
$events['Model.beforeDelete'] = [
'callable' => 'beforeDelete',
'priority' => 3
];
return $events;
}
行为提供了一种方法来创建与表类相关的水平可重用逻辑片段。您可能想知道为什么行为是常规类而不是特性。主要原因是事件监听器。虽然特性允许可重用逻辑片段,但它们会使绑定事件变得复杂。
要向表格添加行为,您可以调用 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 类提供了一种使用工厂/注册表来访问应用程序表格实例的方法。它还提供了一些其他有用的功能。
从注册表加载表格时,您可以通过提供一个 $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']);
注意
您只能在第一次访问该别名之前或期间配置表格。在注册表填充后进行配置将无效。
在测试用例中,您可能希望刷新注册表。当您使用模拟对象或修改表格的依赖项时,这样做通常很有用。
FactoryLocator::get('Table')->clear();
如果您没有遵循约定,您的表格或实体类可能无法被 CakePHP 检测到。为了解决这个问题,您可以使用 Cake\Core\Configure::write
方法设置命名空间。例如
/src
/App
/My
/Namespace
/Model
/Entity
/Table
将通过以下配置进行配置:
Cake\Core\Configure::write('App.namespace', 'App\My\Namespace');