跳至主要内容

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();