[ Laravel 5.3 文档 ] 序言 —— 升级指南

从 5.2 升级5.3

注:预计升级升级时间:2-3小时

PHP & HHVM

Laravel 5.3要求PHP 5.6.4及以上版本,官方将不再支持HHVM,因为其不包含PHP 5.6+新提供的语言特性。

废弃

所有罗列在Laravel 5.2升级指南中的废弃功能都已从框架中移除,你需要查看这个列表以确定不再使用这些废弃功能。

数组

key/value顺序更改

Arr类上的firstlast、以及contains方法现在将“value”作为第一个参数传递给给定闭包,例如:

Arr::first(function ($value, $key) {
    return ! is_null($value);
});

在Laravel之前版本中,$key是第一个参数,但是由于大多数使用案例只对$value感兴趣,所以我们将其放到第一个。你可以在应用中进行一次全局搜索以验证是否你在应用中通过旧的方式使用了这个函数。

Artisan

make:console命令

make:console命令现在被重命名为make:command

认证

认证脚手架

Laravel框架提供的默认的两个认证控制器已经被分割成四个,这一更改让认证控制器变得更加清爽、责任更加明确。升级应用认证控制器到最新的最简单方法就是从GitHub上将四个控制器代码拷贝过来复制到项目中。

你还要确保在路由文件中调用了Route::auth()方法,该方法在底层已经为新控制器注册了合适的路由。

这些新控制器拷贝到应用后,需要重新实现之前在认证控制器中实现的方法和业务。例如,如果你在自定义用于认证的guard,需要重写控制器的guard方法,你可以检查每个认证控制器的trait以判断要重写哪些方法。

注:如果你没有自定义认证控制器,只需要将代码从Github拷到本地项目,并确保在路由文件中调用了Route::auth方法。

密码重置邮件

密码重置邮件现在使用新的通知功能(Laravel Notifications),如果你想要在发送密码重置链接的时候自定义通知发送,需要重写Illuminate\Auth\Passwords\CanResetPassword trait上的sendPasswordResetNotification方法。

User模型必须使用新的Illuminate\Notifications\Notifiabletrait以便邮件重置链接可以正常发送:

<?php

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;
}

注:不要忘了在配置文件config/app.phpproviders数组中注册服务提供者Illuminate\Notifications\NotificationServiceProvider

POST到Logout

Route::auth方法现在为/logout注册了一个POST路由取代之前的GET路由。这可以防止其它应用让用户从应用中退出。想要升级的话,需要将原来的退出请求转化为POST请求方式,或者为/logout URI自定义GET路由:

Route::get('/logout', 'Auth\LoginController@logout');

授权

使用类名调用策略方法

有时候一些策略方法只接收当前认证用户而不是授权模型实例,最常见的场景就是授权create动作。例如,如果你在创建一篇博客,你可能希望检查当前用户是否被授权创建文章。

当定义一个不接收模型实例的策略方法时,比如create方法,对应类名就不再需要以第二个参数的方式传入,只需要传入认证用户实例即可:

/**
 * Determine if the given user can create posts.
 *
 * @param  \App\User  $user
 * @return bool
 */
public function create(User $user)
{
    //
}

AuthorizesResources Trait

AuthorizesResourcestrait已经和AuthorizesRequeststrait合并到一起,你需要从app/Http/Controllers/Controller.php中移除AuthorizesResourcestrait。

Blade

自定义指令

在之前版本的Laravel中,我们使用directive方法注册自定义的Blade指令,传递给指令回调的$expression参数包含了最外层的括号。在Laravel 5.3中,这些最外层的括号将不再包含在传递给指令回调的表达式中,请查看Blade文档确保自定义的Blade指令还能正常工作。

广播

服务提供者

Laravel 5.3 对事件广播进行了显著的优化,需要添加的新的BroadcastServiceProvider从GitHub下载文件)到app/Providers目录,然后将这个新的服务提供者注册到配置文件config/app.phpproviders数组中。

