前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >orm 系列 之 Eloquent演化历程2

orm 系列 之 Eloquent演化历程2

作者头像
zhuanxu
发布于 2018-08-23 04:57:21
发布于 2018-08-23 04:57:21
2.8K00
代码可运行
举报
文章被收录于专栏:进击的程序猿进击的程序猿
运行总次数:0
代码可运行

Eloquent ORM

上篇讲到了数据库Relation的实现,本篇接着讲migrations or database modification logic的功能,此处开始的git是git co aa98553

本文是orm系列的第三篇,也是Eloquent演化的第二篇,Eloquent系列会尝试着讲清楚Eloquent是如何一步一步演化到目前功能强大的版本的,但是毕竟个人能力有限,不可能分析的非常完善,总会有不懂的地方,所以讲的错误的地方,恳请大牛们能不吝赐教;或者如果有什么地方是没看懂的,也请提出来,因为可能那地方就是我自己没看懂,所以没讲明白,你提出后我们就可以一起讨论,让我们能共同的进步的。

数据库蓝图

先开始本文的主题。数据库管理相关的代码都放在Schema目录下, 最开始的结构如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/Illuminate/Database/Schema
├── Blueprint.php
└── Builder.php

就两个文件Blueprint和Builder,Schema/Builder负责提供数据库操作的面向对象似的操作,而Schema/Blueprint则负责存储具体的操作数据,包括数据库操作的命令和数据库表的定义,因此有下面的结构:

接着,我们看看是怎么使用Blueprint的,下看创建table

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
$grammar = new Illuminate\Database\Schema\Grammars\MySqlGrammar;
$blueprint = new Blueprint('users');
$blueprint->create();
$blueprint->increments('id');
$blueprint->string('email');
$statements = $blueprint->toSql($grammar);

此时返回的$statements是一个array,有count($statements)==1,并且$statements[0]为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
create table `users` (`id` int not null auto_increment primary key, `email` varchar(255) not null)

我们现在来看下上述是怎么实现的?

首先构造函数传入表名users,而create则是加了一个命令

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function create()
{
    return $this->addCommand('create');
}

addCommand则是将命令加入进$this->commands中,而下面的increments函数如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function increments($column)
{
  return $this->integer($column, true);
}
public function integer($column, $autoIncrement = false)
{
  return $this->addColumn('integer', $column, compact('autoIncrement'));
}

其最终是调用了addColumn将其加入进$this->columns中,下面看下toSql函数,其核心是下面的部分:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
foreach ($this->commands as $command)
{
  $method = 'compile'.ucfirst($command->name);

  if (method_exists($grammar, $method))
  {
    if ( ! is_null($sql = $grammar->$method($this, $command)))
    {
      $statements = array_merge($statements, (array) $sql);
    }
  }
}

对于每个命令,我们都调用grammar的compileCommand函数,此处我们调用的是compileCreate函数,至此我们就分析完了数据库表操作的方法,下面我们来看migrations功能。

数据库迁移

让我们先看目录结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/Illuminate/Database/Console
└── Migrations
    ├── MigrateCommand.php
    ├── MigrationRepositoryInterface.php
    └── Migrator.php

此处有个新的知识点,也是laravel中一大亮点Artisan,Artisan是 Laravel 自带的命令行接口名称,此处不做具体的介绍了,有机会再细说的,当我们在命令行中执行php artisan command的时候,会去调用migrateCommand,然后最后会调用Migrator中的函数runMigrations函数,看下面分析:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function runMigrations(OuputInterface $output, $package, $path, $pretend = false)
{
  $this->output->writeln('<info>Running migrations at path: '.$path.'</info>');

  // 从文件中获取migrate files
  $files = $this->getMigrationFiles($path);
  // 获取已经执行的migration
  $ran = $this->repository->getRanMigrations($package);
  // 比较不同
  $migrations = array_diff($files, $ran);
 // 执行未执行的migration
  $this->runMigrationList($output, $migrations, $package, $pretend);
}

大版本前夜

看完上面的最基本版本的migrator,我们跨越下直接来看tag v1.1.1版本的eloquent,

git co v1.1.1

此版本是v4.0.0之前的一个版本,从这以后laravel会以组件的形式组织各个功能,让我们分析下v1.1.1的版本,目前具有哪些功能,都是怎么实现的,先看下目录结构:

分别介绍下:

Console和Migrations:这是本篇讲migrations or database modification logic的功能

Eloquent:是前一篇讲的对于Active Record模式中Model的功能,包括了Model、Builder和Relation功能,忘记的可以去看前一篇orm 系列 之 Eloquent演化历程1的内容

Query:包含了最基本的Sql的操作和语法逻辑,类似于自定义了一个DSL语言,提供了面向对象的操作方式

Schema:这也是本篇讲migrations or database modification logic的功能,主要是对数据库表操作sql的建模

此处Connectors是之前没有介绍过的,Connectors是在f917efa中第一次加入的,我们看下到底做了什么,其目录结构是:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/Illuminate/Database/Connectors
├── ConnectionFactory.php
├── Connector.php
├── ConnectorInterface.php
├── MySqlConnector.php
├── PostgresConnector.php
├── SQLiteConnector.php
└── SqlServerConnector.php

看名字我们就知道大致是怎么回事了,ConnectorInterface定义了接口,Connector是基类,ConnectionFactory是工厂模式,负责创建具体的Connectors,再看下Connector文件,里面有个函数叫:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function createConnection($dsn, array $config, array $options)
{
  $username = array_get($config, 'username');

  $password = array_get($config, 'password');

  return new PDO($dsn, $username, $password, $options);
}

其实就是对创建PDO的封装,随着代码复杂度的提高,我们根据SOLID原则(SOLID原则可以看The Clean Architecture in PHP 读书笔记(三)),将创建PDO这部分功能单独抽离出来,变为了Connectors,然后根据SOLID原则,我们再继续看下ConnectionInterfaceConnectionResolverInterface,分别负责的功能可以看下图:

我们可以看到ConnectionInterface负责数据库的操作,ConnectionResolverInterface负责connection的管理,原先这些功能在稍早的版本中都是揉在一起的,还是那个观点:

随着项目复杂度的提升,我们遵循关注点分离的原则,不断去对系统做解耦工作

新增功能

我们接着本篇开头介绍的migrate功能,来看下v1.1.0版本中有的功能,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/Illuminate/Database/Console
├── Migrations
│   ├── BaseCommand.php
│   ├── InstallCommand.php
│   ├── MakeCommand.php
│   ├── MigrateCommand.php
│   ├── RefreshCommand.php
│   ├── ResetCommand.php
│   └── RollbackCommand.php
└── SeedCommand.php

此时Migrations目录下都是支持的命令,而Migrations下面是具体的实现了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
src/Illuminate/Database/Migrations
├── DatabaseMigrationRepository.php
├── Migration.php
├── MigrationCreator.php
├── MigrationRepositoryInterface.php
├── Migrator.php
└── stubs
    ├── blank.php
    ├── create.php
    └── update.php

我们可以看到Eloquent中到处都是接口,接口定义了要满足的契约,彼此之间都过接口交流,最大的进行了解耦。

我们通过一个比较有意思的命令Make来看下migration的实现,make的作用是新建一个migration文件,其会根据命令函数参数,去读取src/Illuminate/Database/Migrations/stubs下面的3个文件中的一个,然后调用下面的函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected function populateStub($name, $stub, $table)
{
  $stub = str_replace('{{class}}', camel_case($name), $stub);

  // Here we will replace the table place-holders with the table specified by
  // the developer. This is useful for quickly creaeting a tables creation
  // or update migration from the console instead of typing it manually
  if ( ! is_null($table))
  {
    $stub = str_replace('{{table}}', $table, $stub);
  }

  return $stub;
}

做一个简单的字符串替换,然后产生文件,然后还有一个比较有意思的是DatabaseMigrationRepository类,其作用是:我们需要记录migration哪些已经做了,哪些还没有做,这些记录方式我们通过DatabaseMigrationRepository来实现,最终是通过将执行记录以log的形式插入到数据库中。

本文最后讲下Eloquent中新增的对象之间的关系:多态关系,以下内容摘自[ Laravel 5.3 文档 ] Eloquent ORM —— 关联关系

表结构

多态关联允许一个模型在单个关联下属于多个不同模型。例如,假设应用用户既可以对文章进行评论也可以对视频进行评论,使用多态关联,你可以在这两种场景下使用单个comments表,首先,让我们看看构建这种关联关系需要的表结构:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
posts
id - integer
title - string
    body - text

videos
  id - integer
  title - string
  url - string

comments
    id - integer
    body - text
    commentable_id - integer
    commentable_type - string

两个重要的需要注意的列是 comments 表上的 commentable_idcommentable_typecommentable_id列对应 PostVideo 的 ID 值,而 commentable_type 列对应所属模型的类名。当访问 commentable 关联时,ORM 根据commentable_type 字段来判断所属模型的类型并返回相应模型实例。

模型结构

接下来,让我们看看构建这种关联关系需要在模型中定义什么:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    /**
     * Get all of the owning commentable models.
     */
    public function commentable()
    {
        return $this->morphTo();
    }
}

class Post extends Model
{
    /**
     * Get all of the post's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

class Video extends Model
{
    /**
     * Get all of the video's comments.
     */
    public function comments()
    {
        return $this->morphMany('App\Comment', 'commentable');
    }
}

以上内容来自[ Laravel 5.3 文档 ] Eloquent ORM —— 关联关系,接下去让我们看下这是怎么实现的?

首先是morphMany的构造函数:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public function __construct(Builder $query, Model $parent, $morphName)
{
    $this->morphType = "{$morphName}_type";

    $this->morphClass = get_class($parent);

    parent::__construct($query, $parent, "{$morphName}_id");
}

然后morphMany其实是扩展是HasOneOrMany,然后再加限制的时候,新增了一个$this->query->where($this->morphType, $this->morphClass);,通过这个限制就可以解决多态关联了,那上面的例子来说,就是多个条件commentable_type=Video,至于HasOneOrMany的分析参考上一篇文章。

以上就是v4.0.0之前的Eloquent的大致功能,目前orm功能已经完善了,数据库迁移功能,Active Record模式的实现,下一步Eloquent的方向是什么呢?让我们跟着git继续追踪吧_

新纪元

为了解决PHP组件管理及散步的问题,2009年的php|tek大会上成立了PHP-FIG组织,目的在于透过三个方式来制定PHP社群在开发组件时的规范,laravel依赖PHP_FIG的建议,将框架组件独立开发并命名为Illuminate,再提供Starter Package让框架使用者可以透过composer建立项目,因此我们从eloquent-v4.0.0开始,我们会开始看项目laravel/framework

在4.*版本的时候,laravel/framework还采用的psr-0规范,所有其目录层级还是src/Illuminate/Database,在这个版本中有个新的目录Capsule,其下面是一个Manager文件,其最初是在f851607中加入的,我们来看下Manager中的内容。

此处为什么会出现Manager,当项目变复杂后,我们很难简单的和Eloquent的内部的组件进行有效的功能了,这个时候需要一个项目的门面,帮助我们和外界沟通,让外界尽可能简单的使用Eloquent,于是就出现了Manager,记得网上搜索过关键字Using Eloquent outside Laravel,我们可以看到一篇Vivek Kumar Bansal写的文章,文章从2方面介绍了怎么使用Eloquent

  • Building Schema
  • Making a Model

在这两个之间,我们需要有一个第一步就是准备环境,我们来看下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use Illuminate\Database\Capsule\Manager as Capsule;

/**
 * Configure the database and boot Eloquent
 */
$capsule = new Capsule;
$capsule->addConnection([
    'driver'    => 'mysql',
    'host'      => 'host',
    'database'  => 'database',
    'username'  => 'username',
    'password'  => 'password',
    'charset'   => 'utf8',
    'collation' => 'utf8_general_ci',
    'prefix'    => 'prefix_'
]);
$capsule->setAsGlobal();
$capsule->bootEloquent();

建立有3个步骤

  1. 添加数据库连接addConnection
  2. 设置实例全局可访问setAsGlobal
  3. 启动Eloquent,bootEloquent

下面具体看下每个步骤做的事情。

第一步addConnection,添加了默认的数据库配置,通过这个配置,我们可以通过DatabaseManager.connection来获取数据库连接connection,从而进行操作。

第二步setAsGlobal,其动作就做了static::$instance = $this,通过将实例设置为静态变量,我们就能在全局通过静态方法来访问Capsule了。

第三步bootEloquent,通过Eloquent::setConnectionResolver($this->manager)设置了Model的静态变量$resolver,从而能够使用Model。

通过上面3步启动完后,我们就能开始下面的工作了,先看第一个Building Schema,即数据库本身的操作,我们给出示例的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use Illuminate\Database\Capsule\Manager as Capsule;
use Illuminate\Database\Schema\Blueprint;

//Creating schema
Capsule::schema()->create('users', function (Blueprint $table) {
    $table->increments('id');
    $table->string('username');
    $table->string('first_name')->nullable();
    $table->string('last_name')->nullable();
    $table->string('email');
    $table->string('password');
    $table->timestamps();
});

//Droping Schema
Capsule::schema()->dropIfExists('users');

其中Capsule::schema()返回的是Schema\Builder实例,然后进行正常的操作操作,此处Blueprint定义了蓝图,数据库表的定义,然后通过Blueprint.build来执行蓝图,从而产生数据库表。

