0%

有可能是配置缓存导致的,

运行:

1
php artisan config:clear

可清除配置缓存,配置缓存保存在 bootstrap/cache/config.php,可以直接去那文件夹看看是不是缓存导致的。

还有另外一个缓存文件 bootstrap/cache/services.json,上面的命令只是清除配置,下面的命令可以同时清除这两个缓存文件。

1
php artisan clear-compiled
1
2
3
php artisan config:clear
php artisan clear-compiled // 还不行再运行下面的
php artisan optimize

使用 MacroableBuilder 添加自定义方法

Laravel 中提供了 Macroabletrait,之前一直没有想过可以用上这个东西。

最近才想到可以这么做,源码看这里:https://github.com/laravel/framework/blob/5.6/src/Illuminate/Support/Traits/Macroable.php

目前用的是 5.1 版本,应该 5.1 版本以后的都有这个特性。

当然,你也可以在 Model 基类里面添加自定义方法,但是这样添加的方法 DB 类是用不了的。

但是使用 macro 定义到 Builder 的方法可以同时在 DB 类和 Eloquent Model 中使用(具体不赘述,自己看源码,就是利用 __call__callStatic 这些魔术方法)。

定义

使用方法:调用 Macroablemacro 方法,绑定一个自定义方法到 Builder 类中,如:

1
2
3
4
\Illuminate\Database\Query\Builder\Builder::macro('active', function () {
return $this->where('status', 1);
});

使用

调用方法是(使用 DB 类):

1
DB::table(xxx)->active()->get();

或者(使用 Eloquent Model):

1
\App\Model\User::active()->first();

放在哪里?

至于我们应该把上面那几行放哪里?

个人觉得一个比较好的地方是 Providers 中,在 app/Providers 下面新建一个 Provider,把 macro 调用放到 Providerregister 方法中。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php

namespace App\Providers;

use Illuminate\Database\Query\Builder;
use Illuminate\Support\ServiceProvider;

/** @mixin \Illuminate\Database\Eloquent\Builder */
class DatabaseServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
Builder::macro('active', function () {
return $this->where('status', 1);
});
}
}

当然,加了 Providers 之后还要在 config/app.php 中配置这个 Provider

就这样。

还有个问题是,这样添加的方法 ide 无法识别,我们这时候就可以使用 @method 了,如:

1
@method $this active()

可以使用命令把这些 @method 写入到 Builder 头部。

一直以来,想调试框架中的某些东西,如想知道 Elpquentcreate 方法返回值是个什么东西,

以前的话,应该就是在 create 方法调用之后,使用 dd 或者 var_dump 之类的函数打印出来

如:

1
2
3
4
5
public function getTest()
{
$user = \App\User::create(['name' => 'tinker']);
dd($user);
}

这样一来,这个流程似乎有点冗长,因为我们还要打开浏览器查看

有了 tinker,我们就可以直接在命令行运行我们想要测试的代码,如:

tinker

我们可以看到,create 方法返回了一个模型对象。

我们就把这个 tinker 当作可以交互式的 代码执行工具好了,我们在学习 Laravel 的过程中可以在这里直接测试 Laravel 框架中的一些用法,也可以进行调试什么的。

通过控制台的 cookie 信息我们会发现,每次请求之后,关键的 cookie,如PHPSESSIDXSRF-TOKEN 都会发生变化,并且都是很长的一串字符串。

其实这是一个 json 数组,其中包含了 ivvaluemac 三个字段:

cookie

这些字段都是在框架加密解密的时候使用的,加密方法是 openssl_encrypt

openssl_encrypt

openssl 不太了解的可以看下下面的例子:

1
2
3
4
5
6
7
8
$data = 'laravel';
$iv = random_bytes(16);
$key = 'this is key';

$encrypt = openssl_encrypt($data, 'AES-256-CBC', $key, 0, $iv);

var_dump($encrypt);
var_dump(openssl_decrypt($encrypt, 'AES-256-CBC', $key, 0, $iv));

Laravel 中的话,key 就是 .env 配置文件里面的 APP_KEY,除了 key 还有两个变化的参数就是 加密、解密的数据以及 iv

也就是说,如果我们需要加密 cookie 的话,我们至少得保存下 加密后的数据以及 iv