缓存

扩展闭包绑定&$this

使用闭包调用Cache::extend方法时,$this会被绑定到CacheManager实例,从而允许你在扩展闭包中调用其提供的方法:

Cache::extend('memcached', function ($app, $config) {
    try {
        return $this->createMemcachedDriver($config);
    } catch (Exception $e) {
        return $this->createNullDriver($config);
    }
});

Cashier

如果你在使用Cashier,需要升级laravel/cashier扩展包到7.0版本,这一版本的Cashier只修改了一些内置方法以便兼容于Laravel 5.3,并没有什么重大更新。

集合

key/value顺序调整

集合方法firstlastcontains都将“value”作为第一个参数传递给相应的回调闭包,例如:

$collection->first(function ($value, $key) {
    return ! is_null($value);
});

在Laravel之前的版本中,$key是第一个参数,由于大部分使用案例只对$value感兴趣,所以将其调整为第一个,你需要在应用中对这些方法做一个全局搜索,以验证$value是否按照期望的方式以第一个参数传入闭包。

where默认使用非严格比较

where现在默认使用非严格比较而不是之前的严格比较,如果你想要进行严格比较,可以使用whereStrict方法。

where方法也不再接收第三个参数标识“严格”,你需要基于应用的需求区分调用wherewhereStrict

控制器

构造函数当中使用Session

在Laravel以前的版本中,你可以在控制器构造函数中获取session变量或者认证后的用户实例。框架从未打算具有如此明显的特性。在Laravel 5.3中,你在控制器构造函数中不再能够直接获取到session变量或认证后的用户实例,因为中间件还未启动。

仍然有替代方案,那就是在控制器构造函数中使用Closure来直接定义中间件。请注意,在使用这个方案的时候,确保你所使用的Laravel版本高于 5.3.4

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Support\Facades\Auth;
use App\Http\Controllers\Controller;

class ProjectController extends Controller
{
    /**
     * 当前用户的的所有Projects.
     */
    protected $projects;

    /**
     * 创建新的控制器实例.
     *
     * @return void
     */
    public function __construct()
    {
        $this->middleware(function ($request, $next) {
            $this->projects = Auth::user()->projects;
            return $next($request);
        });
    }

}

当然,你可以在控制器的其他方法中依靠Illuminate\Http\Request 类获取到session以及认证用户信息。

/**
 * 获取当前用户的所有Projects.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return Response
 */
public function index(Request $request)
{
    $projects = $request->user()->projects;
    $value = $request->session()->get('key');
    //
}

声明:控制器session部分由网友AC1982(微信号)提供翻译支持。

数据库

集合

查询构建器现在返回Illuminate\Support\Collection实例而不是原生数组,以便保持和Eloquent返回结果类型一致。

如果你不想要迁移查询构建器结果到Collection实例,可以在查询构建器的get方法后调用call方法,这将会返回原生的PHP数组结果,从而保证向后兼容:

$users = DB::table('users')->get()->all();

Eloquent $morphClass 属性

可以在Eloquent模型上定义的$morphClass属性已经被移除,以便定义一个“morph map”(变形映射),定义变形映射可以支持渴求式加载并且解决使用多态关联关系引起的额外bugs,如果你之前使用了$morphClass属性,需要使用如下语法将其迁移到morphMap

Relation::morphMap([
    'YourCustomMorphName' => YourModel::class,
]);

例如,如果你之前定义了如下$morphClass

class User extends Model
{
    protected $morphClass = 'user'
}

现在则需要在AppServiceProviderboot方法中定义如下morphMap

use Illuminate\Database\Eloquent\Relations\Relation;

Relation::morphMap([
    'user' => User::class,
]);

Eloquent save方法

如果模型在上一次获取或保存之后被改变过,调用Eloquent的save方法会返回false

Eloquent 作用域