接着我们看第二个Making a Model,使用上非常简单:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//User Model
use Illuminate\Database\Eloquent\Model as Eloquent
class User extends Eloquent {
    //
}

此处Eloquent已经通过初始化设置了静态变量$resolver,我们可以方便的获取连接Connection了,也就是有了数据库操作的功能。

好了,以上就是Capsule/Manager的功能了,相当于Eloquent的门面,负责打点一切,此时我们再画下主要的类图:

上面Capsule是大管家,然后DatabaseManger则是内部统领,管理者两大集团Schema和Query,同时DatabaseManger和Eloquent还处理协作关系,Eloquent负责领域类的打理,最后ConnectionFactory则打点着所有的Connection,提供基础的功能。

总结

从v4.0.0开始,基本Eloquent就已经固定了,到目前最新master分支上,目录结构也没有什么变化了,因此下一篇开始,我们会在最新版的基础上,分析下Eloquent的一些具体的实现,尽情期待。

参考

[ Laravel 5.3 文档 ] Eloquent ORM —— 关联关系

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2016.11.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
react源码解析19.手写迷你版react
react源码解析19.手写迷你版react 视频课程(高效学习):进入课程 课程目录: 1.开篇介绍和面试题 2.react的设计理念 3.react源码架构 4.源码目录结构和调试 5.jsx&核心api 6.legacy和concurrent模式入口函数 7.Fiber架构 8.render阶段 9.diff算法 10.commit阶段 11.生命周期 12.状态更新流程 13.hooks源码 14.手写hooks 15.scheduler&Lane 16.concurrent模式 17.contex
全栈潇晨
2021/06/24
8120
【React进阶-3】从零实现一个React(下)
这篇文章给大家介绍一下我们如何自己去实现一个类似于React这样的框架,当然,我们并不会去实现React里面所有的内容,只是去将React基础性的功能实现一遍,让大家对React的认识更加的深入。因为篇幅有限,所以我将这篇文章拆分成了上、下两部分,此文是下篇。
X北辰北
2022/02/21
7590
【React进阶-3】从零实现一个React(下)
手写简易版 React 来彻底搞懂 fiber 架构
React 16 之前和之后最大的区别就是 16 引入了 fiber,又基于 fiber 实现了 hooks。整天都提 fiber,那 fiber 到底是啥?它和 vdom 是什么关系?
神说要有光zxg
2022/03/03
8510
手写简易版 React 来彻底搞懂 fiber 架构
实现一个小而全的React
我们需要一个可以转换 jsx 的 vanilla js 环境,使用 vite 可以很方便设置好我们的开发环境
玖柒的小窝
2021/10/06
5440
实现一个小而全的React
手写系列-实现一个铂金段位的React
为什么是铂金呢,因为和王者还有很远的距离。本文仅实现简单版本的 React,参考 React 16.8 的基本功能,包括虚拟 DOM、Fiber、Diff 算法、函数式组件、hooks 等。
winty
2021/07/27
9060
从零开始的 React 再造之旅
https://segmentfault.com/a/1190000021689852
ConardLi
2020/02/24
8850
从零开始的 React 再造之旅
怎样徒手写一个React
下面先实现一个最简单的页面渲染,快速了解 JSX、React、DOM 元素的联系。
helloworld1024
2022/10/13
6700
从实现一个React到深度理解React框架核心原理
这篇文章循序渐进地介绍实现以下几个概念,遵循本篇文章基本就能搞懂为啥需要fiber,为啥需要commit和phases、reconciliation阶段等原理。本篇文章又不完全和原文一致,这里会加入我自己的一些思考,比如经过performUnitOfWork处理后fiber tree和element tree的联系等。
夏天的味道123
2022/10/17
6160
React核心技术浅析
这段代码的意思是通过 ReactDOM.render() 方法将 h1 包裹的JSX元素渲染到id为“root”的HTML元素上. 除了在JS中早已熟知的 document.getElementById() 方法外, 这段代码中还包含两个知识点:
夏天的味道123
2022/09/26
1.7K0
手写React的Fiber架构,深入理解其原理
熟悉React的朋友都知道,React支持jsx语法,我们可以直接将HTML代码写到JS中间,然后渲染到页面上,我们写的HTML如果有更新的话,React还有虚拟DOM的对比,只更新变化的部分,而不重新渲染整个页面,大大提高渲染效率。到了16.x,React更是使用了一个被称为Fiber的架构,提升了用户体验,同时还引入了hooks等特性。那隐藏在React背后的原理是怎样的呢,Fiber和hooks又是怎么实现的呢?本文会从jsx入手,手写一个简易版的React,从而深入理解React的原理。
蒋鹏飞
2020/10/15
1.7K1
手写React的Fiber架构,深入理解其原理
react fiber 到底有多细
Fiber 是对 React 核心算法的重构,facebook 团队使用两年多的时间去重构 React 的核心算法,在 React16 以上的版本中引入了 Fiber 架构,极大的提高了大型react项目的性能,也激发了我对其实现的好奇。在研究源码的过程中,能发现很多比较细的点,有任务单元拆分的细,有任务调度、双缓冲、节点复用等优化的细,都非常值得我们学习,接下来就带大家看看react fiber 到底有多细。
有赞coder
2021/05/14
8030
react fiber 到底有多细
【React进阶-2】从零实现一个React(上)
这篇文章给大家介绍一下我们如何自己去实现一个类似于React这样的框架,当然,我们并不会去实现React里面所有的内容,只是去将React基础性的功能实现一遍,让大家对React的认识更加的深入。因为篇幅有限,所以我将这篇文章拆分成了上、下两部分,此文是上篇。
X北辰北
2022/02/21
1.3K0
【React进阶-2】从零实现一个React(上)
这可能是最通俗的 React Fiber 打开方式
作者:荒山 https://juejin.im/post/5dadc6045188255a270a0f85
Nealyang
2019/10/24
2.3K1
这可能是最通俗的 React Fiber 打开方式
读懂React原理之调和与Fiber
Fiber 架构是React16中引入的新概念,目的就是解决大型 React 应用卡顿,React在遍历更新每一个节点的时候都不是用的真实DOM,都是采用虚拟DOM,所以可以理解成fiber就是React的虚拟DOM,更新Fiber的过程叫做调和,每一个fiber都可以作为一个执行单元来处理,所以每一个 fiber 可以根据自身的过期时间expirationTime,来判断是否还有空间时间执行更新,如果没有时间更新,就要把主动权交给浏览器去渲染,做一些动画,重排( reflow ),重绘 repaints 之类的事情,这样就能给用户感觉不是很卡。
xiaofeng123aa
2022/10/03
4650
这可能是最通俗的 React Fiber 打开方式
写一篇关于 React Fiber 的文章, 这个 Flag 立了很久,这也是今年的目标之一。最近的在掘金的文章获得很多关注和鼓励,给了我很多动力,所以下定决心好好把它写出来。我会以最通俗的方式将它讲透, 因此这算是一篇科普式的文章。不管你是使用React、还是Vue,这里面的思想值得学习学习!
前端劝退师
2019/12/02
6220
由浅入深React的Fiber架构
JavaScript是单线程运行的。在浏览器环境中,他需要负责页面的JavaScript解析和执行、绘制、事件处理、静态资源加载和处理。而且只能一个任务一个任务的执行,如果其中某个任务耗时很长,那后面的任务则执行不了,在浏览器端则会呈现卡死的状态。
Careteen
2022/02/14
1.8K0
由浅入深React的Fiber架构
React源码之深度理解diff算法
上一章中 react 的 render 阶段,其中 begin 时会调用 reconcileChildren 函数, reconcileChildren 中做的事情就是 react 知名的 diff 过程,本章会对 diff 算法进行讲解。
goClient1992
2022/12/09
4310
React.createElement和ReactDom.render方法简单思路
实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖,因此,使用 JSX 可以完成的任何事情都可以通过纯 JavaScript 完成。 例如,用 JSX 编写的代码: class Hello extends React.Component { render() { return <div>Hello {this.props.toWhat}</div>; } } ReactDOM.render( <H
刘嘿哈
2022/10/25
3080
【Fiber】: [转]React 为什么使用链表遍历 Fiber 树
To educate myself and the community, I spend a lot of time reverse-engineering web technologies and writing about my findings. In the last year, I’ve focused mostly on Angular sources which resulted in the biggest Angular publication on the web — Angular-In-Depth. Now the time has come to dive deep into React. Change detection has become the main area of my expertise in Angular, and with some patience and a lot of debugging, I hope to soon achieve that level in React.
WEBJ2EE
2021/02/26
7470
【Fiber】: [转]React 为什么使用链表遍历 Fiber 树
【Fiber】:[译]深入解析React的新协调算法
深入研究 React 的新架构 Fiber,了解新协调算法的两个主要阶段。我们将详细了解 React 如何更新组件状态(state)、属性(props)以及如何处理子元素(children)。
WEBJ2EE
2021/02/26
6700
【Fiber】:[译]深入解析React的新协调算法
相关推荐
react源码解析19.手写迷你版react
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档