Laravel

Laravel

Api

REST 是所有 Web 应用都应该遵守的架构设计指导原则。 Representational State Transfer,翻译是”表现层状态转化”。

面向资源是 REST 最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST 要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。

动作

1
2
3
4
5
GET (SELECT):从服务器检索特定资源,或资源列表
POST (CREATE):在服务器上创建一个新的资源
PUT (UPDATE):更新服务器上的资源,提供整个资源
PATCH (UPDATE):更新服务器上的资源,仅提供更改的属性
DELETE (DELETE):从服务器删除资源

命名

路径又称"终点"(endpoint),表示 API 的具体网址。

在 RESTful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以 API 中的名词也应该使用复数。

举例来说,有一个 API 提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

接口尽量使用名词,禁止使用动词,下面是一些例子。

1
2
3
4
5
6
7
8
GET         /zoos:列出所有动物园
POST /zoos:新建一个动物园
GET /zoos/ID:获取某个指定动物园的信息
PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
DELETE /zoos/ID:删除某个动物园
GET /zoos/ID/animals:列出某个指定动物园的所有动物
DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

再比如,某个 URI 是/posts/show/1,其中 show 是动词,这个 URI 就设计错了,正确的写法应该是/posts/1,然后用 GET 方法表示 show。

版本

应该将 API 的版本号放入 URL。如:

1
https://api.example.com/v1

过滤

如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。 下面是一些常见的参数。

1
2
3
4
5
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page_number=2&page_size=100:指定第几页,以及每页的记录数。
?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1:指定筛选条件

状态码

1
2
3
4
5
6
7
8
9
10
11
12
13
200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT - [DELETE]:用户删除数据成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作
401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
429 Too Many Requests - 由于请求频次达到上限而被拒绝访问
500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

响应格式

从可读性和通用性来讲 JSON 是最好的响应数据格式,下面是一个错误消息响应数据结构。

1
2
3
4
5
6
7
{
'message' => ':message',
'errors' => ':errors',
'code' => ':code',
'status_code' => ':status_code',
'debug' => ':debug'
}
  • message:表示在 API 调用失败的情况下详细的错误信息,这个信息可以由客户端直接呈现给用户
  • errors:参数具体错误,比如字段较对错误内容
  • code:自定义错误码
  • status_code:http 状态码
  • debug:debug 调试信息

错误返回值根据情况进行删减

Postman

postman 可以高效的测试和维护接口。https://www.getpostman.com/apps

Dingo

Dingo Api 是致力于提供给开发者一套工具,帮助你方便快捷的建造你自己的 API。这个包的目标是保持尽可能的灵活,它并不能覆盖所有的情况,也不能解决所有的问题。

官网:https://github.com/dingo/api/

文档:https://github.com/dingo/api/wiki/Configuration

安装组件

1
composer require dingo/api:2.0.0-alpha2

执行下面命令生成配置文件 /config/api.php

1
php artisan vendor:publish

配置说明

配置统一定义在 config/api.php 文档中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#接口围绕:[x]本地和私有环境 [prs]公司内部app使用 [vnd]公开接口
'standardsTree' => env('API_STANDARDS_TREE', 'x')

#项目名称
'subtype' => env('API_SUBTYPE', 'hdcms')

#Api前缀 通过 www.hdcms.com/api 来访问 API。
'prefix' => env('API_PREFIX', 'api')

#api域名
'domain' => env('API_DOMAIN', 'api.hdcms.com'),

#版本号
'version' => env('API_VERSION', 'v1')

#开发时开启DEBUG便于发现错误
'debug' => env('API_DEBUG', false)

prefix 与 domain 只能二选一

接口版本

routes/api.php 文件定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$api = app(\Dingo\Api\Routing\Router::class);

#默认配置指定的是v1版本,可以直接通过 {host}/api/version 访问到
$api->version('v1', function ($api) {
$api->get('version', function () {
return 'v1';
});
});

#如果v2不是默认版本,需要设置请求头
#Accept: application/[配置项 standardsTree].[配置项 subtype].v2+json
$api->version('v2', function ($api) {
$api->get('version', function () {
return 'v2';
});
});

基础控制器

1
php artisan make:controller Api/Controller

修改内容如下

1
2
3
4
5
6
7
8
9
10
namespace App\Http\Controllers\Api;

use Dingo\Api\Routing\Helpers;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller as SysController;

class Controller extends SysController
{
use Helpers;
}

Transformers

Transformers 允许你便捷地、始终如一地将对象转换为一个数组。通过使用一个 transformer 你可以对整数和布尔值,包括分页结果和嵌套关系进行类型转换。

基本使用

一个 transformer 是一个类,它会获取原始数据并将返回一个格式化之后的标准数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
namespace App\Transformers;

use App\User;
use League\Fractal\TransformerAbstract;

class UserTransformer extends TransformerAbstract
{
public function transform(User $user)
{
return [
'id' => $user['id'],
'name' => $user['name'],
'created_at'=> $user->created_at->toDateTimeString()
];
}
}

返回单个数据

1
return $this->response->item(User::find(1),new UserTransformer());

返回集合

1
return $this->response->collection(User::get(),new UserTransformer());

分页数据

1
return $this->response->paginator(User::paginate(2),new UserTransformer());

include

获取文章时我们希望获取文章的栏目数据,include 的特性就非常方便了。

下面是 ContentTransformer 中的定义,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ContentTransformer extends TransformerAbstract
{
# 定义可以include可使用的字段
protected $availableIncludes = ['category'];

public function transform(Content $content)
{
return [
'id' => $content['id'],
'name' => $content['title'],
];
}

public function includeCategory(Content $content)
{
return $this->item($content->category,new CategoryTransformer());
}
}

当我们调用 {host}/api/contents?include=category 接口时,栏目数据也一并会返回

1
return $this->response->paginator(Content::paginate(1),new ContentTransformer());

返回结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"data": [
{
"id": 1,
"name": "后盾人 人人做后盾",
"category": {
"data": {
"id": 2,
"name": "编程"
}
}
}
],
"meta": {
"pagination": {
"total": 100,
"count": 1,
"per_page": 1,
"current_page": 1,
"total_pages": 100,
"links": {
"next": "http://laravel.local.com/api/contents?page=2"
}
}
}
}

响应结果

设置响应状态码

1
2
return $this->response->array(User::get())->setStatusCode(200);
return response()->json(['error' => 'Unauthorized'], 401);

错误响应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 一个自定义消息和状态码的普通错误。
return $this->response->error('This is an error.', 404);

// 一个没有找到资源的错误,第一个参数可以传递自定义消息。
return $this->response->errorNotFound();

// 一个 bad request 错误,第一个参数可以传递自定义消息。
return $this->response->errorBadRequest();

// 一个服务器拒绝错误,第一个参数可以传递自定义消息。
return $this->response->errorForbidden();

// 一个内部错误,第一个参数可以传递自定义消息。
return $this->response->errorInternal();

// 一个未认证错误,第一个参数可以传递自定义消息。
return $this->response->errorUnauthorized('帐号或密码错误');

限制请求数

使用 api.throttle中间件结合 limit、expires 参数可实现接口次数限制。下面是定义在 routes/api.php 路由文件中的示例。

1
2
3
4
5
$api->version('v1', ['namespace' => '\App\Api'], function ($api) {
$api->group(['middleware' => 'api.throttle', 'limit' => 2, 'expires' => 1], function ($api) {
$api->get('user', 'UserController@all');
});
});

限制 1 分钟只能访问 2 次。

身份验证

可以通过 api.auth 路由中间件来启用路由或者路由群组的保护,我们使用下面讲解的 jwt 组件完成接口验证。

在所有的路由上启用

1
2
3
$api->version('v1', ['middleware' => 'api.auth'], function ($api) {
// 在这个版本群组下的所有路由将进行身份验证。
});

特定的路由上启用

1
2
3
4
5
6
7
8
9
$api->version('v1', function ($api) {
$api->get('user', ['middleware' => 'api.auth', function () {
// 这个路由将进行身份验证。
}]);

$api->get('posts', function () {
// 这个路由不会验证身份。
});
});

控制器上进行身份验证

Laravel可以在控制器里启用中间件。您可以在构造函数里使用 middleware 的方法。

1
2
3
4
5
6
7
8
9
10
11
12
class UserController extends Illuminate\Routing\Controller
{
use Helpers;

public function __construct()
{
$this->middleware('api.auth');

// 这个中间件只在 index 中启用
$this->middleware('api.auth', ['only' => ['index']]);
}
...

Jwt

Jwt 是高效简单的接口验证组件,使用非常广泛。

GitHub:https://github.com/tymondesigns/jwt-auth

Packagist:https://packagist.org/packages/tymon/jwt-auth

在线文档: http://jwt-auth.readthedocs.io/en/develop/quick-start/

安装组件

目前 2.0 版本正在开发中还不可以正常使用,所以我们使用 1.0.2。

1
composer require tymon/jwt-auth:1.0.2

生成配置文件

1
php artisan vendor:publish

生成密钥

这是用来给你的 token 签名的钥匙,使用以下命令生成一个密钥:

1
php artisan jwt:secret

这将用 JWT_SECRET=foobar 更新.env 文件

配置说明

JWT 配置文件是 config/jwt.php,下面有部分配置项进行说明:

1
2
3
4
5
#令牌过期时间(单位分钟),设置null为永不过期
'ttl' => env('JWT_TTL', 60)

#刷新令牌时间(单位分钟),设置为null可永久随时刷新
'refresh_ttl' => env('JWT_REFRESH_TTL', 20160)

更新用户模型

首先,您需要在用户模型上实现 Tymon\JWTAuth\Contracts\JWTSubject 契约,它要求您实现两个方法 getJWTIdentifier()getJWTCustomClaims()

下面的示例应该能让您了解这可能是什么样子的。显然,您应该根据需要进行任何更改,以满足自己的需要。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php

namespace App;

use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable implements JWTSubject
{
use Notifiable;

/**
* 获取将存储在JWT主题声明中的标识符.
* 就是用户表主键 id
*
* @return mixed
*/
public function getJWTIdentifier()
{
return $this->getKey();
}

/**
* 返回一个键值数组,其中包含要添加到JWT的任何自定义声明.
*
* @return array
*/
public function getJWTCustomClaims()
{
return [];
}
}

配置验证守卫

修改 config/auth.php 文件以使用 jwt 保护来为接口身份验证提供支持。

1
2
3
4
5
6
7
8
9
10
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
]