这样看来,mac 字段似乎有点多余,但是我们可以使用该字段来验证数据的合法性:

x

如果验证不通过,Laravel 也就不会对 data 进行解密操作。

虽然每次请求 cookie 都会发生变化,但是实际数据是没有变的,发生变化只是因为用来加密的 iv 变了(使用 random_bytes 方法生成)。

由于 iv 每次都变化,所以需要把 iv 也一同返回给浏览器,加上验证数据合法性的 mac,最后返回的就是下面的数组的 json 编码后在 base64 编码的数据:

1
2
3
4
5
[
'iv' => random_bytes(16), // 16位随机字节串
'value' => 'xxxx...', // 加密后的数据
'mac' => 'xxx...' // 后续请求验证数据合法性的字符串
]

模型定义

App\User

1
2
3
4
5
6
7
class User extends Model
{
public function profile()
{
return $this->hasOne('App\UserProfile');
}
}

使用 with 查询某个 user 及其的 profile:

1
2
3
4
5
6
7
8
App\User::with([
'profile' => function($query) {
$query->select(['id']);
}
])
->find(4)
->toArray();

上面的用法中,我们会发现,即使数据库有记录,sql 也记录了对应的查询语句,但是 profile 关联却是空的:

但是加上外键就可以得到正确结果了:

1
2
3
4
5
6
7
8
App\User::with([
'profile' => function($query) {
$query->select(['id', 'user_id']); // 多了 user_id
}
])
->find(4)
->toArray();

可以查找到正确的 profile 了。

这和 Laravel 框架的工作方式相关,我们先看看下面的例子:

sql

我们使用 DB::listen 方法去记录相关的 sql 语句

我们查看 log 可以发现有以下语句:

1
2
select * from `users` where `id` in (?, ?) [3,4]
select * from `profiles` where `profiles`.`user_id` in (?, ?) [3,4]

我们可以明显发现,laravel 对于 userprofile 是独立查询的,

也就是说会得到两个集合,一个是 User、一个是 Profile,但是这并不是我们想要的结果,我们需要的结果是,只有一个 User 集合, 并且这个 User 集合里面有 Profile 关联。

但是结果就是这样,如果是你,你会怎么把这些数据关联起来呢?

对了,我们定义关联的时候不是定义了它们的关联方式么?

上面的 hasMany 方法默认第二第三个参数其实就是这两个集合建立关联的关键,第三个参数 user_id、第四个参数 id;这样一来我们就可以通过比较 Profileuser_idUser 里面的 id,如果相等,则这个 Profile 是属于这个 User 的,我们就把该 Profile 放进 Userprofile 关联中,最后就得到我们想要的结果了。

Xdebug 验证

xdebug 证实一下我们的想法:

match getRelationValue matchOneOrMany

如我们所想的那样,图一的 match 方法,顾名思义就是匹配了,通过 user 模型集合和 profile 模型集合进行匹配。

图二,也证实了我们模型建立关联需要通过关联中外键的值得想法。

图三,是通过获取 userlocalkey,也就是 id 的值,来查找 $dictonary 中是否有对应的值,buildDictonary 方法会建立一个关联数组,keyuser_id(外键)的值,值是关联的数据。这样一样,由于我们没有把 user_idselect 出来,最后得到的 $dictonary 的结构并不是预期的那样:

xdebug

其实我们本来是想要得到下面的这种:

1
2
3
[
3 => xxx(UserProfile对象) // 3 是关联的 user_id
]

但是我们得到的却是,所有的 UserProfile 都在一个嵌套的数组里面了,这样一来,下面的 getRelationValue 得到的结果自然就是空的了。

总结

好了,总结一下,就是:Laravel 先查询主要的数据(不带 with),查询完了之后,取出其中的 id 列数组(不一定都是id啊,只是举个例子),将这个数组作为条件去查找关联,有多少个关联就会再去查找多少次,查找完关联之后通过得到的结果的主键和关联数据的外键比对,相等则建立关联。

总结:在关联筛选 field 的时候,也必须要把关联的外键写进去,否则,即使产生了正确的 sql 语句,但是它们建立不了关联,通过 $user->profile 得到的还是一个空集合。