深入理解 Laravel Eloquent(二)——中间操作流(Builder)

本篇教程是该系列教材的第二篇,将主要讲述 Eloquent 中中间操作流的概念。中间操作流是我自己总结并翻译的概念,支撑该功能的类位于 Illuminate\Database\Eloquent\Builder,此概念对于新手入门有很大帮助,但是官方文档没有相关概念和解释。

什么是“中间操作流”(Builder)

Builder 这个单词可以直译成构造器,但是“中间操作流”更容易理解,因为数据库操作大部分时候都是链式操作的。

中间操作流,请看代码:

Article::where('id', '>', 10)->where('id', '<', 20)->orderBy('updated_at', 'desc')->get();

这段代码的 `::where()->where()->orderBy()` 就是中间操作流。中间操作流用面向对象的方法来理解,可以总结成一句话:

创建一个对象,并不断修改它的属性,最后用一个操作来触发数据库操作。

但是,我们都知道,如果直接用 :: 来访问某个 function,无论这个 function 是否为 static,构造函数 __construct() 都不会被调用,那么创建对象是如何实现的呢?请看:https://github.com/illuminate/database/blob/master/Eloquent/Model.php#L3354

如何找到中间操作流的蛛丝马迹

中间操作流这个东西,文档里几乎没有任何有价值的信息,那么,我们该怎么找出这个玩意儿呢?很简单,使用以下代码:

$builder = Article::where('title', "我是标题")->title;

然后你就会看到下面的错误:

Image

为什么会出现错误?因为 `Article::where()` 了之后依然是 `Builder` 对象,还不是 `Article` 对象,不能直接取 `title`。

“终结者”方法

所谓 “终结者” 方法,指的是在 N 个中间操作流方法对某个 Eloquent 对象进行加工以后,触发最终的数据库查询操作,得到返回值。

`first()` `get()` `paginate()` `count()` `delete()` 是用的比较多的一些 “终结者” 方法,他们会在中间操作流的最后出现,把 SQL 打给数据库,得到返回数据,经过加工返回一个 Article 对象或者一群 Article 对象的集合。

复杂用法示例

Article::where('id', '>', '100')->where('id', '<', '200')->orWhere('top', 1)->belongsToCategory()->where('category_level', '>', '1')->paginate(10);

下一步:深入理解 Laravel Eloquent(三)——模型间关系(关联)

WRITTEN BY

avatar
2015.1.5   /   热度:24689   /   分类: Laravel

评论:

海蜗牛
2016-06-24 18:41
我也在纳闷这个对象是如何初始化的呢
原来是这里 Eloquent\Model::__callStatic()

    //没有静态方法::where(),落到这个魔术方法
    public static function __callStatic($method, $parameters)
    {
        //创建子类对象
        $instance = new static;
        //继续调 $this->where()又命中__call()方法
        return call_user_func_array([$instance, $method], $parameters);
    }
    //找不到$this->where()又落到这里
    public function __call($method, $parameters)
    {
        if (in_array($method, ['increment', 'decrement'])) {
            return call_user_func_array([$this, $method], $parameters);
        }
        //还是去隔壁Eloquent\Builder家找where吧
        $query = $this->newQuery();
        return call_user_func_array([$query, $method], $parameters);
    }
xhq
2016-05-01 23:28
链式操作,return this 实现
小菜鸟
2016-04-20 11:46


大神你好,请问在eloquent里,我想执行where in这种语句应该如何构造

select * from table where id in ('id,id,id,')

原型是这种的,望赐教!
祥子
2015-09-30 11:49
$instance = new static;
return call_user_func_array([$instance, $method], $parameters);
new static 是PHP5.5的语法吧。
cq
2015-06-19 01:24
请问楼主,Eloquent与Builder究竟是何种关系? 有点小糊涂
即为何Eloquent的继承类Article使用能够使用一系列builder的方法处理?
simon
2015-05-29 16:04
恕我冒昧,提个建议, builder的直接翻译“构造器”已经足够说明一切,您的中间操作流,会使人家更加混淆和云里雾里。构造器负责把SQL操作需要提供的各个组成部分收集在一起后,最终生成实际执行的SQL 语句,就这么简单。链式操作只是一个简便的写法,假设有个buider对象,那么
buider->where()->group()->orderby()等价于:

builder->where(“age>30");
builder->group("gender");
builder->orderby("age");
最后的操作,假如有增删改查对应的接口函数为insert(), delete(), update(), select(),那么这些接口会使用buider收集到的参数生成对应的SQL语句.
JohnLui
2015-05-29 20:59
@simon:懂得 Builder 的原理的人,即所谓“道”的人,是不需要我来讲解该怎样用的,更不用我来讲解该怎样翻译,我的目的是教 不会用的人 用,即“术”。

“道”只能自己感悟,跟他人学是学不会的。
yankeys
2016-06-30 18:29
@JohnLui:这个回答好赞
DENGBO
2015-04-21 10:38
设计模式里面有个建造者(Builder)模式。应该和博主讲的差不多~
azxkill
2015-04-10 23:28
楼主你好,今天装了个xdebug.结果运行laravel直接apache cpu 100%  ,然后就页面超时了.我运行TP等其它项目都没事,关掉xdebug再打开laravel就正常了,请问这是什么情况啊.
Whoops, looks like something went wrong.
1/1 FatalErrorException in compiled.php line 8952: Maximum execution time of 30 seconds exceeded

    in compiled.php line 8952
    at FatalErrorException->__construct('message' => ''Maximum execution time of 30 seconds exceeded'', 'code' => '1', 'severity' => '0', 'filename' => ''/usr/local/apache2.4.10/htdocs/myLaravel/vendor/compiled.php'', 'lineno' => '8952', 'traceOffset' => '0', 'traceArgs' => '???') in compiled.php line 1743
    at HandleExceptions->fatalExceptionFromError('error' => 'array ('type' => 1, 'message' => 'Maximum execution time of 30 seconds exceeded', 'file' => '/usr/local/apache2.4.10/htdocs/myLaravel/vendor/compiled.php', 'line' => 8952)', 'traceOffset' => '0') in compiled.php line 1738
    at HandleExceptions->handleShutdown() in compiled.php line 0
    at call_user_func:{/usr/local/apache2.4.10/htdocs/myLaravel/vendor/compiled.php:8952}('class Closure { public $static = array ('instance' => class App\Http\Controllers\Admin\AdminHomeController { protected $middleware = array (); protected $beforeFilters = array (); protected $afterFilters = array () }, 'route' => class Illuminate\Routing\Route { protected $uri = 'admin'; protected $methods = array (0 => 'GET', 1 => 'HEAD'); protected $action = array ('middleware' => 'auth', 'uses' => 'App\\Http\\Controllers\\Admin\\AdminHomeController@index', 'controller' => 'App\\Http\\Controllers\\Admin\\AdminHomeController@index', 'namespace' => 'App\\Http\\Controllers\\Admin', 'prefix' => '/admin', 'where' => array ()); protected $defaults = array (); protected $wheres = array (); protected $parameters = array (); protected $parameterNames = array (); protected $compiled = class Symfony\Component\Routing\CompiledRoute { private $variables = array (); private $tokens = array (0 => array (0 => 'text', 1 => '/admin')); private $staticPrefix = '/admin'; private $regex = '#^/admin$#s'; private $pathVariables = array (); private $hostVariables = array (); private $hostRegex = NULL; private $hostTokens = array () }; protected $container = ... }, 'method' => 'index'); public $this = class Illuminate\Routing\ControllerDispatcher { protected $router = ...; protected $container = ... }; public $parameter = array ('$request' => '<required>') }', '...') in compiled.php line 8952
    at Pipeline->Illuminate\Pipeline\{closure}('passable' => '...') in compiled.php line 8935
JohnLui
2015-04-10 23:54
@azxkill:这个错误是 PHP 脚本执行超时,超过 30 秒被杀掉了。
大愚
2015-03-13 20:38
在php中用类名调用非静态方法,效率非常不靠谱,又费内存,又费时。为啥子larave这里会设计成:Page::where()呢?我试了直接用Page->where()这样子调用还不行。你对这方面有没有研究过?
JohnLui
2015-03-13 21:09
@大愚:1. 因为这样写好看,且好用。开发效率是最高优先级,运行效率是最低优先级。
2. 这样写是不符合 PHP 面向对象语法的。。。
大愚
2015-03-13 22:47
@JohnLui:new Page()->where();没写完整。测试后,这样子效率比较高。
不过我也觉得Page:where()这样子写起比较爽!
Jerome
2016-01-18 16:19
@大愚:你又写错了 Page::where()
SimonHG
2015-02-25 09:13
The Builder you called,in fact,is what channel called by Java8.In Chinese should be '管道'.
Ofcause terminal is still the terminal you said.
Good article.

发表评论:

© 2011-2017 岁寒  |  Powered by Emlog