ORM
Pagekit 的对象关系映射器(ORM)帮助你创建应用数据的模型类,每个属性自动映射到相应的表列。你也可以定义你的实体与Pagekit中现有实体(即用户)之间的关系
设置
创建表格
运行以下作,即在你的扩展的钩子里。关于创建表的更通用信息,可以参考数据库章节。installscripts.php
示例:
$util = $app['db']->getUtility();
if ($util->tableExists('@forum_topics') === false) {
$util->createTable('@forum_topics', function ($table) {
$table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
$table->addColumn('user_id', 'integer', ['unsigned' => true, 'length' => 10, 'default' => 0]);
$table->addColumn('title', 'string', ['length' => 255, 'default' => '']);
$table->addColumn('date', 'datetime');
$table->addColumn('modified', 'datetime', ['notnull' => false]);
$table->addColumn('content', 'text');
$table->addIndex(['user_id'], 'FORUM_TOPIC_USER_ID');
$table->setPrimaryKey(['id']);
});
}
定义一个模型类
示例:
<?php
namespace Pagekit\Forum\Model;
use Pagekit\Database\ORM\ModelTrait;
/**
* @Entity(tableClass="@forum_topics")
*/
class Topic
{
use ModelTrait;
/** @Column(type="integer") @Id */
public $id;
/** @Column */
public $title = '';
/** @Column(type="datetime") */
public $date;
/** @Column(type="text") */
public $content = '';
/** @Column(type="integer") */
public $user_id;
/**
* @BelongsTo(targetEntity="Pagekit\User\Model\User", keyFrom="user_id")
*/
public $user;
}
模型是一个普通的PHP类,使用特征。特征允许将某些行为纳入类中——类似于简单的类继承。主要区别在于一个职业可以使用多个特质,而只能继承一个职业。Pagekit\Database\ORM\ModelTrait
如果你不熟悉特质,可以快速看看官方PHP关于特质的文档。
注释将模型绑定到数据库表(自动替换为你安装的数据库前缀):@Entity(tableClass="@my_table")pk_my_table@
注释只有在多行评论开头加两个星号时才有效,而不是只有一个。
// will NOT work:
/* @Column */
// will work:
/** @Column */
// will work:
/**
* @Column
*/
在类中定义属性时,你可以把该变量绑定到表列,把注释放在属性定义上方。你可以使用Doctrine DBAL支持的任何类型。/** @Column(type="string") */
你在模型类中引用的类也必须存在于数据库中。
关系
你在数据库模型中表示的应用数据在实例之间存在某些关系。一篇博客文章有多个相关的评论,且它只属于一个用户实例。Pagekit ORM 提供了定义这些关系的机制,并以程序化方式查询它们。
属于关系
跨不同关系类型使用的基本注释是模型属性上方的注释。在下面的示例中(取自博客模型),我们指定了一个属性,定义为指向 Pagekit 模型实例。@BelongsToPost$userUser
参数指定使用哪个源属性指向用户ID。注意我们还需要定义相应的属性,以便通过查询解决该关系。keyFromuser_id
示例:
/** @Column(type="integer") */
public $user_id;
/**
* @BelongsTo(targetEntity="Pagekit\User\Model\User", keyFrom="user_id")
*/
public $user;
一对多关系
在此关系中,单个模型实例引用任意 另一个模型的实例数量。一个经典的例子是 ,它包含任意数量属于它的实例。反过来 旁边,一条评论恰好属于一个。PostCommentPost
博客包中的示例,见于 。Pagekit\Blog\Model\Post
/**
* @HasMany(targetEntity="Comment", keyFrom="id", keyTo="post_id")
*/
public $comments;
定义关系的逆元:Pagekit\Blog\Model\Comment
/** @Column(type="integer") */
public $post_id;
/** @BelongsTo(targetEntity="Post", keyFrom="post_id") */
public $post;
查询模型时,可以使用 ORM 类。
use Pagekit\Blog\Post;
// ...
// fetch posts without related comments
$posts = Post::findAll();
var_dump($posts);
输出:
array (size=6)
1 =>
object(Pagekit\Blog\Model\Post)[4513]
public 'id' => int 1
public 'title' => string 'Hello Pagekit' (length=13)
public 'comments' => null
// ...
2 =>
object(Pagekit\Blog\Model\Post)[3893]
public 'id' => int 2
public 'title' => string 'Hello World' (length=11)
public 'comments' => null
// ...
// ...
use Pagekit\Blog\Post;
// ...
// fetch posts including related comments
$posts = Post::query()->related('comments')->get();
var_dump($posts);
输出:
array (size=6)
1 =>
object(Pagekit\Blog\Model\Post)[4512]
public 'id' => int 1
public 'title' => string 'Hello Pagekit' (length=13)
public 'comments' =>
array (size=0)
empty
// ...
2 =>
object(Pagekit\Blog\Model\Post)[3433]
public 'id' => int 2
public 'title' => string 'Hello World' (length=11)
public 'comments' =>
array (size=1)
6 =>
object(Pagekit\Blog\Model\Comment)[4509]
...
// ...
// ...
一一关系
一个非常简单的关系是一对一关系。A可能只分配了一个。虽然你只是把所有关于头像的信息都包含在模型里,但有时把这些信息拆分成不同的模型是合理的。ForumUserAvatarForumUser
要实现一对一关系,可以在每个模型类中使用注释。@BelongsTo
/** @BelongsTo(targetEntity="Avatar", keyFrom="avatar_id", keyTo="id") */
targetEntity:目标模型类别keyFrom:表中的外键,指向相关模型keyTo:相关模型中的主键
示例模型:ForumUser
<?php
namespace Pagekit\Forum\Model;
use Pagekit\Database\ORM\ModelTrait;
/**
* @Entity(tableClass="@forum_user")
*/
class ForumUser
{
use ModelTrait;
/** @Column(type="integer") @Id */
public $id;
/** @Column */
public $name = '';
/** @Column(type="integer") */
public $avatar_id;
/** @BelongsTo(targetEntity="Avatar", keyFrom="avatar_id", keyTo="id") */
public $avatar;
}
示例模型:Avatar
<?php
namespace Pagekit\Forum\Model;
use Pagekit\Database\ORM\ModelTrait;
/**
* @Entity(tableClass="@forum_avatars")
*/
class Avatar
{
use ModelTrait;
/** @Column(type="integer") @Id */
public $id;
/** @Column(type="string") */
public $path;
/** @Column(type="integer") */
public $user_id;
/** @BelongsTo(targetEntity="ForumUser", keyFrom="user_id", keyTo="id") */
public $user;
}
为了确保相关模型包含在查询结果中,从模型类获取实例,并在方法中显式列出关系属性。QueryBuilderrelated()
<?php
use Pagekit\Forum\Model\ForumUser;
use Pagekit\Forum\Model\Avatar;
// ...
// get all users including their related $avatar object
$users = ForumUser::query()->related('avatar')->get();
foreach ($users as $user) {
var_dump($user->avatar->path);
}
// get all avatars including their related $user object
$avatars = Avatar::query()->related('user')->get();
foreach ($avatars as $avatar) {
var_dump($avatar->user);
}
多对多关系
有时,两个模型处于关系中,关系双方可能存在多个实例。例如标签和帖子之间的关系:一篇帖子可以被分配多个标签。同时,一个标签可以分配给多个帖子。
下面列出的另一个例子是讨论论坛中最喜欢的话题。用户可以拥有多个最爱话题。一个主题可以被多个用户收藏。
要实现多对多关系,你需要一个额外的数据库表。该表中的每个条目代表实例之间的连接,反之亦然。在数据库建模中,这被称为连接表。TopicForumUser
示例表(即在 中):scripts.php
$util = $app['db']->getUtility();
// forum user table
if ($util->tableExists('@forum_users') === false) {
$util->createTable('@forum_users', function ($table) {
$table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
$table->addColumn('name', 'string', ['length' => 255, 'default' => '']);
$table->setPrimaryKey(['id']);
});
}
// topics table
if ($util->tableExists('@forum_topics') === false) {
$util->createTable('@forum_topics', function ($table) {
$table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
$table->addColumn('title', 'string', ['length' => 255, 'default' => '']);
$table->addColumn('content', 'text');
$table->setPrimaryKey(['id']);
});
}
// junction table
if ($util->tableExists('@forum_favorites') === false) {
$util->createTable('@forum_favorites', function ($table) {
$table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
$table->addColumn('user_id', 'integer', ['unsigned' => true, 'length' => 10, 'default' => 0]);
$table->addColumn('topic_id', 'integer', ['unsigned' => true, 'length' => 10, 'default' => 0]);
$table->setPrimaryKey(['id']);
});
}
关系本身在每个你希望查询的 Model 类中定义。如果你只想列出某个用户的收藏帖,但不列出所有收藏过该帖子的用户,那么你只能在一个模型中定义关系。然而,在下一个例子中,注释位于两个模型类别中。@ManyToMany
注释具有以下参数。@ManyToMany
| 论点 | 描述 |
|---|---|
targetEntity |
目标模型类别 |
tableThrough |
交汇表名称 |
keyThroughFrom |
“from”方向外键的名称 |
keyThroughTo |
“to”方向外键的名称 |
orderBy |
(可选)按陈述排序 |
示例注释:
/**
* @ManyToMany(targetEntity="ForumUser", tableThrough="@forum_favorites", keyThroughFrom="topic_id", keyThroughTo="forum_user_id")
*/
public $users;
示例模型:Topic
<?php
namespace Pagekit\Forum\Model;
use Pagekit\Database\ORM\ModelTrait;
/**
* @Entity(tableClass="@forum_topics")
*/
class Topic
{
use ModelTrait;
/** @Column(type="integer") @Id */
public $id;
/** @Column */
public $title = '';
/** @Column(type="text") */
public $content = '';
/**
* @ManyToMany(targetEntity="ForumUser", tableThrough="@forum_favorites", keyThroughFrom="topic_id", keyThroughTo="forum_user_id")
*/
public $users;
}
示例模型:ForumUser
<?php
namespace Pagekit\Forum\Model;
use Pagekit\Database\ORM\ModelTrait;
/**
* @Entity(tableClass="@forum_user")
*/
class ForumUser
{
use ModelTrait;
/** @Column(type="integer") @Id */
public $id;
/** @Column */
public $name = '';
/**
* @ManyToMany(targetEntity="Topic", tableThrough="@forum_favorites", keyThroughFrom="forum_user_id", keyThroughTo="topic_id")
*/
public $topics;
}
示例查询:
// resolve many-to-many relation in query
// fetch favorite ropics for given user
$user_id = 1;
$user = ForumUser::query()->where('id = ?', [$user_id])->related('topics')->first();
foreach ($user->topics as $topic) {
//
}
// fetch users that have favorited a given topic
$topic_id = 1;
$topic = Topic::query()->where('id = ?', [$topic_id])->related('users')->first();
foreach ($topic->users as $user) {
// ...
}
ORM 查询
获取一个带有指定ID的模型实例。
$post = Post::find(23)
获取所有模型实例。
$posts = Post::findAll();
通过上述查询,关系不会扩展到包含相关实例。在上述例子中,实例的属性不会被初始化。Post$comments
// related objects are not fetched by default
$post->comments == null;
原因在于性能。默认情况下,所需的子查询不被执行,从而节省了执行时间。所以如果你需要相关对象,可以用 上的 方法明确说明在查询中要解析哪些关系。related()QueryBuilder
所以,要获取实例并包含相关实例,你需要构建一个查询来获取相关对象。PostComment
// fetch all, including related objects
$posts = Post::query()->related('comments')->get();
// fetch single instance, include related objects
$id = 23;
$post = Post::query()->related('comments')->where('id = ?', [$id])->first();
注意 已经被替换为 。这是因为 是模型上定义的方法。然而,在第二个例子中,我们有一个 的实例。find(23)->where('id = ?', [$id])->first()find()Pagekit\Database\ORM\QueryBuilder
关于ORM查询和常规查询的更多细节,请查看数据库查询的文档
创建新的模型实例
你可以通过在新的模型实例上调用该方法来创建并保存新模型。save()
$user = new ForumUser();
$user->name = "bruce";
$user->save();
或者,你也可以直接调用模型类的方法,提供一个现有数据数组来初始化实例。之后再调用,将实例存储到数据库中。create()save()
$user = ForumUser::create(["name" => "peter"]);
$user->save();
修改现有实例
获取一个现有实例,对该对象进行任何更改,然后调用该方法将更改存储到数据库中。save()
$user = ForumUser::find(2);
$user->name = "david";
$user->save();
删除现有实例
获取一个现有的模型实例,并调用该方法将该实例从数据库中移除。delete()
$user = ForumUser::find(2);
$user->delete();