修改 dingo 配置文件 config/api.php 文件中的身份验证提供者

1
2
3
'auth' => [
'jwt' => \Dingo\Api\Auth\Provider\JWT::class,
],

验证操作

路由定义

1
2
3
4
5
6
$api = app(\Dingo\Api\Routing\Router::class);
$api->version('v1', ['namespace' => 'App\Http\Controllers\Api',], function ($api) {
$api->post('login', 'AuthController@login');
$api->get('logout', 'AuthController@logout');
$api->get('me', 'AuthController@me');
});

控制器定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
class AuthController extends Controller
{
public function __construct()
{
// 除login外都需要验证
$this->middleware('auth:api', ['except' => ['login']]);
}

//登录获取token
public function login()
{
$credentials = request(['email', 'password']);

if (!$token = auth('api')->attempt($credentials)) {
return $this->response->errorUnauthorized('帐号或密码错误');
}

return $this->respondWithToken($token);
}

//获取用户资料
public function me()
{
return response()->json(auth('api')->user());
}

//销毁token
public function logout()
{
auth('api')->logout();

return response()->json(['message' => 'Successfully logged out']);
}

//刷新token
public function refresh()
{
return $this->respondWithToken(auth('api')->refresh());
}

//响应token
protected function respondWithToken($token)
{
return response()->json([
'access_token' => $token,
'token_type' => 'bearer',
'expires_in' => auth('api')->factory()->getTTL() * 60,
]);
}
}

使用令牌

当请求需要验证的 api 时必须带有 token,下面是使用 header 头携带令牌数据

1
Authorization: Bearer 令牌数据

Laravel

介绍

Laravel 是一套简洁、优雅的 PHP Web 开发框架(PHP Web Framework)。它可以让你从面条一样杂乱的代码中解脱出来;它可以帮你构建一个完美的网络 APP,而且每行代码都可以简洁、富于表达力。

Laravel 是 基于 PHP 语言的 Web 开源框架,采用了 MVC 的架构模式,在 2011 年 6 月正式发布了首个版本。 深度集成 PHP 强大的扩展包(Composer)生态与 PHP 开发者广大的受众群,让 Laravel 在发布之后的短短几年时间得到了极其迅猛的发展。我们通过 Google Trends 提供的趋势图(图 1.1)可以看出,Laravel 框架在过去十年,其增长速度迅猛。

版本

Lts

Long-Term Support,长期技术支持(版本),该版本拥有较长的维护周期,是商业项目优先考虑使用的版本。

常规版

提供 6 个月的 bug 修复,维护周期短但功能较新,学习为了使用新功能可采用这个版本。

全新安装

使用 Composer 安装 Laravel 框架

1
composer create-project --prefer-dist laravel/laravel blog

安装完 Laravel 之后,你必须将 web 服务器更目录指向 public 目录。该目录下的 index.php 文件将作为所有进入应用程序的 HTTP 请求的前端控制器。

需要修改 .env 配置文件中的 APP_NAMEAPP_URL

1
2
APP_NAME=Laravel
APP_URL=http://laravel.local.com

MySQL 低版本

如果你是在版本低于 5.7.7 的 MySQL release 上创建索引,那就需要你手动配置迁移生成的默认字符串长度。

AppServiceProvider.php 文件里的 boot 方法里设置

1
2
3
4
public function boot()
{
\Schema::defaultStringLength(191);
}

下载团队项目

检出别人的项目时,因为.env、verdor、node_moduels 文件或目录是不提交到版本库的,造成检出后项目不能正常运行。需要以下几步处理:

  1. composer install
  2. cnpm install
  3. 复制 .env.example 文件为 .env 文件
  4. 执行 php artisan key:generate 生成应用秘钥
  5. 修改 .env 配置文件其他数据为你网站数据(比如数据库配置等)

基本使用

路由

Laravel 的路由类型很多,先看路由的基础使用

修改 routes/web.php 路由表文件

1
2
3
Route::get('/home', 'TestController@home');
Route::get('/lists', 'TestController@lists');
Route::get('/show', 'TestController@show');

控制器

创建控制器命令

1
php artian make:controller TestController

创建的控制器内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function home(){
return __METHOD__;
}

public function lists(){
return __METHOD__;
}

public function show(){
return __METHOD__;
}
}

浏览器中访问http://laravel.kk/lists显示内容如下

1
App\Http\Controllers\TestController::lists

以上就是 laravel 中基本路由与控制器的操作流程。

视图

视图就是我们显示的页面,视图保存在 resources/views 文件夹中。

继承

模版继承是为了公用相同页面的代码,减少代码冗余

下面定义 views/layouts/master.blade.php 文件

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>
@yield('title', '后盾人')
</title>
</head>
<body>
@yield('content')
</body>
</html>

然后在子模板中引入父模板,并使用 section 替换父模板中 yield 定义的占位内容。

1
2
3
4
5
@extends('layouts.master')
@section('title','后盾人 - 首页')
@section('content')
<h1>首页1</h1>
@endsection

include

include 用于加载外部模板

1
@include('user.show')

被引入的视图会继承父视图中的所有数据,同时也可以向引入的视图传递额外的数组数据:

1
@include('view.name', ['some' => 'data'])

includeIf

当然,如果尝试使用 @include 去引入一个不存在的视图,Laravel 会抛出错误。如果想引入一个可能存在或可能不存在的视图,就使用 @includeIf 指令:

1
@includeIf('view.name', ['some' => 'data'])

includeWhen

如果要根据给定的布尔条件 @include 视图,可以使用 @includeWhen 指令:

1
@includeWhen($boolean, 'view.name', ['some' => 'data'])

stack

Blade 可以被推送到在其他视图或布局中的其他位置渲染的命名堆栈。这在子视图中指定所需的 JavaScript 库时非常有用:

父模版

1
2
3
4
<head>
<!-- Head Contents -->
@stack('scripts')
</head>

子模版

1
2
3
@push('scripts')
<script src="/example.js"></script>
@endpush

component&slot

组件相比 @extends 更灵活些,下面是定义一个 modal 组件。

组件中的变量可以在调用组件时传参数

1
@component('components.modal',['title'=>'你好','url'=>route('home')])

也可以使用 slot 标签赋值

1
2
3
@slot('footer')
<button type="button" data-dismiss="modal" class="btn btn-secondary md-close">cancel</button>
@endslot

示例

定义一个模态框组件 view/components/modal.blade.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<form action="{{$url}}" method="post" {!!isset($id)?"id=\"$id\"":''!!}>
@csrf
@isset($method) @method($method) @endif
<div id="form-bp1" tabindex="-1" role="dialog" class="modal fade colored-header colored-header-primary">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header modal-header-colored">
<h3 class="modal-title">
@isset($title) {{$title}} @endisset
</h3>
<button type="button" data-dismiss="modal" aria-hidden="true" class="close md-close"><span class="mdi mdi-close"></span></button>
</div>
<div class="modal-body">
{{$slot}}
</div>
<div class="modal-footer">
@isset($footer)
{{$footer}}
@else
<button type="button" data-dismiss="modal" class="btn btn-secondary md-close">关闭</button>
<button type="button" data-dismiss="modal" class="btn btn-primary md-close">保存</button>
@endif
</div>
</div>
</div>
</div>
</form>

模板中调用 modal 组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@component('components.modal',['title'=>'你好','url'=>route('home'),'method'=>'PUT'])
<div class="form-group row">
<label for="inputText3" class="col-12 col-sm-3 col-form-label text-sm-right">
Input Text
</label>
<div class="col-12 col-sm-12 col-lg-6">
<input id="inputText3" type="text" class="form-control form-control-sm">
</div>
</div>
@slot('footer')
<button type="button" data-dismiss="modal" class="btn btn-secondary md-close">cancel</button>
<button type="button" data-dismiss="modal" class="btn btn-primary md-close">save</button>
@endslot
@endcomponent

动态视图目录

有时我们需要经常改变视图目录

1
2
$finder = app('view')->getFinder();
$finder->prependLocation(public_path('templates/'));

脚手架

安装 cnpm 使用国内镜像,安装速度快

1
npm install -g cnpm --registry=https://registry.npm.taobao.org

根据 packagist.json 安装前端库

1
2
cnpm install
cnpm install cross-env

我们需要在 resources/assets 目录下的 js 与 css 目录中编写前端文件。

执行编译

需要执行编译操作,生成可供浏览器访问的文件,默认生成在 public/css 与 public/js 目录中

1
npm run dev

引入编译好的样式文件

1
<link rel="stylesheet" href="/css/app.css">

文件监听

每次修改都手动编译效率很低,执行以下命令后 Webpack 会在检测到文件更改时自动重新编译资源:

1
npm run watch

在某些环境中,当文件更改时,Webpack 不会更新。如果系统出现这种情况,请考虑使用 watch-poll 命令:

1
npm run watch-poll

Bootstrap

Laravel6.x 引入前端框架 Bootstrap & jQuery

1
2
3
4
5
6
cd /path/of/your/project
composer require laravel/ui:^1.0 --dev # laravel6
php artisan ui bootstrap
php artisan ui bootstrap --auth
npm install
npm run dev

在 app.scss 文件中定义:

1
@import '~bootstrap/scss/bootstrap';

路由别名

进入路由跳转使用 /home 等形式可以正常进行,但如果我们有很多页面 使用了这种方式,后期我们将 /home 更改为了 /root,就要修改多个页面,显然不是很方式 。这时我们可以为路由起别名。

1
Route::get('/home', 'TestController@home')->name('home');

页面中使用调用即可

1
<a class="nav-link" href="{{ route('home') }}">网站首页</a>

数据迁移

迁移就像是数据库的版本控制, 允许团队简单轻松的编辑并共享应用的数据库表结构。

迁移文件默认保存在 database/migrations 文件夹中。

如果使用 homestead 配置,要保证.env 配置正确,并保证主机客户端连接正常

运行迁移

使用 Artisan 命令 migrate 方法来运行所有未完成的迁移:

1
php artisan migrate

回滚迁移

若要回滚最后一次迁移, 可以使用 rollback 命令。 此命令将回滚最后一次“迁移”的操作,其中可能包含多个迁移文件:

1
php artisan migrate:rollback

关键外键约束

下面是文章表 category_id 与栏目表的 id 关联约束设置,当栏目删除时栏目下的所有文章自动删除。

1
2
$table->unsignedInteger('category_id')->comment('栏目');
$table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade');

Seeder 自动填充测试数据

1
2
3
4
5
6
# 运行填充器
php artisan db:seed
# 单独运行某个填充器要加上--class=ArticleTableSeeder
php artisan db:seed --class=ArticleTableSeeder
# 回滚并重新运行数据迁移
php artisan migrate:refresh --seed

模型

Laravel 的 Eloquent ORM 提供了漂亮、简洁的 ActiveRecord 实现来和数据库交互。每个数据库表都有一个对应的「模型」用来与该表交互。你可以通过模型查询数据表中的数据,并将新记录添加到数据表中

定义模型

首先,创建一个 Eloquent 模型,默认生成的模型通常放在 app 目录中。

创建模型实例的最简单方法是使用 Artisan 命令 make:model

1
php artisan make:model Article

如果要在生成模型时生成 数据库迁移 ,可以使用 --migration-m 选项:

1
php artisan make:model Article -m

数据库表名称

可以通过在模型上定义 table 属性,来指定自定义数据表。如果不指定时使用模型名加 s,如 Article 对应表名为 articles

1
protected $table = 'articles';

时间戳

默认情况下,Eloquent 会默认数据表中存在 created_atupdated_at 这两个字段。如果你不需要这两个字段,则需要在模型内将 $timestamps 属性设置为 false

1
2
// 该模型是否被自动维护时间戳
public $timestamps = false;

本地作用域

本地作用域能定义通用的约束集合以便在应用中复用。

1
2
3
4
5
6
7
8
9
10
11
12
class User extends Model
{
/**
* 查询用户活跃类型 1 活跃 2 不活跃
*
* @return \Illuminate\Database\Eloquent\Builder
*/
public function scopeActive($query,$type=1)
{
return $query->where('active', $type);
}
}

使用方法

1
$users = App\User::active(1)->get();

associate

当更新 belongsTo 关联时,可以使用 associate 方法。此方法将会在子模型中设置外键:

1
2
3
$account = App\Account::find(10);
$user->account()->associate($account);
$user->save();

软删除

软件删除不是真正删除数据,只是在表中更改 deleted_at 状态完成,步骤如下:

修改模型

在模型上使用 Illuminate\Database\Eloquent\SoftDeletes trait 并把 deleted_at 字段加入 $dates 属性:

1
2
3
4
5
6
7
8
...
use Illuminate\Database\Eloquent\SoftDeletes;

class Video extends Model
{
use SoftDeletes;
...
}

修改迁移文件

1
2
3
Schema::create('videos', function (Blueprint $table) {
$table->softDeletes();
});

现在,模型调用 delete 方法,当前日期时间会写入 deleted_at 字段。同时,查询出来的结果也会自动剔除软删除的模型。

包括软删除的模型

1
App\Videos::withTrashed()->get();

withTrashed 方法也可以用在关联查询:

只检索软删除的模型

1
App\Videos::onlyTrashed()->get();

恢复软删除模型

1
App\Videos::get()->restore();

永久删除

1
$videos->forceDelete();

基本上软删除都可以用在关联操作中

Laravel & Lumen 之 Eloquent ORM 使用速查-高级部分

https://segmentfault.com/a/1190000005792734

登录历史跳转

使用系统 auth 验证时,会自动 记录当前的 url,然后登录成功后使用以下代码就会跳转到来源地址。

1
return redirect()->intended('/');

inteded 方法参数默认为 / ,即没有历史来源时的跳转地址。

如果是自定义的验证,则需要使用以下代码记录来源地址。登录成功后还是使用上面代码跳转就可以了。

1
return redirect()->guest(route('login'))->with('error', '请登录后操作');

guest 参数必填即登录地址。

如果想自行定义登录后的回调地址使用下面方法

1
session(['url.intended'=>$request->getRequestUri()]);

分页

1
$users = User::paginate(10);

模板中显示分页列表

1
$users->links();

分页时传递 GET 参数

1
$users->appends(Request::except('page'))->links()

监听 SQL

1
2
3
DB::enableQueryLog();
执行语句
dd(DB::getQueryLog());

TINKER

使用 laravel 提供的 tinker 可以方便的在命令行进行调试。

进入 tinker 环境

1
php artisan tinker

新增用户练习

1
2
3
>>> use \App\User
>>> $user = new User
>>> $user->create(['name'=>'kings','password'=>bcrypt('123'),'email'=>'xxx@qq.com'])

bcrypt 是用来对密码进行加密

查看用户

1
2
>>> User::first()
>>> User::all()

修改记录

下面使用 save 方法操作

1
2
3
>>> $user = User::first()
>>> $user->name='后盾人网站'
>>> $user->save()

下面使用 update 批量更新

1
2
>>> $user = User::first()
>>> $user->update(['name'=>'后盾人','password'=>bcrypt('admin')])

路由参数

比如我们定义下面的路由规则,其中 {user} 和 {name} 为路由中的参数变量

1
Route::get('/user/{user}/{name}','UserController@show')->name('user.show');

控制器中获取参数方式如下:

1
2
3
4
5
6
7
8
<?php namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function show($user,$name){
dd($user.'='.$name);
}
}

隐性路由模型绑定

Laravel 会自动解析定义在路由或控制器行为中与类型提示的变量名匹配的路由段名称的 Eloquent 模型。例如:

1
2
3
Route::get('api/users/{user}', function (App\User $user) {
return $user->email;
});

在这个例子中,由于 $user 变量被类型提示为 Eloquent 模型 App\User,变量名称又与 URI 中的 {user} 匹配,因此,Laravel 会自动注入与请求 URI 中传入的 ID 匹配的用户模型实例。如果在数据库中找不到对应的模型实例,将会自动生成 404 异常。

资源控制器

Laravel 遵从 RESTful 架构的设计原则,将数据看做一个资源。Laravel 资源路由将典型的「CRUD」路由分配给具有单行代码的控制器。

使用 Artisan 命令 make:controller 来快速创建控制器:

1
php artisan make:controller ArticleController --resource

接下来,你可以给控制器注册一个资源路由:

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

资源控制器方法说明:

动作 URI 行为 路由名称
GET /photos index photos.index
GET /photos/create create photos.create
POST /photos store photos.store
GET /photos/{photo} show photos.show
GET /photos/{photo}/edit edit photos.edit
PUT/PATCH /photos/{photo} update photos.update
DELETE /photos/{photo} destroy photos.destroy

可以使用 php artisan route:list 查看已经定义的路由

指定资源模型

如果你使用了路由模型绑定,并且想在资源控制器的方法中使用类型提示,你可以在生成控制器的时候使用 --model 选项:

1
php artisan make:controller PhotoController --resource --model=Photo

伪造表单方法

因为 HTML 表单不能生成 PUTPATCH 或者 DELETE 请求,所以你需要添加一个隐藏的 _method 输入字段来伪造这些 HTTP 动作。辅助函数 method_field 可以帮你创建这个字段:

1
{{ method_field('PUT') }}

表单验证

下面是控制器中的 store 方法

1
2
3
4
5
6
7
8
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// 文章内容是符合规则的,存入数据库
}

常用规则

规则 说明说明 示例
confirmed 验证的字段必须和 foo_confirmation 的字段值一致。例如,如果要验证的字段是 password,输入中必须存在匹配的 password_confirmation 字段。
size 验证的字段必须具有与给定值匹配的大小。对于字符串来说,value 对应于字符数。对于数字来说,value 对应于给定的整数值。对于数组来说, size 对应的是数组的 count 值。对文件来说,size 对应的是文件大小(单位 kb )。
max 验证中的字段必须小于或等于 value。字符串、数字、数组或是文件大小的计算方式都用 size 方法进行评估。
min 验证中的字段必须具有最小值。字符串、数字、数组或是文件大小的计算方式都用 size 方法进行评估。
unique unique:table,column,except,idColumn验证的字段在给定的数据库表中必须是唯一的。如果没有指定 column,将会使用字段本身的名称 'email' => 'unique:users,email_address'
required 验证的字段必须存在于输入数据中,而不是空。如果满足以下条件之一,则字段被视为「空」: 该值为 null. 该值为空字符串。 该值为空数组或空的 可数 对象。 该值为没有路径的上传文件。
email 验证的字段必须符合 e-mail 地址格式。
sometimes 只有在该字段存在时, 才对字段执行验证
nullable 如果你不希望验证程序将 null 值视为无效的,那就将「可选」的请求字段标记为 nullable,也可以理解为有值时才验证。
1
2
3
4
5
$request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'publish_at' => 'nullable|date',
]);

在这个例子里,我们指定 publish_at 字段可以为 null 或者一个有效的日期格式。如果 nullable 的修饰词没有被添加到规则定义中,验证器会认为 null 是一个无效的日期格式。

更多验证规则使用时请查阅手册 https://laravel.com/docs/6.x/validation

显示验证错误

如果表单验证失败,laravel 会向分配错误信息到 $errors 中,那么我们就可在模板中使用以下代码展示验证错误。

1
2
3
4
5
6
7
8
9
@if (count($errors) > 0)
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

表单验证请求

面对更复杂的验证情境中,你可以创建一个「表单请求」来处理更为复杂的逻辑。

创建验证请示

1
php artisan make:request StoreBlogPost

下面是一个示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class UserRequest extends FormRequest
{
public function authorize()
{
return true;
}

public function rules()
{
return [
'name' => 'required|between:5,20',
'email' => "required|email|unique:users,email,".\Auth::user()->id,
'introduce'=>"required|max:80|min:10",
'avatar'=>'mimes:jpeg,bmp,png|dimensions:min_width=300,min_height=200'
];
}

public function messages()
{
return [
'name.required' => '昵称 不能为空',
'name.between'=>'昵称 必须介于 :min 与 :max 之间'
];
}
}

表单函数

csrf

laravel (跨站请求伪造),Laravel 为了安全考虑,会让我们提供一个 token(令牌)来防止我们的应用受到 CSRF(跨站请求伪造)的攻击。放置在表单(form)体内。

1
{{csrf_field()}}

old

old函数获取一次性存放在 session 中的值,用在表单的 value 属性中。这样当我们输出错误回调页面时,原来输入的值还存在。

1
<input type="text" class="form-control" name="name" value={{old('name')}}>

表单请求验证

面对更复杂的验证情境中,你可以创建一个「表单请求」来处理更为复杂的逻辑

1
php artisan make:request StoreBlogRequest

新生成的类保存在 app/Http/Requests 目录下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class UserRequest extends FormRequest
{
// 权限验证
public function authorize()
{
return true;
}
// 验证规则
public function rules()
{
return [
'name'=>'required|max:20',
'email'=>'required|email|unique:users|max:255',
'password'=>'required|confirmed|min:5',
];
}
// 提示信息
public function messages()
{
return [
'name.required' => '名称不能为空',
'email.required' => '邮箱不能为空',
];
}
}

在控制器的方法中依赖注入就可以使用了

1
2
public function store(UserRequest $request){
}

重定向

redirect 函数返回一个 HTTP 重定向响应,如果调用时没有传入参数则返回 redirector 实例:

1
2
return redirect('/home');
return redirect()->route('route.name');

传递路由参数

1
2
$user = User::first()
return redirect()->route('user.show',[$user]);

假设路由规则为 Route::get('user/show/{user}') ,上例中的[$user]会自动提出 $user 模型对象的主键做为路由参数。

闪存数据

有时候你仅想在下一个请求之前在 Session 中存入数据,你可以使用 flash 方法。使用这个方法保存在 Session 中的数据,只会保留到下个 HTTP 请求到来之前,然后就会被删除。闪存数据主要用于短期的状态消息:

1
session()->flash('status', 'Task was successful!');

判断与获取闪存数据

1
2
3
@if (session()->has($msg))
{{session()->get($msg)}}
@endif

公共提示消息

开发中经常用到消息提示,比如登录成功、退出成功等。使用 闪存数据定义一个公共的提示信息组件,就可以解决这个问题。

控制器

1
2
3
4
5
public function store(UserRequest $request)
{
session()->flash('success','注册成功,欢迎回家');
return redirect()->route('user.show',[$user]);
}

公共消息模板 resource/view/layouts/_message.blade.php

1
2
3
4
5
6
7
@foreach (['success','error','danger'] as $msg)
@if (session()->has($msg))
<div class="alert alert-{{$msg}}">
{{session()->get($msg)}}
</div>
@endif
@endforeach

需要用到的页面能过 @include 引入即可

1
@include('layouts._message')

登录验证

使用 Auth::attempt 可以进行用户手动登录

1
2
3
4
5
6
7
public function authenticate()
{
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// 认证通过...
return redirect()->intended('dashboard');
}
}

重定向器上的 intended 方法将重定向到用户尝试访问的 URL。如果该 URL 无效,会给该方法传递回退 URI。

传递历史表单,withInput 用于把历史表单返回给页面,这样页面中就可以使用 old() 函数显示历史输入数据了。

1
return redirect()->back()->withInput();

完整示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LoginController extends Controller
{
public function store(Request $request){
$user = $this->validate($request,[
'email'=>'required',
'password'=>'required',
]);

if(Auth::attempt($user)){
return redirect('/');
}else{
session()->flash('danger','邮箱 或密码错误');
return redirect()->back()->withInput();
}
}
}

判断用户是否已经登录

1
Auth::check();

如果需要将现有用户进行登录,可以使用用户实例调用 login 方法。可以实现用户注册完就登录。

1
2
$user = User::first();
Auth::login($user);

用户退出

1
2
3
4
5
public function logout(){
Auth::logout();
session()->flash('success','退出成功');
return redirect()->route('login');
}

资源路由传参

下面是资源路由中生成编辑资源 url 的方法。

1
{!! route('user.edit',$user->id) !!}

历史跳转

intended 方法将重定向到用户尝试访问的 URL。如果该 URL 无效,会给该方法传递回退 URI。比如当用户编辑资料时,但用户没有登录,会先跳到用户登录页面。使用 intended 方法后登录成功后会回调到,编辑资料页面。如果没有历史,则跳转到 intended 指定的参数路由。

1
return redirect()->intended('home');

模型事件

Eloquent 的模型触发了几个事件,可以在模型的生命周期的以下几点进行监控: retrievedcreatingcreatedupdatingupdatedsavingsaveddeletingdeletedrestoringrestored。事件能在每次在数据库中保存或更新特定模型类时轻松地执行代码。

如果要给某个模型监听很多事件,则可以使用观察器将所有监听器分组到一个类中。观察器类里的方法名应该对应 Eloquent 中你想监听的事件。 每种方法接收 model 作为其唯一的参数。Laravel 没有为观察器设置默认的目录,所以你可以创建任何你喜欢你的目录来存放:

定义事件

下面是 User 模型的观察器

1
2
3
4
5
6
7
8
9
<?php
namespace app\Observers;
use App\User;
class UserObserver{
public function creating(User $user){
$user->active_token = str_random(20);
}
public function saving(User $user){}
}

声明事件

在 AppServiceProvicer 服务提供者的 boot 方法执行以下代码

1
User::observe(UserObserver::class)

或者在模型中定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
namespace App;

use App\Observers\UserServer;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
...
protected static function boot()
{
parent::boot();
User::observe(UserServer::class);
}
}

权限策略

策略是在特定模型或者资源中组织授权逻辑的类。例如,针对用户的更新、修改操作就可以使用策略进行权限控制。

创建策略

1
php artisan make:policy UserPolicy --model=User

下面是一个策略类中的方法,$user为当前登录用户,$model 为调用策略时传递的用户模型实例。表示如果当前登录用户与用户实例相同可以更新。

1
2
3
4
public function update(User $user, User $model)
{
return $user->id == $model->id;
}

声明策略

app/Providers/AuthServiceProvider.php 类的 $policies 属性中声明策略

1
2
3
4
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
'App\User'=>UserPolicy::class
];

控制器中使用策略

1
2
$this->authorize('update', $user);
#后续业务代码..

友好显示

当开启 DEBUG 时会抛出异常,关注后并定义 403.blade.php 模板后可以显示友好的提示。

1
errors/403.blade.php

邮件

配置

下面是针对 qq 邮箱的配置,其他邮箱请自行参考。

登录 mail.qq.com 你的邮箱。查找 设置>帐户>POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务 开启 pop3 并获取授权码。

可以在 config/mail.php 文件或.env 开发文件中设置,下面是.env 中的配置项

1
2
3
4
5
6
7
MAIL_DRIVER=smtp
MAIL_HOST=smtp.qq.com
MAIL_PORT=25
MAIL_USERNAME=1028504601@qq.com
MAIL_FROM_ADDRESS=1028504601@qq.com
MAIL_PASSWORD=a334an2qgnjnoujbeaajj
MAIL_ENCRYPTION=tls

MAIL_PASSWORD 为 QQ 邮箱提供的摄权码

Mailable

在 Laravel 中,每种类型的邮件都代表一个「Mailable」对象。这些对象存储在 app/Mail 目录中。如果在你的应用中没有看见这个目录,别担心,在首次使用 make:mail 命令创建 Mailable 类时这个目录会被创建。

1
php artisan make:mail regMail
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class RegMail extends Mailable
{
use Queueable, SerializesModels;
//公共属性可以模板中直接使用
public $user;
//构造函数
public function __construct($user)
{
$this->user = $user;
}

public function build()
{
return $this->view('mails.reg');
}
}

发送

执行以下代码就可以发送邮件了。

1
\Mail::to(User::first())->send(new \App\Mail\RegMail(User::find(1)));

to:指发送给谁,字段中必须存在 email 和 name,send: 指使用的邮件处理器

集合

contains

contains 方法判断集合是否包含给定的项目

1
2
3
4
5
6
$collection = collect(['name' => 'Desk', 'price' => 100]);
$collection->contains('Desk');
// true

$collection->contains('New York');
// false

pluck

pluck 方法可以获取集合中给定键对应的所有值:

1
2
3
4
5
6
7
$collection = collect([
['product_id' => 'prod-100', 'name' => 'Desk'],
['product_id' => 'prod-200', 'name' => 'Chair'],
]);
$plucked = $collection->pluck('name');
$plucked->all();
// ['Desk', 'Chair']

slice

slice 方法返回集合中给定值后面的部分:

1
2
3
4
$collection = collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
$slice = $collection->slice(4);
$slice->all();
// [5, 6, 7, 8, 9, 10]

has

has 方法判断集合中是否存在给定的键:

1
2
3
$collection = collect(['account_id' => 1, 'product' => 'Desk']);
$collection->has('product');
// true

each

each` 方法将迭代集合中的内容并将其传递到回调函数中:

1
2
3
$collection = $collection->each(function ($item, $key) {
//
});

count

count 方法返回该集合内的项目总数:

1
2
$collection = collect([1, 2, 3, 4]);
$collection->count();

多对多关联

attach

我们假设一个用户可以拥有多个角色,并且每个角色都可以被多个用户共享。给某个用户附加一个角色是通过向中间表插入一条记录实现的,可以使用 attach 方法完成该操作:

1
2
$user = App\User::find(1);
$user->roles()->attach($roleId);

detach

有时也需要移除用户的角色。可以使用 detach移除多对多关联记录。detach 方法将会移除中间表对应的记录;但是这 2 个模型都将会保留在数据库中:

1
2
3
4
5
// 移除用户的一个角色...
$user->roles()->detach($roleId);

// 移除用户的所有角色...
$user->roles()->detach();

sync

sync 方法接收一个 ID 数组以替换中间表的记录。中间表记录中,所有未在 ID 数组中的记录都将会被移除。所以该操作结束后,只有给出数组的 ID 会被保留在中间表中:

1
$user->roles()->sync([1, 2, 3]);

如果你不想移除现有的 ID,可以使用 syncWithoutDetaching 方法:

1
$user->roles()->syncWithoutDetaching([1, 2, 3]);

allRelatedIds

allRelatedIds 用来获取关联模型的 ID 集合。

1
$user->roles()->allRelatedIds();

toggle

toggle 方法用于「切换」给定 ID 数组的附加状态。 如果给定的 ID 已被附加在中间表中,那么它将会被移除,同样,如果如果给定的 ID 已被移除,它将会被附加:

1
$user->roles()->toggle([1, 2, 3]);

消息通知

创建通知

1
php artisan make:notification FindPasswordNotify

这个命令会在 app/Notifications 目录下生成一个新的通知类

数据库通知

database 通知渠道在一张数据库表里存储通知信息。该表以自定义的 JSON 格式,存储如通知类型等描述通知的信息。需要先创建一个数据库表来存放这些通知。

1
2
3
php artisan notifications:table

php artisan migrate

修改通知类的 via 方法为数据库通知

1
2
3
4
public function via($notifiable)
{
return ['database'];
}

然后定义 toArray 方法返回用于储存到数据表中的通知数据。

存取通知

Laravel 的默认模型 App\User 已经引入了 Trait Illuminate\Notifications\Notifiable ,它包含了一个 Eloquet 关系 notifications ,可以为实体返回通知。

1
2
3
4
5
$user = App\User::find(1);

foreach ($user->notifications as $notification) {
echo $notification->type;
}

如果你仅仅想检索所有「未读」通知,你需要使用 unreadNotifications 关系。

1
2
3
4
5
$user = App\User::find(1);

foreach ($user->unreadNotifications as $notification) {
echo $notification->type;
}

通知标记已读

1
2
3
4
5
$user = App\User::find(1);

foreach ($user->unreadNotifications as $notification) {
$notification->markAsRead();
}

你可以直接使用 markAsRead 方法操作一个通知集合,而不是遍历处理每个通知:

1
$user->unreadNotifications->markAsRead();

邮件通知

修改通知类中的 toMail__construct 方法

1
2
3
4
5
6
7
8
9
10
11
12
public function __construct($token)
{
$this->token=$token;
}
public function toMail($notifiable)
{
return (new MailMessage)
->subject('验证邮箱')
->greeting(config('app.name'))->line('点击下面的链接重设密码')
->action('重置密码',url(route('password.change', $this->token)))
->line('感谢使用后盾人');
}

自定义模板

你可以通过发布通知包的资源来修改 HTML 模板和纯文本模板。运行这个命令后,邮件通知模板就被放在了 resources/views/vendor/notifications 文件夹下:

1
php artisan vendor:publish --tag=laravel-notifications

发送通知

使用 Notifiable Trait

通知可以通过两种方法发送: Notifiable trait 的notify 方法或 Notification facade。首先,让我们探索使用 trait :

1
2
3
4
5
6
7
8
9
<?php namespace App;

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

class User extends Authenticatable
{
use Notifiable;
}

默认的 App\User 模型中使用了这个 trait,它包含着一个可以用来发通知的方法:notifynotify 方法需要一个通知实例做参数:

1
2
3
use App\Notifications\InvoicePaid;

$user->notify(new InvoicePaid($invoice));

使用 Notification Facade

另外,你可以通过 Notification facade 来发送通知。它主要用在当你给多个可接收通知的实体发送通知的时候,比如给用户集合发通知。要用 facade 发送通知的话,要把可接收通知的实体和通知的实例传递给 send 方法:

1
Notification::send($users, new InvoicePaid($invoice));

队列化通知

发送通知可能很耗时,尤其是是当频道需要一个额外的 API 来发送通知。要加速你的应用响应时间,让你的通知继承 ShouldQueue 接口 并且在你的类中添加 Queueable trait。这些接口和 trait 已经被使用 make:notification 生成的所有通知引入了,所以你可以直接将他们添加到你的通知类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;
use Illuminate\Contracts\Queue\ShouldQueue;

class InvoicePaid extends Notification implements ShouldQueue
{
use Queueable;

// ...
}

做好一上操作并正常配置好队列后,系统将自动使用异步队列发送通知

自定义函数

有时我们需要加载自定义的函数文件,在 composer.json 文件中定义如下:

1
2
3
4
"autoload": {
...
"files": ["bootstrap/helpers.php"]
}

命令行执行 composer dump-autoload 重新生成加载配置

Redis

在 homestead 中的 redis 允许的 IP 为 127.0.0.1 如果想让 windows 或 mac 的 GUI 工具可访问需要执行以下几步:

1
2
#修改redis配置文件,将127.0.0.1 改为 0.0.0.0
sudo vim /etc/redis/redis.conf

重起 redis 服务

1
sudo service redis restart

Gui 图形管理工具

队列

队列用于异步执行消耗时间多的工作,比如发送邮件,商城定单处理等操作,好处是可以快速为客户响应结果,具体处理异步后台操作。

下面的操作使用高效的 redis 完成处理,所以需要安装相应扩展包

1
composer require "predis/predis:~1.0"

配置

修改 config/queue.php 队列配置文件。

1
'default' => env('QUEUE_DRIVER', 'sync'),

sync 为同步可更改为 database 或 redis 即为后台异步操作 ,需要先在 config/database.php 文件中将连接设置好。

处理失败任务

有时你的队列任务会失败。Laravel 包含了一个便捷的方式指定任务会被最大尝试的次数。在一个任务达到了它最大尝试次数后,它会被放入 failed_jobs 表。要创建 failed_jobs 表你可以使用 queue:failed-table 命令:

1
2
php artisan queue:failed-table
php artisan migrate

创建任务

1
php artisan make:job TestJob

在生成的类中的 handle 方法设置任何具体动作,下面是一个简单的示例。

1
2
3
4
5
6
7
8
9
10
11
12
protected $user;

public function __construct(User $user)
{
$this->user = $user;
}

public function handle()
{
sleep(6);
file_put_contents('a.php', var_export($this->user->toArray(),true));
}

分发任务

1
TestJob::dispatch(User::find(1));

队列处理器

队列处理器用于执行推送到队列中的任务

1
php artisan queue:work

要使 queue:work 在后台运行需要使用下面讲到的 Supervisor (生产环境必用)

Laravel 队列监控面板

Horizon 提供了一个漂亮的仪表盘,并且可以通过代码配置你的 Laravel Redis 队列,同时能够让你轻松地监控你的队列系统中诸如任务吞吐量,运行时间和失败任务等关键指标。

安装

1
2
composer require laravel/horizon
php artisan vendor:publish --provider="Laravel\Horizon\HorizonServiceProvider"

启动执行任务监听

只需一条命令语句即可启动队列执行进程。如果修改了任何代码需要重起 horizon 监听(需要在 homestead 系统执行)

1
php artisan horizon

可以通过路由 /horizon 以图形界面查看任何状态

Supervisor

Supervisor 是一个进程控制系统,用于让我们的队列任务在后台运行,并自动维护进行如果挂了就自动重起。

修改配置文件

1
sudo vi /etc/supervisor/conf.d/laravel-worker.conf

内容如下

1
2
3
4
5
6
7
8
9
[program:laravel-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /home/vagrant/code/hdcms-laravel/artisan queue:work --sleep=3 --tries=3
autostart=true
autorestart=true
user=vagrant
numprocs=8
redirect_stderr=true
stdout_logfile=/home/vagrant/code/hdcms-laravel/worker.log

注意 command 与 stdout_logfile 配置根据环境自行更改

运行

依次执行下面命令加载配置并运行 Supervisor

1
2
3
4
5
sudo supervisorctl reread

sudo supervisorctl update

sudo supervisorctl start laravel-worker:*

这样我们就不需要执行 php artisan queue:work 命令了,交给 Supervisor 自动维护吧

Repository

组件库 Github:https://github.com/houdunwang/repository

Repository 模式主要思想是建立一个数据操作代理层,把 controller 里的数据操作剥离出来。

Repository 模式是架构模式,在设计架构时,才有参考价值。应用 Repository 模式所带来的好处,远高于实现这个模式所增加的代码。只要项目分层,都应当使用这个模式。

这样做有几个好处:

  • 把数据处理逻辑分离使得代码更容易维护
  • 数据处理逻辑和业务逻辑分离,可以对这两个代码分别进行测试
  • 减少代码重复
  • 降低代码出错的几率
  • 让 controller 代码的可读性大大提高

Service 服务化

为了体现无尽的复用原则,应该将可用的功能服务器。比如邮箱发送、图片处理、上传功能等。

创建目录 app\Services

上传服务

创建服务类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
namespace App\Services;
use Symfony\Component\HttpFoundation\File\UploadedFile;
class UploadService
{
protected $allow_ext = ['jpg', 'jpeg', 'png', 'gif'];
public function make(UploadedFile $file)
{
$ext = strtolower($file->getClientOriginalExtension());
if (in_array($ext, $this->allow_ext)) {
$dir = 'uploads/'.date('ymd');
$up_file = time().str_random(10).'.'.$ext;
$file->move($dir, $up_file);

return ['path' => config('app.url').'/'.$dir.'/'.$up_file];
}
return false;
}
}

控制器中使用

控制器中使用依赖注入服务就可正常使用了。

1
2
3
4
5
6
...
public function editorImage(Request $request, UploadService $uploadService)
{
$file = $uploadService->make($request->upload_file);
...
}

图片处理服务

阅读下面的 插件>图片处理 文档安装图片处理插件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
namespace App\Services;
use Intervention\Image\Facades\Image;

class ImageService
{

protected $img;
protected $file;
//设置处理文件
public function file($file)
{
$this->file = $file;
$this->img = Image::make($file);

return $this;
}
//缩放
public function resize($width = null, $height = null)
{
$this->img->resize(
$width,
$height,
function ($constraint) {
//维护比例
$constraint->aspectRatio();
//防止变大
$constraint->upsize();
}
);

return $this;
}
//裁切
public function crop($w, $h)
{
$this->img->crop($w, $h);

return $this;
}
//保存
public function save($file)
{
$file = $file ? $file : $this->file;

return $this->img->save($file);
}
}

控制器中使用

1
2
3
4
5
public function show(User $user,ImageService $image,Request $request)
{
$image->file('uploads/1.jpg')->resize(200)->crop(200,200)->save('uploads/4.jpg');
return view('user.show',compact('user'));
}

任务调度

在过去,开发者必须在服务器上为每个任务生成单独的 Cron 项目。而令人头疼的是任务调度不受源代码控制,而且必须通过 SSH 连接到服务器上来增加 Cron 项目。

编辑 crontab 计划任务(artisan 设置成你的项目地址)

1
2
export EDITOR=vi && crontab -e
* * * * * php /home/vagrant/code/bbs/artisan schedule:run >> /dev/null 2>&1

Laravel Plugin

在 phpstorm 中安装 laravel plugin 插件.

1
Settings > Languages & Frameworks > PHP > Laravel` 点击开启 `Enable for this project

laravel-ide-helper

laravel-ide-helper 用于实现方便的代码提示功能。

使用 composer 安装插件

1
composer require barryvdh/laravel-ide-helper

config\app.php 文件 providers 添加

1
Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class,

app/Providers/AppServiceProvider.php 文件中注册

1
2
3
4
5
6
7
public function register()
{
if ($this->app->environment() !== 'production') {
$this->app->register(\Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class);
}
// ...
}

生成代码跟踪支持

1
php artisan ide-helper:generate

laravel artisan 命令提示

1
settings>Tools>Command Line Tool Support

多语言

语言包

默认表单提示是英文的,我们可以安装中文语言包进行汉化。

1
composer require caouecs/laravel-lang:~4.0

包含大多数语言,语言包位于vendor/caouecs/larvel-lang/src 目录中。

使用

  1. 根据需要复制语言包到 resources/lang 目录中。

  2. 修改 config/app.php 配置文件

    1
    'locale' => 'zh-CN',

JSON 语言包

resources/lang 目录下定义 zh-CN.json 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"Login": "登录",
"Logout": "退出",
"E-Mail Address": "邮箱",
"Register": "注册",
"Password": "密码",
"Confirm Password": "确认密码",
"Name": "帐号",
"Remember Me": "记住我",
"Forgot Your Password?": "找回密码",
"Reset Password": "重置密码",
"Send Password Reset Link": "发送重置邮件",
"Reset Password Notification": "重置密码通知",
"You are receiving this email because we received a password reset request for your account.": "您收到这封邮件是因为我们收到您的帐户密码重置请求。",
"If you did not request a password reset, no further action is required.": "如果没有要求重新设置密码,则不需要进一步的操作。"
}

在模板中就可以使用 __('Login') 调用了,Laravel 默认的登录模板大量使用了 JSON 语言包

SESSION

默认 Laravel 使用文件管理会话,下面是我们改为更高效的数据库管理。

创建数据表

1
2
3
php artisan session:table
composer dump-autoload
php artisan migrate

修改.env 中的驱动

1
2
3
...
SESSION_DRIVER=database
...

验证码

https://github.com/mewebstudio/captcha

安装

1
2
composer require mews/captcha
php artisan vendor:publish --provider='Mews\Captcha\CaptchaServiceProvider'

配置文件 config/captcha.php

使用

1
2
3
4
5
6
#前台
<img src="{{ captcha_src('default') }}" onclick="this.src='/captcha/default?_'+Math.random()">

#后台验证
$rules = ['captcha' => 'required|captcha'];
$validator = Validator::make(Input::all(), $rules);

用户认证

重定向未认证用户

auth 中间件判定某个用户未认证,会返回一个 JSON 401 响应,或者,如果不是 Ajax 请求的话,将用户重定向到 login 命名路由。

你可以通过在 app/Exceptions/Handler.php 文件中定义一个 unauthenticated 方法来改变这一行为:

1
2
3
4
5
6
7
8
use IlluminateAuthAuthenticationException;

protected function unauthenticated($request, AuthenticationException $exception)
{
return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(route('login'));
}

事件

Laravel 在认证过程中引发了各种各样的事件。你可以在 EventServiceProvider 中对这些事件做监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
* 应用程序的事件监听器映射。
*
* @var array
*/
protected $listen = [
'Illuminate\Auth\Events\Registered' => [
'App\Listeners\LogRegisteredUser',
],

'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],

'Illuminate\Auth\Events\Authenticated' => [
'App\Listeners\LogAuthenticated',
],

'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],

'Illuminate\Auth\Events\Failed' => [
'App\Listeners\LogFailedLogin',
],

'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],

'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],

'Illuminate\Auth\Events\PasswordReset' => [
'App\Listeners\LogPasswordReset',
],
];

异常处理 Excepton

异常处理通常是防止未知错误产生所采取的处理措施。异常处理的好处是你不用再绞尽脑汁去考虑各种错误,这为处理某一类错误提供了一个很有效的方法,使编程效率大大提高。

创建异常类

1
php artisan artisan make:exception UploadException

定义类内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php

namespace App\Exceptions;

use Exception;
use Throwable;

class UploadException extends Exception
{
public function __construct(string $message = "", int $code = 403, Throwable $previous = null)
{
parent::__construct($message, $code, $previous);
}

//报告或者通过日志记录一个异常
public function report(Exception $exception)
{
parent::report($exception);
}

//将异常通过 Http 返回
public function render()
{
return response()->json(['message' => $this->getMessage(), 'code' => 403], $this->code);
}
}

异常日志

当发生异常时会在 storage/logs/laravel.log 文件中记录异常。

如果不想执行异常记录需要在 app/Exceptions/Handler.php 异常处理器文件的 $dontReport 属性声明。

1
2
3
4
5
6
7
8
class Handler extends ExceptionHandler
{
...
protected $dontReport = [
UploadException::class,
];
...
}

全站动态

使用 https://github.com/spatie/laravel-activitylog 组件构成,可查看 https://docs.spatie.be/laravel-activitylog/v3/introduction 文档学习使用该组件。

安装组件

1
composer require spatie/laravel-activitylog

创建数据迁移文件并执行

1
2
3
php artisan vendor:publish --provider="Spatie\Activitylog\ActivitylogServiceProvider" --tag="migrations"

php artisan migrate

记录动态

基本使用

1
2
3
4
activity()
->performedOn($someContentModel)//主题比如评论模型
->causedBy($userModel)//用户模型,不设置时为当前登录用户
->log('edited');

设置自定义属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
activity()
->performedOn($someContentModel)
->withProperties(['key' => 'value'])//设置自定义属性
->log('edited');

//返回最后动态
$activity = Activity::all()->last();

//获取用户模型
$activity->causer

//获取日志名
$activity->log_name

//获取属性
$activity->getExtraProperty('key');

//获取自定属性 'key' 为 'value' 的所有动态
$activity->where('properties->key', 'value')->get();

//返回模型实例
$activity->subject;

记录模型事件

程序包可以自动记录事件,例如创建,更新和删除模型时。

1
2
3
4
5
6
7
8
9
10
11
12
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;

class NewsItem extends Model
{
use LogsActivity;

protected $fillable = ['name', 'text'];

//设置动态记录的属性
protected static $logAttributes = ['name', 'text'];
}

如果要记录$fillable对模型的所有属性的更改,可以protected static $logFillable = true;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$newsItem = NewsItem::create([
'name' => 'original name',
'text' => 'Lorum'
]);

$activity = Activity::all()->last();

//返回 'created'
$activity->description;

//返回创建的NewsItem实例
$activity->subject;

//返回模型NewsItem属性 ['attributes' => ['name' => 'original name', 'text' => 'Lorum']];
$activity->changes();

自定义要记录的事件

默认情况下,包将记录createdupdateddeleted的事件。您可以通过$recordEvents在模型上设置属性来修改此行为。

1
2
3
4
5
6
7
8
9
10
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;

class NewsItem extends Model
{
use LogsActivity;

//only the `deleted` event will get logged automatically
protected static $recordEvents = ['created','updated'];
}

自定义日志名称

指定$logName使模型使用除默认名称之外的其他名称。

1
2
3
4
5
6
7
8
9
use Illuminate\Database\Eloquent\Model;
use Spatie\Activitylog\Traits\LogsActivity;

class NewsItem extends Model
{
use LogsActivity;

protected static $logName = 'system';
}

Markdown 转 HTML

Parsedown 组件用于将 markdown 内容转为 html

安装组件

1
composer require erusev/parsedown

模型中使用

1
2
3
4
5
6
7
8
9
10
class Article extends Model
{
...
public function getMarkdownAttribute()
{
$Parsedown = new \Parsedown();
return $Parsedown->text($this['content']);
}
..
}

角色权限

https://docs.spatie.be/laravel-permission/v3/introduction/

安装

1
2
3
4
5
6
7
8
composer require spatie/laravel-permission

#生成迁移文件,根据业务需要可以随意添加表字段
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"

php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"

php artisan migrate

基本使用

缓存角色和权限数据以加速性能。当你使用提供的方法来操作角色和权限时,缓存会自动为您重置:

1
2
3
4
5
6
7
8
9
$user->assignRole('writer');
$user->removeRole('writer');
$user->syncRoles(params);
$role->givePermissionTo('edit articles');
$role->revokePermissionTo('edit articles');
$role->syncPermissions(params);
$permission->assignRole('writer');
$permission->removeRole('writer');
$permission->syncRoles(params);

但是,如果直接在数据库中操作“权限/角色”数据而不是调用提供的方法,则除非手动重置缓存,否则不会在应用程序中看到反映的更改。

手动缓存重置

缓存角色和权限数据以加速性能。当你使用提供的方法来操作角色和权限时,缓存会自动为您重置。

要手动重置此软件包的缓存,请运行:

1
php artisan cache:forget spatie.permission.cache

代码删除缓存

1
app()['cache']->forget('spatie.permission.cache');

模型动作

User 等模型中引入 use Spatie\Permission\Traits\HasRoles 可以拥有所有权限操作方法

1
2
3
4
5
6
7
use Spatie\Permission\Traits\HasRoles;

class User extends Authenticatable
{
use HasRoles;
...
}

新建角色

1
2
use Spatie\Permission\Models\Role;
$role = Role::create(['name' => 'admin']);

删除角色

1
$user->removeRole('writer');

角色也可以同步:

1
2
//所有当前角色将从用户中删除,并替换为给定的数组中的角色
$user->syncRoles(['writer', 'admin']);

添加权限

1
2
3
4
5
6
7
use Spatie\Permission\Models\Permission;

Permission::create(['name' => 'edit_articles']);
$role->givePermissionTo('edit_articles');

#或者使用下面分配权限
$permission->assignRole($role);

可以使用以下方法之一将多个权限同步到一个角色:

1
2
$role->syncPermissions($permissions);
$permission->syncRoles($roles);

删除权限

1
2
$role->revokePermissionTo($permission);
$permission->removeRole($role);

一次性撤消并添加新的权限:

1
$user->syncPermissions(['edit articles', 'delete articles']);

添加用户角色

1
2
3
4
5
6
7
8
// 单个角色
$user->assignRole('admin');

// 数组形式的多个角色
$user->assignRole(['super_user', 'admin']);

// 同步角色(不存在添加,存在忽略)
auth()->user()->syncRoles(['admin']);

添加用户权限

1
2
3
4
5
// 为用户添加『直接权限』
$user->givePermissionTo('manage_contents');

// 获取所有直接权限
$user->getDirectPermissions()

检查用户角色

1
2
3
4
5
6
7
8
// 是否是超级管理员
$user->hasRole('super_user');

// 拥有多个角色中的一个角色
$user->hasAnyRole(Role::all());

// 包含所有角色
$user->hasAllRoles(Role::all());

检查权限

1
2
3
4
5
//用户是否拥有权限
$user->can('edit_articles');

//角色是否拥有权限
$role->hasPermissionTo('edit_articles');

或者你可以传递一个代表权限 ID 的整数

1
2
3
$user->hasPermissionTo('1');
$user->hasPermissionTo(Permission::find(1)->id);
$user->hasPermissionTo($somePermission->id);

如果用户有任何一个权限即通过:

1
$user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']);

用户必须拥有所有权限时通过:

1
$user->hasAllPermissions(['edit articles', 'publish articles', 'unpublish articles']);

你也可以传递整数以通过权限 ID 进行查找

1
$user->hasAnyPermission(['edit articles', 1, 5]);

使用 Laravel 默认的 can 功能测试用户是否拥有权限:

1
$user->can('edit articles');

获取用户权限

1
2
3
4
5
6
7
8
//获取直接分配给用户的所有权限的列表
$permissions = $user->getDirectPermissions();

//从用户角色继承的权限
$user->getPermissionsViaRoles();

//适用于用户的所有权限(继承和直接)
$permissions = $user->getAllPermissions();

获取用户角色

1
$roles = $user->getRoleNames(); // 返回一个集合

根据权限或角色获取用户

HasRoles 特征(trait)还为您的模型添加了一个“角色”范围,以便将查询范围限定为某些角色或权限:

仅返回具有角色 'writer' 的用户

1
$users = User::role('writer')->get();

仅返回具有 'edit articles' (继承或直接)权限的用户

1
$users = User::permission('edit articles')->get();

Blade 和角色

测试一个特定的角色:

1
2
3
4
5
@role('writer')
I am a writer!
@else
I am not a writer...
@endrole

同上

1
2
3
4
5
@hasrole('writer')
I am a writer!
@else
I am not a writer...
@endhasrole

测试列表中的任何角色:

1
2
3
4
5
6
7
8
9
10
11
@hasanyrole($collectionOfRoles)
I have one or more of these roles!
@else
I have none of these roles...
@endhasanyrole
// or
@hasanyrole('writer|admin')
I am either a writer or an admin or both!
@else
I have none of these roles...
@endhasanyrole

测试所有角色:

1
2
3
4
5
6
7
8
9
10
11
@hasallroles($collectionOfRoles)
I have all of these roles!
@else
I do not have all of these roles...
@endhasallroles
// or
@hasallroles('writer|admin')
I am both a writer and an admin!
@else
I do not have all of these roles...
@endhasallroles

Blade 和权限

此软件包不会添加任何特定于权限的 Blade 指令。 因此,使用 Laravel 原生的 @can 指令来检查用户是否具有某种权限。

1
2
3
@can('edit articles')
//
@endcan

or

1
2
3
@if(auth()->user()->can('edit articles') && $some_other_condition)
//
@endif

图片处理

spatie/image

https://docs.spatie.be/image/v1/usage/basic-usage 用于提供丰富的图片处理方式

Intervention/image

https://github.com/Intervention/image 用于裁切缩放等图片处理操作

安装

1
2
$ composer require intervention/image
$ php artisan vendor:publish --provider="Intervention\Image\ImageServiceProviderLaravel5"

使用

1
2
3
4
5
6
7
8
9
$img = Image::make('public/foo.jpg');
//只设置高亮标准
$img->resize(null, 400, function ($constraint) {
//维护比例
$constraint->aspectRatio();
//防止变大
$constraint->upsize();
});
$img->save($file);

spatie/image

https://github.com/spatie/image

日期处理

https://carbon.nesbot.com/ 无需安装 Laravel 已经内置。

中文文档:https://segmentfault.com/a/1190000012716974#articleHeader2

下面是中文本地化的设置:

1
$user->created_at->diffForHumans()

app/Providers/AppServiceProvider.phpboot方法中设置:

1
\Carbon\Carbon::setLocale('zh');

Debugbar

https://github.com/barryvdh/laravel-debugbar

安装

1
composer require barryvdh/laravel-debugbar --dev

修改 .env 配置文件

1
DEBUGBAR_ENABLED=true

模块化设计

https://nwidart.com/laravel-modules/v6/introduction

https://github.com/houdunwang/laravel-module

组件介绍

通过使用模块来管理大型 Laravel 项目,模块就像一个 laravel 包非常方便的进行添加或移除。

nwidart/laravel-modules是一个 Laravel 软件包,创建该软件包是为了使用模块管理大型 Laravel 应用。模块就像 Laravel 包一样,它具有一些视图,控制器或模型。Laravel 5 支持并测试了该软件包。

此软件包是pingpong / modules的重新发布,重新组织和维护的版本,不再维护。该软件包在AsgardCMS 中使用

原始软件包没有的一大额外奖励:tests

注意:

如果从以前的版本升级到 v6,请运行以下命令:

1
php artisan module:v6:migrate

安装组件

要通过 Composer 安装,运行以下命令

1
composer require nwidart/laravel-modules

通过运行以下命令来发布程序包的配置文件

1
php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"

自动加载

默认情况下,模块类不会自动加载。您可以使用来自动加载模块psr-4

composer.json

1
2
3
4
5
6
7
8
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Modules\\": "Modules/"
}
}
}

创建模块

运行以下命令创建模块

1
php artisan module:make <module-name>

也可以在一个命令中创建多个模块

1
php artisan module:make Blog User Auth

默认情况下,当您创建新模块时,该命令将自动添加一些资源,例如控制器,种子类,服务提供者等。如果您不需要这些,则可以添加--plain标志,以生成普通模块

1
2
3
php artisan module:make Blog --plain
# or
php artisan module:make Blog -p

webpack

laravel 前端使用 webpack 工具构建,我们介绍几个常用动作

复制目录

1
mix.copyDirectory('assets/img', 'public/img');

Doctrine/dbal

dbal 是用于管理数据表结构的组件库。

在线文档:https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/schema-manager.html#schema-manager

Active 状态

在我们构建页面的过程中,经常会根据 url 的参数设置页面元素的 active 属性,将其渲染为「选中」状态。

官网 https://www.hieule.info/products/laravel-active-version-3-released

安装

Require this package as your dependencies:

1
composer require hieu-le/active

Append this line to your providers array in config/app.php

1
HieuLe\Active\ActiveServiceProvider::class,

Append this line to your aliases array in config/app.php

1
'Active' => HieuLe\Active\Facades\Active::class,

语法

1
function active_class($condition, $activeClass = 'active', $inactiveClass = '')

使用

此扩展包提供了一批函数让我们更方便的进行 $condition 判断:

  1. if_uri() - 判断当前的 url 是否满足指定的 url;
  2. if_uri_pattern() - 判断当前的 url 是否含有指定的字符;
  3. if_query() - 判断指定的 GET 变量是否符合设置的值;
  4. if_route() - 判断当前对应的路由是否是指定的路由;
  5. if_route_pattern() - 判断当前的路由是否包含指定的字符;
  6. if_route_param() - 判断当前的 url 有无指定的路由参数。
1
2
3
4
5
<a class="{{ active_class(if_route('users.index')) }}" href="">会员</a>

<a class="{{ active_class(if_route('cateogry.show') && if_route_param('category', 1)) }}" href="">实战</a>

<a class="{{ active_class(if_route('cateogry.show') && if_route_param('category', 2)) }}" href="">阶段</a>

XSS 过滤

https://github.com/mewebstudio/Purifier

安装

1
2
composer require mews/purifier
php artisan vendor:publish --provider="Mews\Purifier\PurifierServiceProvider"

配置文件

1
config/purifier.php

使用

1
clean(Input::get('inputname'),'default');

Guzzle

Guzzle 是一个 PHP 的 HTTP 客户端,用来轻而易举地发送请求,并集成到我们的 WEB 服务上。

http://guzzle-cn.readthedocs.io/zh_CN/latest/

sudo-su 用户切换

https://github.com/viacreative/sudo-su

安装软件包

1
composer require viacreative/sudo-su

Add the package's service provider to your app in your project's AppServiceProvider:

1
2
3
4
5
6
7
8
9
class AppServiceProvider extends ServiceProvider
{
public function register()
{
if (config('app.debug')) {
$this->app->register('VIACreative\SudoSu\ServiceProvider');
}
}
}

发布

1
php artisan vendor:publish

配置

修改配置文件 config/sudosu.php 添加允许使用的域名后缀

1
'allowed_tlds' => ['dev', 'local','hd'],

guard

如果用户模型不是 User 也需要在配置文件中修改

1
'user_model' => \Modules\Admin\Entities\Admin::class

使用

在模板中使用

1
2
3
@if (config('app.debug'))
@include('sudosu::user-selector')
@endif

simditor 编辑器

simditor 是一款简单快速的编辑器。

http://simditor.tower.im/

下载 https://github.com/mycolorway/simditor/archive/v2.3.16.zip

配置

  1. 复制site/assets/scripts 目录为 Laravel 项目的 resources/plugin/simditor/scripts 目录

  2. 复制site/assets/styles 目录为 Laravel 项目的 resource/plugin/simditor/styles 目录

  3. 修改 webpack.mix.js

    1
    .mix.copy('resources/plugin','public/plugin')

创建控制器

1
artisan make:controller Components/UploadController

下面是控制器内容,使用了 UploadService 服务(参考上面Service 服务化 文档)

1
2
3
4
5
6
7
8
9
10
11
12
13
...
class UploadController extends Controller
{
public function editorImage(Request $request, UploadService $uploadService)
{
$file = $uploadService->make($request->upload_file);
return [
"success" => true,
"msg" => "上传成功",
"file_path" => $file['path'],
];
}
}

配置路由

1
Route::post('upload/image','Components\UploadController@editorImage')->name('upload.image');

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<script type="text/javascript" src="{{asset('js/simditor/module.js')}}"></script>
<script type="text/javascript" src="{{asset('js/simditor/hotkeys.js')}}"></script>
<script type="text/javascript" src="{{asset('js/simditor/uploader.js')}}"></script>
<script type="text/javascript" src="{{asset('js/simditor/simditor.js')}}"></script>
<script>
$(function () {
var editor = new Simditor({
textarea: $('#editor'),
pasteImage: true,
upload: true,
upload: {
url: '',
params: {_token: '{{ csrf_token() }}'},
fileKey: 'upload_file',
connectionCount: 3,
leaveConfirm: '文件上传中,关闭此页面将取消上传。'
}
});
});
</script>

Mailhog

Mailhog (homestead 已经内置)可以轻松的抓取到你发送的电子邮件并进行检查,而无需将邮件真正发送给收件人. 开始之前, 请更新你的 .env 文件并使用如下邮件设置:

1
2
3
4
5
6
MAIL_DRIVER=smtp
MAIL_HOST=localhost
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null

访问

1
http://domain:8025

china-area-data

中国行政区域数据 https://www.npmjs.com/package/china-area-data

predis

redis 队列驱动

Github:https://github.com/nrk/predis

安装

1
composer require "predis/predis:~1.0"

判断设备

判断移动、平板设备等场景还是很常见的。

Github:https://github.com/hisorange/browser-detect

宝塔设置

域名设置

Laravel 使用 public 目录访问,需要将域名绑定在 public 目录

打包上传

  1. 将本地 Laravel 项目打成 zip 包
  2. 在宝塔中的文件菜单选择你的网站目录,然后上传 zip 文件
  3. 上传后解压释放即可

伪静态

在网站菜单选择需要修改的网站,进行以下设置

MySQL

宝塔的 mysql 版本低,运行会出错。

  1. 先在宝塔后台升级 mysql

  2. 在 Laravel 的 AppServiceProvider.php 文件里的 boot 方法里设置

    1
    2
    3
    4
    public function boot()
    {
    \Schema::defaultStringLength(191);
    }

常见错误

  1. 有时执行 composer 意外中断或卡住,删除 vendor 与 composer.lock 后重新执行
  2. 使用模块化开发时模块下的 node_modules 严重影响 composer 执行,可以先删除模块下的 node_modules 再执行 composer install 、composer dumpautoload 等命令

Swoole 加速

安装 Swoole 扩展

Swoole 扩展到 GitHub 首页下载 Swoole 扩展源码,地址:https://github.com/swoole/swoole-src 下载后按照标准的 PHP 扩展编译方式进行编译和安装。

1
2
3
4
unzip swoole-master.zip
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config
sudo make install

编译安装完后,修改 php.ini 加入 extension=swoole.so 开启 swoole 扩展

安装 laravel-swoole 组件

组件地址:https://github.com/swooletw/laravel-swoole

1
composer require swooletw/laravel-swoole

然后,添加服务提供者: 如果你使用 Laravel ,在 config/app.php 服务提供者数组添加该服务提供者:

1
2
3
4
5
[
'providers' => [
SwooleTW\Http\LaravelServiceProvider::class,
],
]

启动 swoole 服务

现在,你可以执行以下的命令来启动 Swoole HTTP 服务。

1
/usr/local/php/bin/php artisan swoole:http start

然后你可以看到以下信息:

1
2
Starting swoole http server...
Swoole http server started: <http://127.0.0.1:1215>

现在可以通过访问 http://127.0.0.1:1215 来进入 Laravel 应用

Laravel-swoole 命令

1
2
3
4
5
6
7
8
9
10
# 启动
/usr/local/php/bin/php artisan swoole:http start
# 重启
/usr/local/php/bin/php artisan swoole:http restart
# 重载
/usr/local/php/bin/php artisan swoole:http reload
# 停止
/usr/local/php/bin/php artisan swoole:http stop
# 查看服务信息
/usr/local/php/bin/php artisan swoole:http infos

配合 Ngnix 使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
gzip on;
gzip_min_length 1024;
gzip_comp_level 2;
gzip_types text/plain text/css text/javascript application/json application/javascript application/x-javascript application/xml application/x-httpd-php image/jpeg image/gif image/png font/ttf font/otf image/svg+xml;
gzip_vary on;
gzip_disable "msie6";
upstream labs {
# Connect IP:Port
server 127.0.0.1:1215 weight=5 max_fails=3 fail_timeout=30s;
keepalive 16;
}

server {
listen 80;

server_name www.xxx.com;

root /data/www/labs/public/;

access_log /var/log/nginx/xxx.cn.access.log main;
error_log /var/log/nginx/xxx.cn.error.log;

autoindex off;
index index.html index.htm;

location / {
try_files $uri @laravels;
}

location @laravels {
# proxy_connect_timeout 60s;
# proxy_send_timeout 60s;
# proxy_read_timeout 120s;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Real-PORT $remote_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header Scheme $scheme;
proxy_set_header Server-Protocol $server_protocol;
proxy_set_header Server-Name $server_name;
proxy_set_header Server-Addr $server_addr;
proxy_set_header Server-Port $server_port;
proxy_pass http://labs;
}
}

Artisan 命令

1
php artisan make:controller HomeController
坚持原创技术分享,您的支持将鼓励我继续创作!