Laravel实用技巧

Laravel 作为大名鼎鼎的 PHP Web 框架,有着「过度设计」的美誉(滑稽)。也就免不了附带着一些需要学习和摸索才能掌握的奇技淫巧(好吧其实只需要看看文档就行)。笔者结合自身的开发经验,把值得一提的东西梳理成文,便于读者在使用 Laravel 进行开发时事半功倍。

Composer

已经 9102 年了,Composer 作为 PHP 的包管理工具,相信已经不需要做介绍了。使用 Laravel 的过程中不可避免地会使用到各种各样的第三方包,Laravel 毫无疑问是使用 Composer 进行包管理的,对于国内开发者而言,第一件事显然是把 Composer 换为国内源,只需执行一下命令即可修改全局设置:

$ composer config -g repo.packagist composer https://packagist.phpcomposer.com

调试代码

打印变量

一般使用框架自带的 dd() 方法而不是 PHP 原生方法 print_r()var_dump() 来打印变量信息,因为 dd() 方法可以打印多个任何类型变量:dd($object, 123, [123, 324]);

顺便一提,所谓 dd 的意思是 dump, die,而在最新的 Laravel 6 框架中,新增了 ddd() 方法,它是 dd() 方法的升级版,意思是 dump, die, debug,你可以在这篇文章查看该方法的介绍。

打印 SQL 语句

在使用框架的 ORM 查询构造器时,可以通过 toSql() 方法输出原生 SQL 语句。

SQL 语句中的参数值由于被 ? 代替,可以使用 getBindings() 输出构造器中的查询条件:

<?php

$builder = DB::table('user')->where('id', 1);
$bindings = $builder->getBindings();
$sql = str_replace('?', '%s', $builder->toSql());
$sql = sprintf($sql, ...$bindings);

dd($sql);

数据库

数据库迁移

参考文档:数据库迁移

Laravel 提供了一整套的数据库迁移方案,便于开发使用代码直接创建、修改数据库的表结构,而无需繁琐地书写表结构的 SQL 语句。首先使用如下命令生成迁移文件:

$ php artisan make:migration do_some_migration

然后在 /database/migrations/ 目录下找到对应的迁移文件 2019_08_29_172043_do_some_migration.php,根据业务需求设计表结构:

<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class DoSomeMigration extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// 创建user表
Schema::create('user', function (Blueprint $table) {
$table->increments('id');
$table->timestamps(); // 创建生成时间和修改时间字段
$table->softDeletes(); // 创建软删除字段
});

// 修改post表
Schema::table('post', function (Blueprint $table) {
$table->uuid('user_id')->after('id')->comment('外键关联模型'); // 新增字段
$table->string('name', 50)->change(); // 修改字段属性
$table->renameColumn('from', 'to'); // 字段重命名
$table->dropColumn('votes'); // 删除字段
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('user');
}
}

完成表结构设计之后执行如下命令进行数据库迁移,如果数据库配置无误的话,框架会按照迁移文件自动创建、修改表结构,完成数据库迁移操作:

$ php artisan migrate          // 完成所有未执行的迁移
$ php artisan migrate --force // 强制执行迁移
$ php artisan migrate:rollback // 回滚所有迁移
$ php artisan migrate:refresh // 回滚所有迁移,并重新执行所有迁移,相当于重建数据库

数据库填充

参考文档:数据填充

后端开发最头疼的就是模拟数据了,特别是新建数据库以后还要一条一条记录手动 mock 那就太糟心了。Laravel 自带了一个数据库填充引擎,可以按照工厂填空类进行数据库填充测试数据。这里分三步走战略:

  1. 准备对应模型类和数据填充工厂类
  2. 生成数据填充执行类
  3. 执行填充

准备类文件

创建工厂类之前你务必要先准备好对应的 User 模型,在本例中,User 模型文件位于 /app/User.php,然后创建工厂类 /database/factories/UserFactory.php ,并为每个字段写好数据填充要求:

<?php

use Illuminate\Support\Str;
use Faker\Generator as Faker;

$factory->define(App\User::class, function (Faker $faker) {
return [
'name' => $faker->name,
'email' => $faker->unique()->safeEmail,
'email_verified_at' => now(),
'password' => '$2y$10$TKh8H1.PfQx37YgCzwiKb.KjNyWgaHb9cbcoQgdIVFlYg7B77UdFm', // secret
'remember_token' => Str::random(10),
];
});

生成填充执行类

使用如下命令生成填充执行类文件:

$ php artisan make:seeder UserTableSeeder

修改 /database/seeds/UserTableSeeder.php 文件,约束填充的记录条数:

<?php

use Illuminate\Database\Seeder;

class UserTableSeeder extends CommonSeeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
static $createdAt;

//单独插入一条数据
App\User::insert([
'id' => 1,
'name' => 'admin',
'password' => sha1('ooxxooxx'),
'email' => '',
'remember_token' => str_random(60),
'created_at' => $createdAt ?: $createAt = $this->randDate(),
'updated_at' => $createdAt ?: $createdAt = $this->randDate(),
]);

//生成 4 个用户
factory(App\User::class, 4)->create();
}
}

并在 /database/seeds/DatabaseSeeder.php 从添加需要填充的类文件:

<?php

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$this->call([
PostTableSeeder::class,
UserTableSeeder::class,
]);
}
}

执行填充

执行数据库填充命令收货一堆假数据:

$ php artisan db:seed                             // 执行 DatabaseSeeder.php 内定义的所有数据库填充
$ php artisan db:seed --class=ExampleTableSeeder // 执行指定填充类的数据填充

伪造数据

可以看到上述填充类中使用了 $faker 这个玩意儿,其对应的是一个可堪使用的数据填充工具 Faker,提供了诸如电话号码、地址、邮箱、用户名、文章标题、文章摘要、文章内容、图片链接等一系列的填充项,基本能够覆盖大部分需求。关于中文数据填充的相关操作可以阅读《使用 Laravel 数据填充功能生成中文测试数据

另外,框架自带的 Illuminate\Support\Str 也提供了很多丰富的字符串处理方法可堪使用。

查询构造器

参考文档:查询构造器

这部分没太多可说的,掌握基本的 select()where()whereIn()group()latest()get()paginate() 基本可以完成 80% 的工作了,需要提到的是,由于查询构造器的查询方法返回值不同,这里需要格外注意:

  • find($id) 需要一个 id 并返回一个模型。如果不存在匹配的模型,则返回 null
  • findOrFail($id) 需要一个 id 并返回一个模型。如果不存在匹配的模型,则会引发错误,它会抛出一个 error
  • first() 返回在数据库中找到的第一条记录。如果不存在匹配的模型,则返回 null
  • firstOrFail() 返回在数据库中找到的第一条记录。如果不存在匹配的模型,则会引发错误。它会抛出一个 error

Eloquent ORM

模型

参考文档:查询构造器

在定义模型时,有两个模型类属性比较重要:

protected $guarded = ['id', 'deleted_at'];      //不可批量更新字段
protected $hidden = ['deleted_at']; //查询时不可见字段

上述字段可以视为黑名单属性,对应的白名单属性为:

protected $fillable = ['name'];     //可批量更新字段
protected $visible = ['tag']; //查询时可见字段

软删除

相关操作参见:Eloquent ORM 实例教程 —— 模型删除及软删除相关实现

某些表字段设计为软删除,而非物理删除,在模型内使用软删除,只需两步:

  1. 引用软删除 trait
  2. 增加软删除字段

引用软删除 Trait

首先在目标模型上使用 Illuminate\Database\Eloquent\SoftDeletes trait:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
use SoftDeletes;
}

增加软删除字段

这一部分不再赘述,可以参照前文数据库迁移部分,新增一个软删除字段,默认新增的软删除字段名为 deleted_at

你也可以手动修改数据库表结构增加自定义名称的软删除字段,当然,你需要在对应的模型中使用 DELETED_AT 类常量指定自定义的软删除字段名:

<?php

namespace App;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Flight extends Model
{
use SoftDeletes;
const DELETED_AT = 'custom_delete_colunm';
}

其他说明

一般来说,完成前述设置后已经可以正常使用软删除,当你在模型实例上使用 delete() 方法删除数据实例时,当前日期时间会写入 deleted_at 字段,表示该记录被软删除。同时,查询出来的结果也会自动排除已被软删除的记录。

需要硬删除的话使用 forceDelete() 方法即可。

为了使模型定义更加清晰,不妨在目标模型内设置类成员变量,显式指定 deleted_at 字段的类型:

protected $dates = ['deleted_at'];

当然,如果你不想将该字段的值设为删除日期,也可以对其进行改造,具体改造方式可以参阅《Laravel5软删除(SoftDeletes)的deleted_at改造

修改器

对数据库字段的特殊处理,可以使用 Eloquent 修改器 对入库字段进行预处理,比如姓名首字母大写才能入库等等。

另外,某字段储存为 json 格式,处理时需要转换为 PHP 数组,也只需在模型内声明 $cast 属性加入 array 类型转换,当你访问的时候就会自动被转换为 PHP 数组:

protected $casts = [
'options' => 'array',
];

游标

cursor 方法允许你使用游标遍历数据库,它只执行一次查询。处理大量的数据时, cursor() 方法可以大大减少内存的使用量,比如导入、导出数据时,如果直接 foreach 遍历变量,可能会犹豫变量太大导致内存溢出。此事使用基于迭代器的游标可以大大提升程序的鲁棒性:

foreach (Flight::where('foo', 'bar')->cursor() as $flight) {
//
}

关联模型

参考文档:关联模型

这一部分可以说是 Laravel 的精髓所在。框架设计的模型关联可以将多个模型之间的关联关系在 Model 中定义清楚,便于数据查询和后期维护,特别是在例如列表页、详情页等需求中轻松地获取有关联关系的将数据对象,完全无需自己手动拼接、处理数据。

举两个例子:

  • 在 User 和 Post 模型中绑定关联关系后,就可以使用 $post->comments 来获取该文章对应的全部评论。

  • 在列表查询时,可能需要减少多次查询,避免 1+N 次查询。比如文章列表:

    1. 获取文章
    2. 获取每篇文章的评论

ps. 数据库中文章表为 post,评论表为 comment

如果遍历 $posts 使用关联查询 $post->comments 将造成过多不必要的 SQL 查询。假设该页有 10 条 post 记录,正常情况将执行 1+10=11 条 SQL。为解决这种多查询问题,可以使用预加载来实现最简查询。

使用时,在查询语句中使用 with() 即可,如:

$books = App\Book::with('author')->get();

foreach ($books as $book) {
echo $book->author->name;
}
  • 获取关联对象的数量可以使用关联模型计数withCount() 方法,它将放在结果模型的 {relation}_count 列,如:
$posts = App\Post::withCount('comments')->get();

foreach ($posts as $post) {
echo $post->comments_count;
}

跨域

处理跨域问题可以考虑使用 Laravel CORS 扩展包


暂时就写这么多,上述技巧基本可以涵盖常见的 CURD 需求(滑稽。如果你有其他推荐的奇技淫巧不妨在评论区留言。