Eloquent作用域现在会以第一个作用域约束的布尔值为准,例如,如果你的作用域以orWhere开头,则将不再被转化为正常的where。如果你现在的代码中使用了这个特性(在循环中添加了多个orWhere约束),需要验证第一个条件是否是正常的where以避免布尔逻辑问题。

如果你的作用域约束都是以where开头则不需要做任何调整,你可以通过toSql方法查看当前的SQL语句:

User::where('foo', 'bar')->toSql();

join 语句

JoinClause类被重写以便统一查询构建器的语法,on方法上可选的$where参数已被移除,要添加where条件需要显式使用查询构建器提供的where方法:

$query->join('table', function($join) {
    $join->on('foo', 'bar')->where('bar', 'baz');
});

$bindings属性也被移除,要直接操作join绑定可以使用addBinding方法:

$query->join(DB::raw('('.$subquery->toSql().') table'), function($join) use ($subquery) {
    $join->addBinding($subquery->getBindings(), 'join');
});

加密

Mcrypt加密被移除

Laravel 5.1.0版本中Mcrypt加密就已经被废弃,在Laravel 5.3中该功能被完全移除,以便于统一使用基于OpenSSL的新的加密实现,该实现从Laravel 5.1.0开始就已经作为默认的加密实现方式。

如果你还在配置文件config/app.php中使用基于cipher的Mcrypt加密,需要将cipher更新到AES-256-CBC并且将key设置为32位随机字符串(这可以通过Artisan命令php artisan key:generate生成)。

如果你在使用Mcrypt加密保存加密数据到数据库,则需要安装包含Mcrypt加密实现的扩展包laravel/legacy-encrypter,你需要通过该扩展包解密数据然后通过新的OpenSSL加密方式对数据进行重新加密。例如,你可能在自定义Artisan命令中这么做:

$legacy = new McryptEncrypter($encryptionKey);

foreach ($records as $record) {
    $record->encrypted = encrypt(
        $legacy->decrypt($record->encrypted)
    );

    $record->save();
}

异常处理器

构造函数

异常处理器基类现在需要传递一个Illuminate\Container\Container实例到构造函数,这只有当你在app/Exception/Handler.php中定义了自定义的__construct方法时才会对应用产生影响,如果你这么做了,需要传递一个容器实例到parent::__construct方法:

parent::__construct(app());

中间件

can中间件命名空间修改

罗列在HTTP Kernel的$routeMiddleware属性中的can中间件需要作如下修改:

'can' => \Illuminate\Auth\Middleware\Authorize::class,

can中间件认证异常

如果用户没有认证的话can中间件会抛出Illuminate\Auth\AuthenticationException异常实例,如果你手动捕获了其它异常类型,需要修改为捕获这个异常,在大多数案例中,这一修改对应用不会造成影响。

绑定替代中间件

路由模型绑定现在通过中间件来完成,所有应用都需要在app/Http/Kernel.php文件的web中间件组中添加Illuminate\Routing\Middleware\SubstituteBindings

\Illuminate\Routing\Middleware\SubstituteBindings::class,

你还需要在HTTP Kernel的$routeMiddleware属性中注册路由中间件用于绑定替代:

'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,

这个路由中间件注册后,还要将其添加到api中间件组:

'api' => [
    'throttle:60,1',
    'bindings',
],

通知(Notifications)

安装

Laravel 5.3内置了一个新的、基于驱动的通知系统,你需要在配置文件config/app.phpproviders数组中注册服务提供者Illuminate\Notifications\NotificationServiceProvider

你还需要在配置文件config/app.phpaliases数组中注册门面Illuminate\Support\Facades\Notification

最后,你可以在User模型或其它你希望接收通知的模型中使用 Illuminate\Notifications\Notifiable trait。

分页

自定义

与之前版本的Laravel相比,Laravel 5.3中自定义分页生成的HTML变得更加简单,你只需要定义一个简单的Blade模板,而不需要定义一个“Presenter”类。自定义分页视图最简单的方式就是通过vendor:publish命令将它们导出到resources/views/vendor目录:

php artisan vendor:publish --tag=laravel-pagination

这个命令会将视图放置到resources/views/vendor/pagination目录,该目录下的default.blade.php文件会对应到默认的分页视图。只需要编辑这个文件就可以自定义分页HTML。

阅读完整的分页文档了解更多实现细节。

队列

配置

在队列配置中,所有配置项expire需要重命名为retry_after,类似的,Beanstalk配置的配置项ttr也需要重命名为retry_after。这一命名更改让配置项的意义更加明确。

闭包

队列闭包不再支持,如果你在应用中将闭包添加到队列,需要将闭包转化为一个类,然后将类实例添加到队列。

集合序列化

Illuminate\Queue\SerializesModels trait 现在适当实例化了Illuminate\Database\Eloquent\Collection。对绝大多数应用来说这不算重大更新,但是,如果你的应用绝对依赖于不是通过队列任务从数据库重新获取的集合,那么就要验证这一改动对应用没有负面影响。

后台woker

在使用Artisan命令queue:work的时候不再需要指定--daemon选项,运行php artisan queue:work命令的时候自动判断在后台运行。如果你想要处理单个任务,可以在命令后加上--once选项:

// Start a daemon queue worker...
php artisan queue:work

// Process a single job...
php artisan queue:work --once

事件数据修改

多个队列任务事件如JobProcessingJobProcessed将不再包含$data属性,你需要更新应用调用$event->job->payload()来获取对应数据。

失败任务表

如果你的应用有了failed_jobs表,需要添加exception字段到这张表,exception字段应该是TEXT类型,用于保存导致队列任务失败的异常字符串。

在传统风格队列任务上序列化模型

通常,Laravel的队列任务通过传递任务实例到Queue::push方法添加到队列,不过,一些应用会通过如下这种传统的方式添加任务到队列:

Queue::push('ClassName@method');

如果你在使用这种语法,Eloquent模型将不再会被自动序列化然后通过队列重新获取,如果你想要Eloquent模型继续被队列自动序列化,需要在任务类上使用Illuminate\Queue\SerializesModelstrait并使用新的方法将任务推送到队列:

Queue::push(new ClassName);

路由

资源路由参数默认是单数

之前版本的Laravel中,使用Route::resource注册的路由参数并没有“单数化”,这可能会在注册路由模型绑定的时候引起一些非预期的行为,例如,给定如下Route::resource调用:

Route::resource('photos', 'PhotoController');

show路由的URI将会定义如下:

/photos/{photos}

在Laravel 5.3中,所有资源路由参数默认都是单数化的,因此,同样调用Route::resource,将会注册URI如下:

/photos/{photo}

如果你想要继续维护之前版本的行为而不是自动单数化资源路由参数,可以在AppServiceProvider中这样调用singularResourceParameters方法:

use Illuminate\Support\Facades\Route;

Route::singularResourceParameters(false);

资源路由名称不再受路由前缀影响

使用Route::resource的时候URI前缀将不再影响分配给路由的路由名称,如果在Route::group中使用Route::resource时调用了指定的prefix选项,则需要检查所有对route辅助函数的调用以验证不再追加URI的prefix到路由名称。

如果这一改动导致同一名称下有两个路由,可以在调用Route::resource的时候使用names选项为给定路由指定一个自定义路由,更多信息请查看完整的路由文档。

验证

表单请求异常

如果一个表单请求验证失败,Laravel现在会抛出一个Illuminate\Validation\ValidationException实例而不是HttpException实例,如果你手动捕获了表单请求的HttpException实例,需要修改catch区块代码为捕获ValidationException

支持空的原生数值

当验证数组、布尔值、整型、数字、字符串时,null不会被当作有效值,除非在约束条件中设置包含nullable

Validate::make($request->all(), [
    'string' => 'nullable|max:5',
]);

学院君

学院君 has written 548 articles

资深PHP工程师,Laravel学院院长