12

初尝 Laravel 5.8

 4 years ago
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.
neoserver,ios ssh client
logo-laravel.svg

几个月前,作为前端开发者,尝试用 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 服务器

重装后恢复 phpstudy 提供的开发环境

环境变量文件 .env

独立的环境配置文件,在这里可以修改数据库驱动,与线上环境区分开。
连接本地的 MySQL 服务:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

Windows 下更新 MySQL 至 5.7.22

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/customersindexcustomers.indexGET/customers/createcreatecustomers.createPOST/customersstorecustomers.storeGET/customers/{id}showcustomers.showGET/customers/{id}/editeditcustomers.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 项为 ebookfile 项为必填
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 项为 ebookfile 项为必填,可以这么编写:

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、支付系统、订单系统等经典设计,都需要去了解。

掌握基本的提问智慧,大部分目前想要实现的东西,别人已经研究过,通过搜索引擎,能较快能找到思路,不必埋头苦造轮子。站在巨人的肩膀上才能看的更远。





About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK