基于 Laravel Eloquent 组件编写 ORM 模型类

ORM 及其实现模式

接下来我们来看看如何实现 MVC 模式中的 M,即模型类。

模型类负责与数据库进行交互,这里的模型指的是数据表的模型,一个模型类对应一张数据表,数据表的字段会映射为模型类的属性,我们可以通过模型类提供的方法实现对应数据表记录的增删改查,这样一来,我们就将原来面向过程的数据库操作转化为面向对象风格的编程,将对数据表的 SQL 执行转化为对模型类的方法调用。

我们把这种对象与数据表的映射称之为对象关系映射(Object Relational Mapping),简称 ORM。ORM 两种最常见的实现方式是 Active Record 和 Data Mapper,Active Record 尤其流行,在很多框架中都能看到它的身影,比如 Laravel 框架使用的 Eloquent ORM 使用的就是 Active Record 模式,而 Symfony 框架使用的 Doctrine ORM 使用的则是 Data Mapper 模式。

两者的主要区别是:

  • 在 Active Record 模式中,模型类与数据表一一对应,一个模型实例对应一行数据表记录,操作模型实例等同于操作表记录;
  • 而在 Data Mapper 模式中,业务领域(Domain)和数据持久层是完全分离的,模型类操作与数据表更新之间通过 EntityManager 来维护,上层操作的模型类完全不需要和数据库有任何关联。

所以 Active Record 模式更加简单,容易上手,但是与数据库的直接关联使其性能和灵活性不及 Data Mapper 模式,但是相对的,Data Mapper 模式理解和实现起来更加复杂,不利于快速上手。

这里,我们选择使用更加简单的 Active Record 模式来实现 ORM 模型类,并且为了简化流程,我们直接基于 Laravel 框架的 Eloquent ORM 组件来编写,就不再重复造轮子了。

下载 Eloquent ORM 相关扩展包

Eloquent ORM 作为 Laravel 框架自带的 ORM 实现,还可以在 Laravel 框架之外作为独立的 ORM 组件使用。在我们这里的博客应用项目中,可以通过 Composer 在根目录下运行如下命令下载对应的 Eloquent ORM 扩展包:

composer require illuminate/database

为了能够正常使用 Eloquent 提供的模型事件功能,还可以下载 Laravel 提供的独立事件扩展包:

composer require illuminate/events

上述扩展包下载完成后,就可以在博客项目根目录下的 vendor 目录中看到对应的扩展包了:

-w501

进入 vendor/illuminate/database,可以通过阅读 README.md 文件查看如何使用独立的 Eloquent ORM 组件,接下来,我们将参照这个文档介绍来编写博客项目的模型类实现。

初始化数据库连接

首先我们在 app/bootstrap.php 中引入 Eloquent ORM 的 Capsule 类完成数据库连接初始化,在此之前,先在配置文件 config/app.php 中调整数据库连接配置符合 Eloquent 约定:

'mysql' => [
    'driver' => 'mysql',
    'host' => '127.0.0.1',
    'port' => 3306,
    'database' => 'blog',
    'username' => 'root',
    'password' => 'root',
    'charset' => 'utf8mb4',
    'collation' => 'utf8mb4_general_ci',
    'prefix'    => '',
]

然后在 bootstrap.php 中新增一个 initDatabase 方法基于 Capsule Manager 初始化数据库连接:

use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Events\Dispatcher;
use Illuminate\Container\Container as IlluminateContainer;

function initDatabase(Container $container)
{
    $capsule = new Capsule();
    $dbConfig = $container->resolve('app.store');
    $capsule->addConnection($dbConfig['drivers'][$dbConfig['default']]);
    $capsule->setEventDispatcher(new Dispatcher(new IlluminateContainer));
    $capsule->setAsGlobal();
    $capsule->bootEloquent();
}

并设置事件分发器,启动 Eloquent 模型类全局可用(为了编写 Eloquent 模型类,如果只是使用 Laravel 提供的数据库查询构建器功能,则不需要这些操作)。最后不要忘了在 bootApp 方法中调用这个方法:

function bootApp(Container $container)
{
    ...
    initDatabase($container);
    return $container;
}

基于 Eloquent 基类编写模型类

在应用启动阶段完成以上初始化操作后,就可以基于 Eloquent ORM 编写模型类了,目前博客项目包含了两张数据表 —— postsalbums

-w973

我们可以分别为其编写模型类 Post

<?php
namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    public $timestamps = false;

    public function album()
    {
        return $this->belongsTo(Album::class);
    }
}

Album

<?php
namespace App\Model;

use Illuminate\Database\Eloquent\Model;

class Album extends Model
{
    public $timestamps = false;
    
    public function posts()
    {
        return $this->hasMany(Post::class);
    }
}

这两个模型类分别存放在 app/model 目录下的 Post.phpAlbum.php 中,它们继承了 Eloquent ORM 的模型类基类 Model,这样就可以使用 Eloquent 模型类支持的属性和方法。

这里,我们通过设置 $timestamps 属性为 false 表示禁用 Eloquent 模型类自动维护时间字段机制。然后在 Post 类中通过 album() 方法定义某个 Post 模型实例归属于 Album 模型实例(通过 album_id 字段),而在 Album 类中通过 posts() 方法定义一个 Album 模型实例可能包含多个 Post 模型实例(一对多关联),这种关联关系与数据表记录的关联关系对应,具体细节可以参考 Eloquent 官方文档,这里不详细展开了。

重构博客项目数据库操作代码

编写好模型类之后,我们来重构博客项目中之前的数据库交互代码,改为通过模型类获取:


class HomeController extends Controller
{
    public function index()
    {
        $albums = Album::all()->toArray();
        ...
    }
}

class AlbumController extends Controller
{
    public function list()
    {
        ...
        $album = Album::with('posts')->findOrFail($id)->toArray();
        $posts = $album['posts'];
        ...
    }
}

class PostController extends Controller
{
    public function show()
    {
        ...
        $post = Post::with('album')->findOrFail($id)->toArray();
        ...
        $album = $post['album'];
        ...
    }
}

这里的模型类方法和关联查询都可以在 Eloquent 官方文档查询到,这里不详细介绍了,需要注意的是,我们之前在视图模板中都是通过关联数组获取数据库查询结果,这里为了避免重构视图层代码,直接在查询结果上调用 toArray 方法将其转化为数组格式。

完成以上重构后,运行 composer dump-auto 更新自动加载文件,让新增命名空间与目录路径映射关系生效,访问博客应用,首页、专辑页、文章页显示正常,表明代码重构成功。

注:本篇教程源码可以在 Github 获取:https://github.com/nonfu/master-laravel-code/tree/v0.9/practice/blog

小结

至此,我们就完成了 MVC 模式在博客应用中的落地,下篇教程,我们将探索如何通过现代工程化的方式管理前端资源和依赖,我们将引入 NPM、Webpack、Laravel Mix、jQuery 和 Bootstrap,并基于这些工具和框架替换博客应用主题。

上一篇: 通过 PHP 原生代码实现视图模板引擎的解析和渲染

下一篇: 引入 Laravel Mix 管理前端资源