R·ex / Zeng


音游狗、安全狗、攻城狮、业余设计师、段子手、苦学日语的少年。

Laravel 版本与 PHP 版本之间的坑

注意:本文发布于 828 天前,文章中的一些内容可能已经过时。

又到了一年一度五四评优的季节,技术部又有的忙了。作为陪伴这套系统时间最长的人,也是目前唯一了解全部技术细节的人,我自然需要帮现在负责的同学们一点小忙。于是我又把这套系统 clone 了下来。

如果用的是 Apache,那么 Laravel 已经有了 .htaccess 文件;但是Nginx 的配置并不像官方说的那么简单,下面是我尝试了许多次才配置成功的:

location /evaluation {
    if (-d $request_filename) {
        rewrite ^/(.*)([^/])$ http://$host/$1$2/ permanent;
    }
    alias /path/to/evaluation/public/;
    if (!-f $request_filename) {
        rewrite ^(.*)$ /evaluation/index.php?$query_string;
    }
    location ~ \.php {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME /path/to/evaluation/public/index.php;
        include fastcgi_params;
    }
}

然后使用自带的 artisan 脚本将数据导入进去,复制并修改 .env.sample,理论上来讲应该就可以跑起来了……

为什么说是“理论上”呢?因为如果实际上也能跑起来,就没有这篇文章了。

打开网站,一个大大的报错:Function mcrypt_get_iv_size() is deprecated。后来才发现 mcrypt 扩展在 PHP 7.1 中被移除了。网上的解决方法大多是要么升级 Laravel(我们之前用的是 5.0),要么降级 PHP(最高 7.0)。降级 PHP 在我这儿显然不现实,因此考虑升级 Laravel。

于是我对着文档升级了许久,最终发现 redirect()->with 总是无法传值,Session::flash 在当前文件是好的,换了文件之后就没了……不知道哪里出了问题,据说 Laravel 升级到 5.2 之后就是神坑,于是还有着四个项目的我果断放弃。

但是很不甘心,如果可以直接 patch Laravel 5.0,使其能在 PHP 7.1 上运行,至少作为调试是没问题了吧。于是我就开始了 patch 的过程。

跟着输出的 callstack 发现错误在 vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php 中,它调用了那个过时的函数。通读了一遍,发现整个文件都是建立在 mcrypt 上面的,因此必须全部替换掉。看了一下 5.4 版的对应文件,使用的是 PHP 自带的 hash_hmac 函数,因此不会有问题,直接将整个文件用新版本替换掉。

然后发现报错变成了“未实现 Encrypter 接口的 setModesetCipher 函数”。文件在 vendor/laravel/framework/src/Illuminate/Contracts/Encryption/Encrypter.php,看到新版本直接将这俩函数砍掉了,于是也将这个文件替换掉。

然后报错又变成了 The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.,于是又看到之前被替换过的 vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php 文件。里面调用了 supported($key, $cipher),若不支持则输出上述错误。supported 函数逻辑很简单:

$length = mb_strlen($key, '8bit');
return ($cipher === 'AES-128-CBC' && $length === 16) ||
       ($cipher === 'AES-256-CBC' && $length === 32);

因此只支持 AES-128-CBCAES-256-CBC 两种加密方式了,而旧版 Laravel 则使用了 MCRYPT_RIJNDAEL_128,因此需要修改 config/app.php'cipher' => 一行,后面改为 'AES-256-CBC'

然后的报错让我知道了 Laravel 是有个叫 APP_KEY 的东西需要用到这一段,在 .env 中可以设定,但我的加密算法改了,key 应该要换一个吧?于是搜了半天,网上说可以使用 php artisan key:generate 来重新生成,但我在运行的时候提示 Class 'Memcached' not found。Memcached 就没有 Windows 版的 PHP 扩展,那咋办?尝试修改 .env 中的 CACHE_DRIVER 为其它值并不管用。然后我突然想到,那条命令是命令行里的啊,可以在前面加上 CACHE_DRIVER=[空格] 来达到效果。于是最终输入 CACHE_DRIVER= php artisan key:generate,终于重新生成了一段 key。命令行输出如下(中间省略一部分):Application key [base64:fZYdu....hRZI=] set successfully.

然而还是启动不了,报错还是“不支持此加密格式”。思路又回到 supported 函数上,我不然在 return 之前输出一下 key 吧,看看是不是 32 位。于是我惊奇地发现 key 还是一串 Base64……于是看到新版的 vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php 文件:

// If the key starts with "base64:", we will need to decode the key before handing
// it off to the encrypter. Keys may be base-64 encoded for presentation and we
// want to make sure to convert them back to the raw bytes before encrypting.
if (Str::startsWith($key = $config['key'], 'base64:')) {
    $key = base64_decode(substr($key, 7));
}
return new Encrypter($key, $config['cipher']);

……我错了,我把这个文件也替掉还不行么?


经过了一系列摸索,终于一切运行正常了。

当然,纸飞机的服务器暂时还在用 PHP 5.6,我的修改也基本都是在 vendor 文件夹和 .env 这些不会被 Git 追踪的位置,线上的 key 重新生成一份就可以了。除此以外,这么做仅用于本地调试是没问题的。至于线上,我觉得最好还是等 Laravel 5.5 LTS 出来之后再做考虑吧……

版权声明:除文章开头有特殊声明的情况外,所有文章均可在遵从 CC BY 4.0 协议的情况下转载。

这是我们共同度过的

第 1313 天