前言
上一节学习了CRUD 操作源码。下面几节来看下将代码优雅组织起来的查询构造器吧
本文主要内容整理自leoyang的系列文章,详情见引用
查询构造器
DB::table 与 查询构造器
在深入开展查询构造器的开始阶段,我们先通过一条简单的语句来说明下查询构造器的执行过程
DB::table('table')->get();
首先,这个DB就是我们之前说的connection
,然后是table函数
// \Illuminate\Database\Connection::table
public function table($table, $as = null)
{
return $this->query()->from($table, $as);
}
这个链式操作的第一步table函数
public function query()
{
return new QueryBuilder(
$this, $this->getQueryGrammar(), $this->getPostProcessor()
);
}
然后是query(),这个函数返回了一个Builder(Query/Builder),有三个参数,自身、语法编译器、结果处理器
public function from($table)
{
$this->from = $table;
return $this;
}
跟在query后面的from在这里比较简单,其他的我们后面会提到,这里只是设定了表明。设定完成之后,正式移交到Builder,开始执行get()
public function get($columns = ['*'])
{
return collect($this->onceWithColumns(Arr::wrap($columns), function () {
return $this->processor->processSelect($this, $this->runSelect());
}));
}
直接使用结果处理函数,嵌套runSelect()
public function processSelect(Builder $query, $results)
{
return $results;
}
protected function runSelect()
{
return $this->connection->select(
$this->toSql(), $this->getBindings(), ! $this->useWritePdo
);
}
runSelect真正的调起connection,发起sql调用,toSql()获取sql语句,getBindings()获取绑定参数
直到toSql(),真正使用语法编译器,生成sql语句。grammar是懒加载的,其中的编译等方法,都是在toSql() 时才会调用。
public function toSql()
{
return $this->grammar->compileSelect($this);
}
下面开始,详细的看看每个步骤吧
语法编译器
先来说下语法编译器,相对于connection的CRUD,语法编译器中都有相对应的函数执行编译操作
compileSelect
、compileInsert
、compileDelete
、compileUpdate
等等。
其中尤其要注意的是compileSelect
函数,因为这里面包含了太多的功能,因为你会发现很多compile函数并没有调用,当然不是没有使用,而是被包含在了compileComponents
函数中了,如下代码所示:
protected $selectComponents = [
'aggregate',
'columns',
'from',
'joins',
'wheres',
'groups',
'havings',
'orders',
'limit',
'offset',
'lock',
];
/**
* Compile a select query into SQL.
*
* @param \Illuminate\Database\Query\Builder $query
* @return string
*/
public function compileSelect(Builder $query)
{
if ($query->unions && $query->aggregate) {
return $this->compileUnionAggregate($query);
}
// If the query does not have any columns set, we'll set the columns to the
// * character to just get all of the columns from the database. Then we
// can build the query and concatenate all the pieces together as one.
$original = $query->columns;
if (is_null($query->columns)) {
$query->columns = ['*'];
}
// To compile the query, we'll spin through each component of the query and
// see if that component exists. If it does we'll just call the compiler
// function for the component which is responsible for making the SQL.
$sql = trim($this->concatenate(
$this->compileComponents($query))
);
if ($query->unions) {
$sql = $this->wrapUnion($sql).' '.$this->compileUnions($query);
}
$query->columns = $original;
return $sql;
}
/**
* Compile the components necessary for a select clause.
*
* @param \Illuminate\Database\Query\Builder $query
* @return array
*/
protected function compileComponents(Builder $query)
{
$sql = [];
foreach ($this->selectComponents as $component) {
// To compile the query, we'll spin through each component of the query and
// see if that component exists. If it does we'll just call the compiler
// function for the component which is responsible for making the SQL.
if (isset($query->$component) && ! is_null($query->$component)) {
$method = 'compile'.ucfirst($component);
$sql[$component] = $this->$method($query, $query->$component);
}
}
return $sql;
}
语法编译器会将上述所有的语句放入 $sql[] 成员中,然后通过 concatenate
函数组装成 sql 语句:
protected function concatenate($segments)
{
return implode(' ', array_filter($segments, function ($value) {
return (string) $value !== '';
}));
}
然后,我们还有一系列函数不得不提,wrap
函数,这些是grammer中的函数,作用是处理表名、变量名等等,wrap
函数本身是用来处理表名和列名的,处理流程如下:
- 若是 Expression 对象,利用函数 getValue 直接取出对象值,不对其进行任何处理,用于处理原生 sql。expression 对象的作用是保护原始参数,避免框架解析的一种方式。也就是说,当我们用了 expression 来包装参数的话,laravel 将不会对其进行任何处理,包括库名解析、表名前缀、别名等。
- 若表名 / 列名存在 as,则利用函数 wrapAliasedValue 为表名设置别名。
- 若表名 / 列名含有 .,则会被分解为 库名/表名,或者 表名/列名,并调用函数 wrapSegments。
代码在此:
public function wrap($value, $prefixAlias = false)
{
if ($this->isExpression($value)) {
return $this->getValue($value);
}
if (strpos(strtolower($value), ' as ') !== false) {
return $this->wrapAliasedValue($value, $prefixAlias);
}
return $this->wrapSegments(explode('.', $value));
}
其他还有 wrapAliasedValue
,wrapSegments
,wrapValue
等等,各有各的功能,感兴趣可以去源码看下。
from 语句
下面来说下刚才看到了的from语句,上面我们知道了,执行DB::table()时实际上是执行了from函数,它完成了表名的设定,代码再放一遍:
// \Illuminate\Database\Connection::table
public function table($table, $as = null)
{
return $this->query()->from($table, $as);
}
这个本身木有什么了,但是我们主要想说的是grammar 中对应的compileFrom
函数:
protected function compileFrom(Builder $query, $table)
{
return 'from '.$this->wrapTable($table);
}
public function wrapTable($table)
{
if (! $this->isExpression($table)) {
return $this->wrap($this->tablePrefix.$table, true);
}
return $this->getValue($table);
}
就是说,我们调用 from
时,可以传递两种参数,一种是字符串,另一种是 expression
对象,示例:
DB::table('table');
DB::table(new Expression('table'));
- 传递
expression
对象,什么是expression?(我的理解是有点像是一个SQL代码写的存储好的变量的对象,嗯)
当我们传递 expression
对象的时候,grammer
就会调用 getValue
取出原生 sql
语句。
- 传递字符串
当我们向from
传递普通的字符串时,laravel
就会对字符串调用wrap
函数进行处理,处理流程上一个小节已经说明: - 为表名加上前缀
$this->tablePrefix
- 若字符串存在
as
,则为表名设置别名。 - 若字符串含有
.
,则会被分解为库名
与表名
,并进行分别调用wrapTable
函数与wrapValue
进行处理。 - 为表名前后添加
"
,例如t1.t2
会被转化为"t1"."t2"
(不同的数据库添加的字符不同,mysql 就不是"
)
这里放出本文原作者发现的一个问题,大家感兴趣的可以看下:laravel
对 from
处理流程存在一些问题,表名前缀设置功能与数据库名功能公用存在问题,相关 issue
地址是:[Bug] Table prefix added to database name when using database.table
select 语句
好的,接下来,从select语句开始
public function select($columns = ['*'])
{
$this->columns = is_array($columns) ? $columns : func_get_args();
return $this;
}
public function selectRaw($expression, array $bindings = [])
{
$this->addSelect(new Expression($expression));
if ($bindings) {
$this->addBinding($bindings, 'select');
}
return $this;
}
public function addSelect($column)
{
$column = is_array($column) ? $column : func_get_args();
$this->columns = array_merge((array) $this->columns, $column);
return $this;
}
selectRaw 是传入expression对象时的处理
select 语句在SQL语句中,其实就是列的选择,也就是from
之前的部分。
grammar中对应的处理函数就是compileColumns
函数:
protected function compileColumns(Builder $query, $columns)
{
if (! is_null($query->aggregate)) {
return;
}
$select = $query->distinct ? 'select distinct ' : 'select ';
return $select.$this->columnize($columns);
}
public function columnize(array $columns)
{
return implode(', ', array_map([$this, 'wrap'], $columns));
}
select 函数中还会有一种比较特殊的,selectSub
。laravel 的 selectSub 支持闭包函数、queryBuild 对象或者原生 sql 语句,以下是单元测试样例:
$query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
$query->selectSub(function ($query) {
$query->from('two')->select('baz')->where('subkey', '=', 'subval');
}, 'sub');
另一种写法:
$query = DB::table('one')->select(['foo', 'bar'])->where('key', '=', 'val');
$query_sub = DB::table('one')->select('baz')->where('subkey', '=', 'subval');
$query->selectSub($query_sub, 'sub');
生成的 sql:
select "foo", "bar", (select "baz" from "two" where "subkey" = 'subval') as "sub" from "one" where "key" = 'val'
selectSub 语句的实现其实并不复杂:
public function selectSub($query, $as)
{
if ($query instanceof Closure) {
$callback = $query;
$callback($query = $this->forSubQuery());
}
list($query, $bindings) = $this->parseSubSelect($query);
return $this->selectRaw(
'('.$query.') as '.$this->grammar->wrap($as), $bindings
);
}
protected function parseSubSelect($query)
{
if ($query instanceof self) {
$query->columns = [$query->columns[0]];
return [$query->toSql(), $query->getBindings()];
} elseif (is_string($query)) {
return [$query, []];
} else {
throw new InvalidArgumentException;
}
}
可以看到,如果 selectSub 的参数是闭包函数,那么就会先执行闭包函数,闭包函数将会为 query 根据查询语句更新对象。parseSubSelect 函数为子查询解析 sql 语句与 binding 变量。
where 部分语句
from语句前面已经说过了,下面来看where语句,先糊一脸源代码
public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if (is_array($column)) {
return $this->addArrayOfWheres($column, $boolean);
}
list($value, $operator) = $this->prepareValueAndOperator(
$value, $operator, func_num_args() == 2
);
if ($column instanceof Closure) {
return $this->whereNested($column, $boolean);
}
if ($this->invalidOperator($operator)) {
list($value, $operator) = [$operator, '='];
}
if ($value instanceof Closure) {
return $this->whereSub($column, $operator, $value, $boolean);
}
if (is_null($value)) {
return $this->whereNull($column, $boolean, $operator !== '=');
}
if (Str::contains($column, '->') && is_bool($value)) {
$value = new Expression($value ? 'true' : 'false');
}
$type = 'Basic';
$this->wheres[] = compact(
'type', 'column', 'operator', 'value', 'boolean'
);
if (! $value instanceof Expression) {
$this->addBinding($value, 'where');
}
return $this;
}
满眼望去一堆堆的条件语句,我们挑选其中最常见的两个,结合grammar代码来看:
where 语句
首先是对wheres
的处理,compileWheres
函数负责所有 where
查询条件的语法编译工作,compileWheresToArray
函数负责循环编译查询条件,concatenateWhereClauses
函数负责将多个查询条件合并。
compileWheresToArray
函数负责把 $query->wheres 中多个 where 条件循环起来:
-
$where['boolean']
是多个查询条件的连接,and 或者 or,一般 where 条件默认为 and, 各种 orWhere 的连接是 or -
where{$where['type']}
是查询的类型,laravel 把查询条件分为以下几类:base、raw、in、notIn、inSub、notInSub、null、notNull、between、column、nested、sub、exist、notExist。每种类型的查询条件都有对应的 grammer 方法
concatenateWhereClauses
函数负责连接所有的搜索条件,由于 join 的连接条件也会调用 compileWheres
函数,所以会有判断是否是真正的 where 查询
protected function compileWheres(Builder $query)
{
if (is_null($query->wheres)) {
return '';
}
if (count($sql = $this->compileWheresToArray($query)) > 0) {
return $this->concatenateWhereClauses($query, $sql);
}
return '';
}
protected function compileWheresToArray($query)
{
return collect($query->wheres)->map(function ($where) use ($query) {
return $where['boolean'].' '.$this->{"where{$where['type']}"}($query, $where);
})->all();
}
protected function concatenateWhereClauses($query, $sql)
{
$conjunction = $query instanceof JoinClause ? 'on' : 'where';
return $conjunction.' '.$this->removeLeadingBoolean(implode(' ', $sql));
}
where 数组
如果column是数组,就会调用:
protected function addArrayOfWheres($column, $boolean, $method = 'where')
{
return $this->whereNested(function ($query) use ($column, $method, $boolean) {
foreach ($column as $key => $value) {
if (is_numeric($key) && is_array($value)) {
$query->{$method}(...array_values($value));
} else {
$query->$method($key, '=', $value, $boolean);
}
}
}, $boolean);
}
可以看到,数组分为两类,一种是列名为 key,例如 ['foo' => 1, 'bar' => 2]
,这个时候就是调用 query->where('foo', '=', '1', ‘and’)
。还有一种是 [['foo','1']
,['bar','2']]
,这个时候就会调用 $query->where(['foo','1'])
。
public function whereNested(Closure $callback, $boolean = 'and')
{
call_user_func($callback, $query = $this->forNestedWhere());
return $this->addNestedWhereQuery($query, $boolean);
}
public function addNestedWhereQuery($query, $boolean = 'and')
{
if (count($query->wheres)) {
$type = 'Nested';
$this->wheres[] = compact('type', 'query', 'boolean');
$this->addBinding($query->getBindings(), 'where');
}
return $this;
}
由于 compileWheres 会返回 where ... 或者 on ... 等开头的 sql 语句,所以我们需要把返回结果截取前 3 个字符或 6 个字符。
其他的更多where语句分析请看原文
join 语句
join
语句对数据库进行连接操作,join 函数的连接条件可以非常简单:
DB::table('services')->select('*')->join('translations AS t', 't.item_id', '=', 'services.id');
也可以比较复杂:
DB::table('users')->select('*')->join('contacts', function ($j) {
$j->on('users.id', '=', 'contacts.id')->orOn('users.name', '=', 'contacts.name');
});
//select * from "users" inner join "contacts" on "users"."id" = "contacts"."id" or "users"."name" = "contacts"."name"
$builder = $this->getBuilder();
DB::table('users')->select('*')->from('users')->joinWhere('contacts', 'col1', function ($j) {
$j->select('users.col2')->from('users')->where('users.id', '=', 'foo')
});
//select * from "users" inner join "contacts" on "col1" = (select "users"."col2" from "users" where "users"."id" = foo)
还可以更加复杂!
DB::table('users')->select('*')->leftJoin('contacts', function ($j) {
$j->on('users.id', '=', 'contacts.id')->where(function ($j) {
$j->where('contacts.country', '=', 'US')->orWhere('contacts.is_partner', '=', 1);
});
});
//select * from "users" left join "contacts" on "users"."id" = "contacts"."id" and ("contacts"."country" = 'US' or "contacts"."is_partner" = 1)
DB::table('users')->select('*')->leftJoin('contacts', function ($j) {
$j->on('users.id', '=', 'contacts.id')->where('contacts.is_active', '=', 1)->orOn(function ($j) {
$j->orWhere(function ($j) {
$j->where('contacts.country', '=', 'UK')->orOn('contacts.type', '=', 'users.type');
})->where(function ($j) {
$j->where('contacts.country', '=', 'US')->orWhereNull('contacts.is_partner');
});
});
});
//select * from "users" left join "contacts" on "users"."id" = "contacts"."id" and "contacts"."is_active" = 1 or (("contacts"."country" = 'UK' or "contacts"."type" = "users"."type") and ("contacts"."country" = 'US' or "contacts"."is_partner" is null))
其实 join 语句与 where 语句非常相似,将 join 语句的连接条件看作 where 的查询条件完全可以,接下来我们看看源码。
public function join($table, $first, $operator = null, $second = null, $type = 'inner', $where = false)
{
$join = new JoinClause($this, $type, $table);
if ($first instanceof Closure) {
call_user_func($first, $join);
$this->joins[] = $join;
$this->addBinding($join->getBindings(), 'join');
}
else {
$method = $where ? 'where' : 'on';
$this->joins[] = $join->$method($first, $operator, $second);
$this->addBinding($join->getBindings(), 'join');
}
return $this;
}
可以看到,程序首先新建了一个 JoinClause 类对象,这个类实际上继承 queryBuilder,也就是说 queryBuilder 上的很多方法它都可以直接用,例如 where、whereNull、whereDate 等等。
如果第二个参数是闭包函数的话,就会像查询组一样根据查询条件更新 $join
。
如果第二个参数是列名,那么就会调用 on 方法或 where 方法。这两个方法的区别是,on 方法只支持 whereColumn 方法和 whereNested,也就是说只能写出 join on col1 = col2 这样的语句,而 where 方法可以传递数组、子查询等等.
public function on($first, $operator = null, $second = null, $boolean = 'and')
{
if ($first instanceof Closure) {
return $this->whereNested($first, $boolean);
}
return $this->whereColumn($first, $operator, $second, $boolean);
}
public function orOn($first, $operator = null, $second = null)
{
return $this->on($first, $operator, $second, 'or');
}
grammer——compileJoins
接下来我们来看看如何编译 join 语句:
protected function compileJoins(Builder $query, $joins)
{
return collect($joins)->map(function ($join) use ($query) {
$table = $this->wrapTable($join->table);
return trim("{$join->type} join {$table} {$this->compileWheres($join)}");
})->implode(' ');
}
可以看到,JoinClause 在编译中是作为 queryBuild 对象来看待的。
union 语句
union 用于合并两个或多个 SELECT 语句的结果集。Union 因为要进行重复值扫描,所以效率低。如果合并没有刻意要删除重复行,那么就使用 Union All。
我们在 laravel 中可以这样使用:
$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
//(select * from `users` where `id` = 1) union (select * from `users` where `id` = 2)
还可以添加多个 union 语句:
$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
$query->union(DB::table('users')->select('*')->where('id', '=', 3));
//(select * from "users" where "id" = 1) union (select * from "users" where "id" = 2) union (select * from "users" where "id" = 3)
union 语句可以与 orderBy 相结合:
$query = DB::table('users')->select('*')->where('id', '=', 1);
$query->union(DB::table('users')->select('*')->where('id', '=', 2));
$query->orderBy('id', 'desc');
//(select * from `users` where `id` = ?) union (select * from `users` where `id` = ?) order by `id` desc
union 语句可以与 limit、offset 相结合:
$query = DB::table('users')->select('*');
$query->union(DB::table('users')->select('*'));
$builder->skip(5)->take(10);
//(select * from `users`) union (select * from `dogs`) limit 10 offset 5
union 函数
union 函数比较简单:
public function union($query, $all = false)
{
if ($query instanceof Closure) {
call_user_func($query, $query = $this->newQuery());
}
$this->unions[] = compact('query', 'all');
$this->addBinding($query->getBindings(), 'union');
return $this;
}
语法编译器对 union 的处理:
public function compileSelect(Builder $query)
{
$sql = parent::compileSelect($query);
if ($query->unions) {
$sql = '('.$sql.') '.$this->compileUnions($query);
}
return $sql;
}
protected function compileUnions(Builder $query)
{
$sql = '';
foreach ($query->unions as $union) {
$sql .= $this->compileUnion($union);
}
if (! empty($query->unionOrders)) {
$sql .= ' '.$this->compileOrders($query, $query->unionOrders);
}
if (isset($query->unionLimit)) {
$sql .= ' '.$this->compileLimit($query, $query->unionLimit);
}
if (isset($query->unionOffset)) {
$sql .= ' '.$this->compileOffset($query, $query->unionOffset);
}
return ltrim($sql);
}
protected function compileUnion(array $union)
{
$conjuction = $union['all'] ? ' union all ' : ' union ';
return $conjuction.'('.$union['query']->toSql().')';
}
可以看出,union 的处理比较简单,都是调用 query->toSql 语句而已。值得注意的是,在处理 union 的时候,要特别处理 order、limit、offset。
orderBy 语句
orderBy 语句用法很简单,可以设置多个排序字段,也可以用原生排序语句:
DB::table('users')->select('*')->orderBy('email')->orderBy('age', 'desc');
DB::table('users')->select('*')->orderBy('email')->orderByRaw('age desc');
如果当前查询中有 union 的话,排序的变量会被放入 unionOrders 数组中,这个数组只有在 compileUnions 函数中才会被编译成 sql 语句。否则会被放入 orders 数组中,这时会被 compileOrders 处理:
public function orderBy($column, $direction = 'asc')
{
$this->{$this->unions ? 'unionOrders' : 'orders'}[] = [
'column' => $column,
'direction' => strtolower($direction) == 'asc' ? 'asc' : 'desc',
];
return $this;
}
grammar中orderBy 的编译也很简单:
protected function compileOrders(Builder $query, $orders)
{
if (! empty($orders)) {
return 'order by '.implode(', ', $this->compileOrdersToArray($query, $orders));
}
return '';
}
protected function compileOrdersToArray(Builder $query, $orders)
{
return array_map(function ($order) {
return ! isset($order['sql'])
? $this->wrap($order['column']).' '.$order['direction']
: $order['sql'];
}, $orders);
}
group 语句
groupBy 语句的参数形式有多种:
DB::select('*')->from('users')->groupBy('email');
DB::select('*')->from('users')->groupBy('id', 'email');
DB::select('*')->from('users')->groupBy(['id', 'email']);
DB::select('*')->from('users')->groupBy(new Raw('DATE(created_at)'));
groupBy 函数很简单,仅仅是为 $this->groups 成员变量合并数组:
public function groupBy(...$groups)
{
foreach ($groups as $group) {
$this->groups = array_merge(
(array) $this->groups,
Arr::wrap($group)
);
}
return $this;
}
语法编译器的处理:
protected function compileGroups(Builder $query, $groups)
{
return 'group by '.$this->columnize($groups);
}
insert 语句
insert 语句也是我们经常使用的数据库操作,它的源码如下:
public function insert(array $values)
{
if (empty($values)) {
return true;
}
if (! is_array(reset($values))) {
$values = [$values];
}
else {
foreach ($values as $key => $value) {
ksort($value);
$values[$key] = $value;
}
}
return $this->connection->insert(
$this->grammar->compileInsert($this, $values),
$this->cleanBindings(Arr::flatten($values, 1))
);
}
laravel 的 insert 是允许批量插入的,方法如下:
DB::table('users')->insert([['email' => 'foo', 'name' => 'taylor'], ['email' => 'bar', 'name' => 'dayle']]);
一个语句可以向数据库插入两条记录。sql 语句为:
insert into users (
email,
name) values ('foo', 'taylor'), ('bar', 'dayle');
因此,laravel 在处理 insert 的时候,首先会判断当前的参数是单条插入还是批量插入。
if (! is_array(reset($values))) {
$values = [$values];
}
reset 会返回 values 的第一个元素。如果是批量插入的话,第一个元素必然也是数组。如果的单条插入的话,第一个元素是列名与列值。因此如果是单条插入的话,会在最外层再套一个数组,统一插入的格式。
如果是批量插入的话,首先需要把插入的各个字段进行排序,保证插入时各个记录的列顺序一致。
grammar中compileInsert对 insert 的编译也是按照批量插入的标准来进行的:
public function compileInsert(Builder $query, array $values)
{
$table = $this->wrapTable($query->from);
if (! is_array(reset($values))) {
$values = [$values];
}
$columns = $this->columnize(array_keys(reset($values)));
$parameters = collect($values)->map(function ($record) {
return '('.$this->parameterize($record).')';
})->implode(', ');
return "insert into $table ($columns) values $parameters";
}
首先对插入的列名进行 columnze 函数处理,之后对每个记录的插入都调用 parameterize 函数来对列值进行处理,并用 () 包围起来。
update 语句
public function update(array $values)
{
$sql = $this->grammar->compileUpdate($this, $values);
return $this->connection->update($sql, $this->cleanBindings(
$this->grammar->prepareBindingsForUpdate($this->bindings, $values)
));
}
与插入语句相比,更新语句更加复杂,因为更新语句必然带有 where 条件,有时还会有 join 条件:
public function compileUpdate(Builder $query, $values)
{
$table = $this->wrapTable($query->from);
$columns = collect($values)->map(function ($value, $key) {
return $this->wrap($key).' = '.$this->parameter($value);
})->implode(', ');
$joins = '';
if (isset($query->joins)) {
$joins = ' '.$this->compileJoins($query, $query->joins);
}
$wheres = $this->compileWheres($query);
return trim("update {$table}{$joins} set $columns $wheres");
}
其中有一个我们也比较常用的功能,updateOrInsert
,其语句会先根据 attributes 条件查询,如果查询失败,就会合并 attributes 与 values 两个数组,并插入新的记录。如果查询成功,就会利用 values 更新数据。
public function updateOrInsert(array $attributes, array $values = [])
{
if (! $this->where($attributes)->exists()) {
return $this->insert(array_merge($attributes, $values));
}
return (bool) $this->take(1)->update($values);
}
delete 语句
删除语句比较简单,参数仅仅需要 id 即可,delete 语句会添加 id 的 where 条件:
public function delete($id = null)
{
if (! is_null($id)) {
$this->where($this->from.'.id', '=', $id);
}
return $this->connection->delete(
$this->grammar->compileDelete($this), $this->getBindings()
);
}
删除语句的编译需要先编译 where 条件:
public function compileDelete(Builder $query)
{
$wheres = is_array($query->wheres) ? $this->compileWheres($query) : '';
return trim("delete from {$this->wrapTable($query->from)} $wheres");
}
动态 where
laravel 有一个有趣的功能:动态 where。
DB::table('users')->whereFooBarAndBazOrQux('corge', 'waldo', 'fred')
这个语句会生成下面的 sql 语句:
select * from users where foo_bar = 'corge' and baz = 'waldo' or qux = 'fred';
也就是说,动态 where 将函数名解析为列名与连接条件,将参数作为搜索的值。
我们先看源码:
public function dynamicWhere($method, $parameters)
{
$finder = substr($method, 5);
$segments = preg_split(
'/(And|Or)(?=[A-Z])/', $finder, -1, PREG_SPLIT_DELIM_CAPTURE
);
$connector = 'and';
$index = 0;
foreach ($segments as $segment) {
if ($segment !== 'And' && $segment !== 'Or') {
$this->addDynamic($segment, $connector, $parameters, $index);
$index++;
}
else {
$connector = $segment;
}
}
return $this;
}
protected function addDynamic($segment, $connector, $parameters, $index)
{
$bool = strtolower($connector);
$this->where(Str::snake($segment), '=', $parameters[$index], $bool);
}
- 首先,程序会提取函数名
whereFooBarAndBazOrQux
,删除前 5 个字符得到FooBarAndBazOrQux
- 然后正则判断,根据 And 或 Or 对函数名进行切割:FooBar、And、Baz、Or、Qux
- 添加 where 条件,将驼峰命名改为蛇型命名。
总结
其实原文的查询构造器是分为三篇的,但是我这里只提出了常用的一些语句,然后汇总成了一篇,当然会丢失很多语句的处理,不过也能给出一个全貌,这也算是我个人的一个习惯吧。下一篇开始Eloquent Model 源码分析。
引用
Laravel 官方文档 -- 查询构造器
Laravel Database——查询构造器与语法编译器源码分析 (上)
Laravel Database——查询构造器与语法编译器源码分析 (中)
Laravel Database——查询构造器与语法编译器源码分析 (下)
Database 查询构建器
如何写一个属于自己的数据库封装(6) - 查询 - WHERE篇
以上
欢迎大家关注我的公众号
网友评论