

初尝 Laravel 5.8
source link: http://misaka.im/index.php/archives/52/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

几个月前,作为前端开发者,尝试用 koa2(Node.js) 开发项目,不用过多在意语法语,数据库操作基于 LeanCloud,功能简单,开发难度较低。但 koa2 自身功能集成不高,常用功能需安装各种中间件和社区包得到支持。
PHP 作为 Web开发的热门语言,可以全球说过半数网站都使用了它。 Laravel 作为优雅的 PHP 框架,一直有所耳闻。近些日子阅读了社区翻译的文档,逐渐上手了如何使用这款优质的开源后端框架。
在这之前需要一套开发的基础环境,至少包括:
- PHP 及它的包管理工具 Composer
- HTTP 服务器 Nginx
- 关系型数据库 MySQL
这里我用的是 PhpStudy Windows 2016 版本 ,当然你也可以自行搭建。
创建新项目
传统的方式是从 Git 仓库中克隆到本地。像前端 Vue CLI 一样,优雅的 Laravel 也有自家的手脚架,可快速地创建新项目.
使用 composer 命令安装一个全局安装器:
composer global require laravel/installer
当前路径下创建新项目:
laravel new laravel-project
HTTP 服务器
环境变量文件 .env
独立的环境配置文件,在这里可以修改数据库驱动,与线上环境区分开。
连接本地的 MySQL 服务:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
Artisan 命令行
学习编程的前期,我们还在手动创建源文件。 Laravel 命令行工具带来了一套全新的操作,创建模型、控制器、数据库种子文件,并将有关的功能关联起来,省去了手动新建重命名文件的麻烦。
而在后面会用到更多类似的命令:
创建 TestController
的控制器:
php artisan make:controller TestController
// will be created TestController.pnp in /app/Http/Controllers
创建 Test
的模型,和对应的数据库迁移文件:
php artisan make:model Test -m
一些必不可少的功能被抽象成路由、模板、表单验证等 API 提供给开发者实用,其互相独立又不可分割,组成了现今 Web 后端框架的基础功能。
Nginx 将浏览器请求的 URL 转发到 Laravel 里,这样就能针对不同的路径和查询参数,处理结果给客户端。
默认的路由声明位于 /routes
文件夹中,routes/web.php
中约定了由模板渲染的路由结果:
Route::view('contact', 'contact.create');
view()
函数渲染 resources/views/contact/create.blade.php
模板文件得到 HTML 格式的文档,最后返回给浏览器。
当系统功能过多,路由文件就变得冗余,不妨将资源约定成 RESTFul 风格,Laravel 将自动将其解析成若干个路由:
Route::resource('customers', 'CustomersController');
HTTP MethodURIAcitonRoute nameGET/customers
indexcustomers.indexGET/customers/create
createcustomers.createPOST/customers
storecustomers.storeGET/customers/{id}
showcustomers.showGET/customers/{id}/edit
editcustomers.editPUT/PATCH/customers/{id}
updatecustomers.updateDELETE/customers/{id}
destroycustomers.destroy我们可以在路由文件中处理所有的请求逻辑,随着时间的推移,路由会变得十分拥挤。
Route::get('test', function (){
return 'hello world!';
});
一般控制器文件约定被存放在 app/Http/Controllers
目录中。
同样,使用 Artisan 助手创建控制器文件:
php artisan make:controller CustomersController
将逻辑代码迁移到控制器中:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class CustomersController extends Controller
{
public function index() {
$customers = [
'John Doe',
'Jane Doe',
'Bob The Builder',
];
return view('internals.customers',[
'customers' => $customers
]);
}
}
Route::get('customers', 'CustomersController@index')
->name('customers.index');
当 GET 请求与路由 URI 匹配,CustomersController
控制器中的 index
方法将会被执行,将数据渲染到对应的视图。
Customers
控制器继承框架提供的Controller
基类,这样可以使用 Laravel 提供的控制器功能。
有段时间,我在编写前端代码,使用了若干个流程控制语句 if
来判断用户输入的内容是否为空,是否符合正则或其他格式,最终达到表单校验的需求。
在控制器中我们可以渲染对应的视图,也可获取从客户端发来的 HTTP 请求,一般称为表单数据。
如上一小节所说,继承了 Controller
控制器基类的 BooksController
,它提供了一系列方法去验证请求:
php artisan make:controller BooksController -m -r
同样,在路由文件声明到达控制器的路径和方法:
Route::post('books', 'BooksController@store');
除了控制器,还需要使用 模型 与数据库建立关系,并创建数据库迁移文件:
php artisan make:model Book -m
Book 模型配置 $guarded
属性类似于黑名单机制,经过 模型 过滤后再写入数据库
//app/Book.php
protected $guarded = [];
较难理解的是 MVC 模型在经典模式下的存在价值。
网路请求经由 Nginx 转发到 PHP-FPM ,被框架路由过滤,执行控制器内对应的函数,查询数据表,将结果同步回调到页面渲染函数,返回页面给客户端。
数据库迁移文件替代了我们手动在数据库中添加字段,改变结构。创建关于 Books
表的字段描述,书名和作者:
//database/migrations/2019_09_26_152922_create_books_table.php
class CreateBooksTable extends Migration
{
public function up()
{
Schema::create('customers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('title');
$table->string('author');
$table->timestamps();
});
}
}
别忘了创建完后刷新数据表:
php artisan migrate
同时,为了不让复杂的验证逻辑堆积在 Controller
中,创建一个 StoreCustomer
表单请求类来处理:
php artisan make:request StoreBook
对于表单校检验证规则,Laravel 提供了非常详尽的验证规则,配置 rules
函数返回它们:
title
author
为必填项,- 当
type
项为ebook
时file
项为必填
class StoreBook extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required|min:3',
'author' => 'required',
'type' => '',
'file' => 'required_if:type,ebook'
];
}
public function messages()
{
return [
'title.required' => 'A title is required',
'author.required' => 'A author is required',
];
}
}
校验失败时,可知自定义 messages
函数返回字段提示信息,默认情况下也有提示。
将表单校检分离至 Requests/StoreBook.php
,无需在控制器中写任何验证逻辑。
当数据验证成功后,即可操作 Book 模型 内的 Eloquent ORM API ,完成数据记录。
使用 Book
模型的 create
方法,将数据新增到数据库中:
class BooksController extends Controller
{
public function store(StoreBook $request)
{
$book = Book::create($request ->validated());
return response()->json(['code' => '200', 'msg'=> 'ok']);
}
}
使用 Postman 发出 POST 请求,请求内容为 { "title": "CSAPP": "author": "Randal E.Bryant"}
。返回结果:
{
"code": "200",
"msg": "ok"
}
显然请求通过了 rules()
校验,将内容经过直接 protected $guarded
过滤,再使用 ORM API 写入 books
数据表,返回 ok
。
那提交的内容不符合规则呢?把校验器错误收集起来,经 JSON 格式化后返回给客户端。
在 Requests/StoreBook.php
创建 failedValidation
函数,优化验证失败的返回结果:
//app/Http/Requests/StoreBook.php
protected function failedValidation(Validator $validator)
{
$data = [
'code' => 422,
'msg' => $validator->errors()->first(),
];
$response = new Response(json_encode($data), 422);
throw (new ValidationException($validator, $response))
->errorBag($this->errorBag)
->redirectTo($this->getRedirectUrl());
}
当 title 字段少于 3个英文字符:
{
"code": 422,
"msg": "The title must be at least 3 characters."
}
当 type 字段为 ebook 时,没有提交 file 字段:
{
"code": 422,
"msg": "The file field is required when type is ebook."
}
Laravel Validator 实例支持非常多验证规则,从常规格式到业务逻辑,复杂规则可以使用 Illuminate\Validation\Rule
类来构造规则,好处在于不用手动地编写验证函数或规则字符串。如图片的尺寸比:
use Illuminate\Validation\Rule;
Validator::make($data, [
//'avatar' => 'dimensions:ratio=3/2'
'avatar' => [
'required',
Rule::dimensions()->maxWidth(1000)->maxHeight(500)->ratio(3 / 2),
],
]);
在不熟悉框架的前提下,虽有强大的校检规则,部分开发者仍会通过自行编写的校检函数来解决问题。随着时间推移,加大了后期代码维护难度。所以在大型项目中,代码审核显得尤为重要,保证了项目的良性发展。
HTTP 单元测试
刚入门的开发者,使用各种软件来制造 HTTP 请求,并观察编写的函数是否正常输出。
就像上面的表单校验往往需要调试数次,面对堆积如山的需求时,调试难以保证产出率。如果可以使用编程方式,按照需求编写若干完整请求和结果验证器,便事半功倍。
Laravel 结合了 PHPUnit 并提供一些便利的辅助函数,能很好地编写测试用例,比如上一节 Books
接口。
别忘了修改测试功能的配置文件 phpunit.xml
。使用 laravel_test
数据库,这样能避免与开发数据库内容冲突:
<php>
<server name="TELESCOPE_ENABLED" value="false"/>
<server name="DB_CONNECTION" value="mysql"/>
<server name="DB_DATABASE" value="laravel_test"/>
</php>
同样,创建测试文件 php artisan make:test BooksTest
,并填充一些基本的表单数据:
class BooksTest extends TestCase
{
private function data()
{
return [
'title' => 'Test book title',
'author' => 'Test author',
];
}
}
TestCase 类提供了测试 JSON 的函数,用来发出 HTTP 请求,配合 assert
断言方法,判断请求结果是否符合预期,
比如提交 title
author
字段,正常情况下 Laravel 会返回 200
响应头和对应的 JSON:
public function test_a_book_can_be_added_through_post()
{
$response = $this->json('POST', '/books', $this->data());
$response
->assertStatus(200)
->assertJson([
'code' => '200'
]);
}
进入项目根目录,敲入:
.\vendor\bin\phpunit --filter test_a_book_can_be_added_through_post
. 1 / 1 (100%)
Time: 316 ms, Memory: 16.00 MB
OK (1 test, 2 assertions)
进入 laravel_test
数据库,查询 books
表所有内容:
mysql> SELECT * FROM books;
+----+-----------------+-------------+---------------------+---------------------+
| id | title | author | created_at | updated_at |
+----+-----------------+-------------+---------------------+---------------------+
| 1 | Test book title | Test author | 2019-09-29 08:52:26 | 2019-09-29 08:52:26 |
+----+-----------------+-------------+---------------------+---------------------+
1 rows in set (0.00 sec)
假如提交 title
字段缺失,我们可以建立这样的测试案例:
public function test_a_title_is_required()
{
$response = $this->json('POST', '/books', array_merge($this->data(), ['title' => '']) );
$response
->assertStatus(422)
->assertJson([
'code' => '422'
]);
}
断言返回的 JSON
含有 code: 422
内容,切响应头的状态为 422
。
还有当 type
项为 ebook
时 file
项为必填,可以这么编写:
public function test_the_file_field_is_required_when_type_is_ebook()
{
$response = $this->json('POST', '/books', array_merge($this->data(),[
'title' => '123',
'author' => '123',
'type' => 'ebook'
]));
$response
->assertStatus(422)
->assertJson([
'code' => '422'
]);
}
HTTP 单元测试的颗粒度根据项目内测试压力进行调整,如果前端、测试人员充足,保证表单校验和业务流程。
模板引擎(视图)
它支撑了页面渲染,在修改 blade 文件后编译并缓存起来。如多数前端框架的视图层一样,具有模板继承、扩展、逻辑判断等基础功能。
在前端框架突飞猛进地时代,Laravel 的模板引擎 Blade ,它提供更多后端逻辑功能,使得后端无需转向前后分离的协作模式,也能轻易地实现提交表单、权限控制、布局组件化。
下面展示了典型 Blade 内容:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
@include('nav')
<main class="py-4">
<div class="container">
@yield('content')
</div>
</main>
</div>
</body>
</html>
就像 Vue 一样,在双重 {{ }}
花括号内可以使用表达式,通过编译后最终得到 PHP 文件,缓存在 storage/framework/views
目录。
需要注意常用指令的使用方法:
@include
引入其他模板文件,常用来插入其他一些 HTML 片段
@section
常以 @show
@stop
@overwrite
@append
指令来结束, 不建议使用已废弃的 @endsection
。
@section
与 @yield
都是在母版中定义可替代的区块,在子模板中使用 @section
来扩展区块时,表现并不相同;
@yield
@yield 并不能扩展原来有母版中的内容,使用 @parent
关键字也不能让原有的内容与扩展内容并存
@yield('title', '默认标题')
出现在母版中,第二个参数为默认值。
子模板使用:
@extends('master')
@section('title')
@parent
新的标题
@stop
新的标题
@section
@section
用来继承并扩展原有区块的内容。子模板中使用 @parent
使得母版中原有的内容被保留,然后融合新的内容。
@section('content')
默认的内容
@show
@extends('master')
@section('content')
@parent
扩展的内容
@stop
默认的内容 扩展的内容
@show & @stop
- 建议在定义
@section
时用@show
结尾,替换或扩展时用@stop
结尾 - 解析子模板时遇到
@show
结尾的区块会立即显示内容,然后套用模板继承机制,继续渲染内容
<div id="zoneA">
@section('zoneA')
This is zone A master
@show
</div>
<div id="zoneB">
@section('zoneB')
This is zone B master
@stop
</div>
<div id="zoneC">
@section('zoneC')
This is zone C master
@show
</div>
@extends('master')
@section('zoneA')
This is zone A slave
@stop
@section('zoneB')
This is zone B slave
@stop
@section('zoneC')
This is zone C slave
@show
This is zone C slave
<div id="zoneA">
This is zone A slave
</div>
<div id="zoneB">
</div>
<div id="zoneC">
This is zone C slave
</div>
这种错误的写法导致 zone B
区块丢失,并且使扩展的 This is zone C slave
内容过早出现在页面的首位。
@append
@append
用于多次将内容添加到对应的区块中
@section('content')
content A
@append
@section('content')
content B
@append
@section 的结束指令 @override 在 Laravel 5.8 中没有效果
大部分功能 与 ThinkPHP5 模板引擎 使用备忘 里描述的相似
模板引擎使得后端开发不用过多地学习现代前端的专业技能,在页面渲染上也能得心应手。
Laravel 拥有 Web 后端大部分的基础功能,包括:路由、视图(模板)、数据库操作,还有家喻互晓 MVC 的设计模式。更高级的身份授权、代码调试、等功能,都已提供完善的支持。
根据发展需求,将技术重点放在数据库优化、提供微型服务、自动化部署、促进 DevOps 协作等方面,使后端技术得到提升。
当评估完项目需要时候哪款框架,就必要遵循它的使用方法,甚至了解的设计原理。否则选用框架进行开发,有可能是表面高大上,内部结构不堪入目。
互联网诞生不够50年,其中 Web 发展到了 3.0,为更好服务其他行业,经过无数公司的努力,制定了非常多业界通用的设计和模式,从 RFC 标准到通用协议,除了基本的技术手段,比如 身份验证 JWT、支付系统、订单系统等经典设计,都需要去了解。
掌握基本的提问智慧,大部分目前想要实现的东西,别人已经研究过,通过搜索引擎,能较快能找到思路,不必埋头苦造轮子。站在巨人的肩膀上才能看的更远。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK