j. Laravel笔记
安装
composer create-project --prefer-dist laravel/laravel projectnamechmod -R a+w storage bootstrap/cache # 设定目录权限php artisan key:generate # 生成加密秘钥confit/app.php配置timezone=>Asia/Shanghai, locale=>zh* 服务器解析到`public`目录上 *
环境及配置
.env
配置文件、phpunit.xml env
变量设定 会被加载至两处- 系统环境变量(系统已有设定则不会覆盖,影响到
phpunit.xml env
配置失效) PHP $_ENV
变量
- 系统环境变量(系统已有设定则不会覆盖,影响到
env()
读取的是系统环境变量.env
文件在Docker
容器启动时被加载到容器的系统环境变量- 当前应用程序环境
App::environment()
错误&异常 处理
App\Exceptions\Handler
处理- 错误上报
report()
(默认是记录日志) - 浏览器输出
render()
- 错误上报
- HTTP异常
- 手动抛出异常
abort(404 [,'error msg'])
- 定义异常页面
resources/views/errors/404.blade.php
- 手动抛出异常
- 浏览器输出错误细节
APP_DEBUG=true
Monolog
日志- 配置
- 日志模式:
APP_LOG=single
(single、daily、syslog、errorlog
) - 日志最小级别
APP_LOG_LEVEL=debug
- 日志模式:
- 记录
\Log::debug|info|notice|warning|error|critical|alert|emergency('xxx', Array $context)
(错误级别降序)
- 配置
ServiceProvider
php artisan make:provider XxxServiceProvider
(自动注册于config/app.php
)- 所有
Provider
的register
完毕后才调用boot
- 延迟加载
Provider
protected $defer = true;
- 在
provides
方法返回延迟加载服务的类名
Service Container
绑定
* 仅当需要修改容器中绑定的对象时才进行手工绑定 *$this->app->bind(类名, 闭包); # 简单绑定$this->app->bind(接口名, 类名); # 接口实现绑定$this->app->singleton(类名, 闭包); # 单例绑定$this->app->instance(类名, 对象); # 实例绑定$this->app->when(类名)->needs(类名)->give(闭包); # 场景绑定$this->app->when(类名)->needs('$变量名')->give(变量值); # 数据绑定
解析
* $this->app->make(类名);* app(类名)* resolve(类名);* 自动依赖注入
容器事件
* 每当服务容器解析一个对象前就会触发一个事件 - 前置回调 *$this->app->resolving(function ($object, $app) { // 解析任何类型的对象时都会调用该方法...});$this->app->resolving(HelpSpot\API::class, function ($api, $app) { // 解析「HelpSpot\API」类型的对象时调用...});
Facades
- 访问 Container内实例 的静态代理(虽然静态,但仍然可测试)
- 本质和辅助函数没有区别
- 使用模式
- 通过绑定的别名
\Cache
- 直接原生使用
\Illuminate\Support\Facades\Cache
- 通过绑定的别名
- 继承
Illuminate\Support\Facades\Facade
类,并实现getFacadeAccessor
方法返回容器绑定key
HTTP 路由
路由方法
get、post、put、patch、delete、options
- 匹配基本请求类型
Route::get(路径, 控制器@方法, ['except'=>[..], 'only'=>[..]]) ->where(正则约束) ->middleware('') ->name('')
match
- 匹配多个请求类型any
- 匹配所有请求类型resource
- 处理Rest请求- 资源路由Actions:
index、create、store、show、edit、update、destroy
Route::resource(路径, 控制器, ['except'=>[..], 'only'=>[..]])
- 资源路由Actions:
$url = route('profile', Array $context) # 从命名路由生成urlreturn redirect()->route('profile'); # 重定向到命名路由## 当前路由信息 ##$route = Route::current();$name = Route::currentRouteName();$action = Route::currentRouteAction();## 控制器中调用路由参数 ##$this->route('ParaName')## 表单方法伪造 ##{ { method_field('PUT') }}# 生成路由缓存(仅对基于控制器实现的路由有效)php artisan route:clearphp artisan route:cache
HTTP中间件
- 中间件用于过滤进入应用程序的 HTTP 请求
- 中间件注册
- Laravel
app/Http/Kernel.php
- lumen
bootstrap/app.php
- Laravel
- 中间件类型
- 前置中间件
BeforeMiddleware
- 后置中间件
BeforeMiddleware
- 路由中间件
$router->middleware('xxx', ...)
(别名或全名) - 路由组中间件
Route::group(['middleware' => ['xxx', ...]], Closure);
- 控制器中间件 - 构造函数中
$this->middleware('xxx')->only()->except()
- 前置中间件
- 中间件的
terminate($request, $response)
方法(响应被发送到浏览器之后才运行) - 中间件划归为组 通过
$middlewareGroups
属性 - 中间件传参
->middleware('中间件名:参数1,参数2,...');
CSRF保护
- input标签POST参数类型
{ { csrf_field() }}
自动触发VerifyCsrfToken
中间件 - meta标签X-CSRF-TOKEN头部类型
<meta name="csrf-token" content="{ { csrf_token() }}">
$.ajaxSetup({headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}});
控制器
- 常规控制器
- 单一行为控制器 - 实现
public function __invoke
方法(路由不指定Action
) - Restful资源控制器
php artisan make:controller XxxController --resource
- 请求数据
$request->get|input(xxx)
$request->all()
- 表单请求模拟 - 隐藏域
_method
指定HTTP类型{ { method_field('PUT') }}
响应
- 字符串 -> 字符串
- 数组|集合 -> JSON
- Json响应
return response()->json(array $data)[->withCallback($jsonpCallbackName)]
- 文件下载
return response()->download($pathToFile [, $name, $headers])->deleteFileAfterSend(true) #文件名必须ascii
- 文件内容
return response()->file($pathToFile [, $headers])
- 视图响应
# 嵌套视图路径用点隔开 - 常规视图:return view($viewName, $data)[->with($key, $value)](或者View::make()) - 定制头部:return response()->view($viewName, $data, $statuCode)->header($field, $value) View::exists($viewName) #检查视图是否存在 View::share($key, $value) #设定全部视图共享的数据,AppServiceProvider::boot()中 ## 视图编排ViewComposers(在视图输出前做修饰)## # 定义composer类 class MyComposer { public function compose(View $view) { $view->.... } } # 视图绑定composer View::composer($viewName|$viewNames|*, $composer::Class|function($view){}) ## ViewCreator(视图一创建就执行修饰) ## View::creator($viewName|$viewNames|*, $creator::Class|function($view){})
- 手工创建响应
$response = response($content, $statusCode) $response->header($field, $value)->withHeaders(array $headers) $response->cookies($name, $value, $minutes [, $path, $domain, $secure, $httpOnly]) #中间件EncryptCookies::$except中配置不加密签名的cookie项
- 重定向响应
- 常规跳转:return redirect($url) - 带session闪存跳转:return redirect($url)->with($name, $value) - 跳至命名路由:return redirect()->route($routeName [, array $routeParams]) - 回到上一页:return back()->withInput(); #基于session实现的 - 跳至控制器方法:return redirect()->action('Controller@action' [, array $params])
视图
细节
blade
注释不会html
输出{ {-- 注释内容 --}}
- 内嵌PHP代码
@php xxx @endphp
- 视图堆栈定义
@push(栈名) xxx @endpush
,调用@stack(栈名)
- 服务注入
@inject('varName', My::class)
,调用{ { $varName }}
布局和区块
- 继承区块
@extends(区块名)
- 定义区块内容
@section(区块名, 'content')
@section(区块名) @parent xxx @endsection
#@parant
指令引入继承区块的内容
- 显示区块内容
@yield(区块名)
组件和插槽
用于视图组件的重用- 定义视图组件,设定插槽注入动态数据
- 默认插槽
{ { $slot }}
- 命名插槽
{ { $varname }}
- 默认插槽
- 调用视图组件
component(组件视图名 [, array $data]) @slot('varname') 命名插槽数据 @endslot 默认插槽数据endcomponent
子视图
- 父视图变量在子视图可用
@include(视图名 [, $data])
@includeif(视图名 [, $data])
- 集合渲染
@each(视图名, $collection, 'itemName', 集合空的候选视图)
数据输出
- 常规变量
{ { $var }}
- 非转义变量
{!! $var !!}
- 函数结果
{ { func() }}
- 抑制变量解析
@{ { $var }}
(最终结果双括号保留,@符剔除) - 抑制一段内容里的变量解析
@verbatim xxx @endverbatim
控制流
- 条件语句
@if (bool) xxx @elseif (bool) xxx @else xxx @endif
@unless (bool) xxx @endunless
@isset(bool) xxx @endisset
@empty(bool) xxx @endempty
- 循环语句
@for ($i = 0; $i < 10; $i++) xxx @endfor
@foreach () xxx @endforeach
@forelse () xxx @empty yyy @endforelse
@while (bool) xxx @endwhile
- 循环控制
@continue
@continue(bool)
@break
@break(bool)
*循环变量$loop->index
$loop->first
$loop->last
$loop->count
$loop->parent
自定义指令
- 需要清楚视图缓存
php artisan view:clear
Blade::directive('directName', function ($expression) { return " ";});
本地化
resources/lang/zh/file.php
- 语言配置
- 默认配置
config/app.php
- 动态配置
App::setLocale($locale)
- 判断配置
App::getLocale(); App::isLocale('en');
- 默认配置
- 读取(不存在则返回键名)
- 函数式
__('[filename.]lanKey' [, array $data])
($data
可替换翻译中的:xxx
占位符) - Blade式
@lang('[filename.]lanKey')
- 函数式
- 翻译复数
- 单复数形式用
|
隔开 - 可指定不同范围的复数形式
{0}xxx|[1,10]yyy|[11,*]zzz
- 复数翻译输出
trans_choice('[filename.]lanKey', $num)
- 单复数形式用
- 覆盖拓展包的翻译
resources/lang/vendor/{package}/{locale}
前端资源编译 - Mix
前端移除 可选移除前端脚手架 php artisan preset none
Mix运行
npm install #安装package.json中的依赖# 执行构建任务npm run devnpm run production# 监控文件变动自动重新构建npm run watchnpm run watch-poll #自动监控无效可用这个命令
Mix构建定义 - 基于webpack
定义构建任务(webpack.mix.js
)
# 构建基础mix.webpackConfig({}) #部分调整webpack配置(亦可整个重置webpack.config.js) .less|sass|js($from, $to, {$settings}) .options({processCssUrls:false}) .extract(['vue', ...]) #将指定依赖库导出到vendor.js文件 .version() #version后变名资源加载可通过 `mix(资源路径)` .disableNotifications() #关闭编译通知 .sourceMaps()# css|js文件合并mix.styles|scripts([$fromPaths], $targetPath)# 文件|目录拷贝mix.copy|copyDirectory($from, $to)# 使用编译的js(顺序加载)# `npm run环境`的检测mix.inProduction()
browserSync支持
1. mix.browserSync(域名|{详细配置}) #浏览器:3000 -> browserSyn -> WebServer -> 文件监控2. npm run watch
环境变量
.env
中MIX_
打头的变量- 使用:
process.env.变量名
数据校验
- 验证失败后
- 常规请求返回一个
redirect
至先前位置 ajax
请求返回json错误信息
及422状态码
- 常规请求返回一个
- 校验字段可通过点语法来嵌套
- 所有的验证错误会被自动
flash
至session
- 错误信息
MessageBag $errors
自动被ShareErrorsFromSession
中间件绑定到视图 - 字段的特殊校验
bail
先决规则(任意规则校验失败后,该字段后续规则不再进行)sometimes
规则(有则校验)
校验方式
- 控制器验证:校验请求
$this->validate($request, array $rules)
- 手动创建验证:校验请求数据
$validator = Validator::make($request->all(), array $rules); $validator->validate(); # 校验失败将自动跳转 $validator->fails(); $validator->after(function($validator){}); #验证后钩子
- 表单请求验证
- 定制了 数据校验&鉴权逻辑 的
Request
php artisan make:request MyRequest
- 路径app/Http/Requests
MyRequet
实现rules()+messages()、authorize()
逻辑- 控制器中类型提示注入
MyRequest
后会自动在方法执行前进行验证
- 定制了 数据校验&鉴权逻辑 的
错误消息
/** $return Illuminate\Support\MessageBag */$errors = $validator->errors();$errors->has('FieldName'); # 检查指定字段时候出错$errors->first('FieldName'); # 指定字段第一个错误提示$errors->get('FieldName'); # 指定字段所有错误提示$errors->all(); # 全部错误
数据库
底层
# 读写分离配置database连接配置下新增read、write键并配置相应db集群DB::connection(连接名)->select(...); # 数据库切换DB::connection()->getPdo(); # 获得底层pdo实例## 数据库事务 ##- 自动模式 DB::transaction(Closure);- 手动模式 DB::beginTransaction(); DB::rollBack(); DB::commit();## 数据库锁 ##$builder->sharedLock(); # 共享锁(锁住写入)$builder->lockForUpdate(); # 悲观所(锁住读写)
QueryBuilder
* 查询结果`Illuminate\Support\Collection`集合中的每个实例都是`StdClass`类型 *DB::get|first|select|insert|update|delete|statement($sql, array $params); # 原生sql查询$builder = DB::table('tableName'); # 查询构造器$builder->insert($row | $rows); # 批量插入$builder->chunk($perPage, function($records){...}); # 查询结果分块,闭包若返回false则将停止处理后续结果$builder->distinct(); # 去重$builder->->increment('field' [, $step]; # 递增$builder->whereColumn('field1', '=', 'field2'); # 列比较# 字段值$builder->value(FieldName); # 获取一行记录的字段值$builder->pluck(FieldName); # 获取集合的一列字段字段值$builder->pluck(KeyField, $ValueField); # 获取集合的两列键值字段# 字段选取$builder->select('field1', 'xxx as filed2');$builder->select(DB::raw('field1, xxx as filed2'));# 参数分组(通过where的闭包进行)$builder->where(function($query){ $query->where ...});# exist查询$builder->whereExists(function($query){ $query->select('xx')->from('xx')->where('child.parent_id = parent.id')...});## 子查询构建$builder = DB::table(DB::raw("({$childQuery->toSql()}) as tmp"))->->mergeBindings($childQuery->getQuery());
分页
# 系统默认当前页码?page=参数# 直接返回分页器, 将被框架自动转成JSON# 创建分页$builder|Model->paginate($perPage) // Illuminate\Pagination\LengthAwarePaginator 带完整分页信息$builder|Model->simplePaginate($perPage) // Illuminate\Pagination\Paginator 不查询分页情况(仅知道前后页的简单分页)# 内部集合数据$paginator->getCollection()$paginator->setCollection($collection)# 分页助手方法->links(【'view.自定义分页模板'】) # 分页链接(php artisan vendor:publish --tag=laravel-pagination)->setPath($uri) # 设定基础uri->appends(Array) # 分页链接加参数
Migration
php artisan make:migration create_xxxs_table --create|table=xxxs # migration建表php artisan migrate:rollback 【--step=num】 # 回滚上次数据库变动涉及的migration操作php artisan migrate:reset # 回滚所有migrationphp artisan migrate:refresh 【--step=num】 # 先重置后重载migrationSchema::hasTable($tableName) # 结构检查Schema::hasColumn($colName1, ...)Schema::connection('foo') # 切换链接Schema::rename($from, $to) # 重命名Schema::drop|dropIfExists($tableName) # 删除# 表定义$table->increments('id');$table->字段类型(字段名)->unsigned()->nullable()->default(默认值)->after(列名)->comment(注释); #字段定义$table->timestamps(); #created_at & updated_at$table->softDeletes(); #软删除deleted_at$table->索引类型(字段名 [, 索引名称]); #索引定义(复合索引则传入字段数组)# 杂项表处理$table->engine = 'InnoDB';$table->....->change(); #更新已有字段$table->renameColumn('from', 'to'); #重命名字段$table->dropColumn($colName); #删除字段$table->dropIndex($index); #删除索引# !注意事项! #SQLite 在单个Schema下只支持处理一个Column
EloquentORM
基础查询
php artisan make:model Models/ModelName -m# 查$builder = Model::query() | $model->newQuery()$builder->get()$builder->first|firstOrFail()$builder->chunk($num, function($rows{ });Model::all() #表中所有记录Model::find|findOrFail($id | $ids)# 游标查询foreach($builder->cursor() as $item) # 一次查一条,节约内存# 增$model=new MyModel;$model->save();Model::create($data);Model::firstOrCreate($data);Model::firstOrNew($data);# 删$builder->delete(); # 批量删除$model->delete();Model::destroy($ids);# 改$model->save();$builder->update($data); # 批量更新
模型定制属性
protected $connection = 'connectionName'; # 重定向连接名protected $table = 'tableName'; # 重定向表名(默认使用模型的SnakeCase复数形式为表名)protected $primaryKey = 'fieldName'; # 重定向主键名(默认为整型id)public $incrementing = false; # 声明主键为非递增、非数字# 默认的created_at、updated_at字段被转换成Carbonprotected $dates = [created_at', 'updated_at', 'deleted_at']; # 指定哪些字段被自动Carbon转换protected $dateFormat = 'Y-m-d H:i:s'; # 设定日期字段存储或序列化的格式const CREATED_AT = 'createtimeName'; # 重指定默认创建时间字段const UPDATED_AT = 'updatetimeName'; # 重指定默认ge时间字段public $timestamps = false; # 禁止自动维护时间字段(默认的单个模型save()方法调用时自动更新两个时间字段)# 调用create()批量赋值前配置下面属性之一(Mass-Assignment批量赋值保护)protected $guarded = []; # 黑名单,空数组则所有属性可被批量赋值protected $fillable = []; # 白名单# belongsTo、belongsToMany关系 更新父级时间戳protected $touches = [关联名];# 属性类型转换,支持integer,real,float,double,string,boolean,object,array,collection,date,datetime,timestampprotected $casts = [字段 => 目标数据类型];# 特色使用:json字段array类型转换为数组# 字段显示与隐藏(影响toArray、toJson方法)protected $hidden = ['password'];protected $visible = ['id', 'name'];# 临时修改字段可见性$model->makeVisible/makeHidden('field')->toArray();# array/json输出追加访问器字段protected $appends = ['is_admin'];public function getIsAdminAttribute(){ return (bool)this->attributes['admin'];}# 动态重定向连接名public function getConnectionName(){ return app()->environment('testing') ?'DatabaseName' : config('database.default');}# 字段值修饰器- 区别于`eloquence`中的`Mappable, Mutable`- accessor —— 修饰器方法取名为 “get字段驼峰式Attribute($value)”- mutator —— 修饰器方法取名为 “get字段驼峰式Attribute($value)”
软删除
# migration创建软删除字段$table->softDeletes();# 模型声明软删除(deleted_at非空时认定为记录已被删除)use SoftDeletes;protected $dates = ['deleted_at'];$model->trashed(); # 判断是否被软删除$model->forceDelete(); # 强制完全删除# 恢复被软删除的数据$model->restore(); # 单个恢复$builder->restore(); # 批量恢复# 默认软删除数据不在查询结果中Model::withTrashed() # 声明包含软删除数据Model::onlyTrashed() # 声明只查询软删除数据
查询作用域(增加查询约束条件)
## 全局作用域 ##class CustomScope implenments Scope{ public function apply(Builder $builder, Model $model) { return $builder->where... }}class CustomModel extends Model{ protected static function boot() { parent::boot(); # 模型绑定全局作用域类 static::addGlobalScope(new AgeScope); # 模型绑定全局域闭包 static::addGlobalScope('age', function(Builder $builder) { $builder->where... }); }}# 临时解除全局作用域Model::withoutGlobalScope(...)## 本地作用域 ##class CustomModel extends Model{ public function scopeXxx($query【, $params】) { return $query->where... }}# 本地作用域(临时作用)$builder->Xxx([$params])... CustomModel::Xxx([$params])
打印SQL
/* @var \Illuminate\Database\Eloquent\Builder $query */$query->toSql();\DB::enableQueryLog();# SQL查询dd(\DB::getQueryLog());
模型事件
# 模型生命周期事件creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored# 监听模型事件AppServiceProvider->boot(){ # 监听sql查询事件 \DB::listen(function ($query) { dump($query->sql, $query->bindings); }); # 注册模型单一事件监听器 XxxModel::creating(function ($xxxModel) { # 返回false将取消 save / update 操作 return boolean; }); # 用observer管理模型一组事件监听器 XxxModel::observe(XxxObserver::class);}class XxxObserver{ public function created(XxxModel $Xxx){ ... }}
关联查询
关联定义
多对多关联是个绑定关系 & 其他关联是个衍生关系## 正向关系 ##- 子模型外键参考为“snake父模型名_主键”- 父模型本地键参考为“主键”$this->hasOne(子模型, 外键, 本地键) #一对一$this->hasMany(子模型, 外键, 本地键) #一对多## 反向关系 ##- 子模型外键参考为“snake关联方法名_主键”- 父模型其他键参考为“主键”$this->belongsTo(父模型, 外键, 其他键) #一对一$this->belongsTo(父模型, 外键, 其他键) #多对一## 多对多 ##- 中间表名参考为“字母顺序排列组合的下划线表名”- 中间表上的外键: * 当前模型外键参考为“snake当前模型名_主键” * 关联模型外键参考为“snake关联模型名_主键”$this->belongsToMany(关联模型, 中间表, 当前模型外键, 关联模型外键) ->using(中间表模型) #可选,自定义中间表模型(继承Illuminate\Database\Eloquent\Relations\Pivot) ->wherePivot|wherePivotIn() #可选,过滤中间表字段 ->withPivot($field1, ...) #可选,声明中间表包含的外键以外的字段## 远程一对多 ##一对多两级放大- 中间模型外键参考为“snake模型名_主键”- 远程模型外键参考为“snake模型名_主键”- 当前模型本地键参考为“snake模型名_主键”$this->hasManyThrough(远程模型, 中间模型, 中建模型外键, 远程模型外键, 本地键)## 一对多morph多态 ##多个一衍生针对同组多- 可选关联名参考为“关联方法名”,必选关联名建议为“子模型名able”- type字段名参考为“关联名_type”- id字段名参考为“关联名_id”- 本地键参考为“主键”正向s: $this->morphMany(子模型, 必选关联名, [type字段名, id字段名, 本地键])反向able:$this->morphTo([可选关联名, type字段名, id字段名])- 默认“type字段值”参考为“完整命名空间指定模型名”- 自定义type字段值需注册多态映射到AppServiceProvider::bootuse Illuminate\Database\Eloquent\Relations\Relation;Relation::morphMap([type => 模型]);## 多对多morph多态 ##多个一跨中间表绑定共享同一组多多对多关联基础上,其中一端多进行多态化正向s: $this->morphToMany(父模型, 必选关联名)多个反向s: $this->morphedByMany(子模型, 必选关联名)
查询关联
################ 查询关联数据 - 懒加载 ################直接通过动态属性访问关联是“懒加载”,在访问关联数据属性时才加载关联查询: - $model->$relation #直接动态属性访问关联 - $model->$relation->pivot #pivot属性访问中间表(多对多关联场景,且默认只能访问到外键字段)################ 查询关联数据 - 预加载 ################使用with方法“EagerLoad预加载”,在查询父数据时即加载关联(其`WhereIn`机制解决了`N+1`问题,减少总查询至2次)声明: - 模型::with($relation1, ...) #加载多个关联 - 模型::with($relation1.$relation1_a, ...) #加载嵌套关联 - 模型::with([$relation => function($query){ $query->关联数据约束、排序等 }])->... #约束关联数据 - $collection|$model->load($relation1, ... | [$relation1 => function])#延迟预加载(手动预加载)查询: - $model->$relation #with声明后动态属性访问关联################ 查询关联计数 ################只统计不加载数据声明: - 单个关联计数 模型::withCount(关联名)->... - 别名关联计数 模型::withCount('关联名 AS 别名')->... - 多个关联计数 模型::withCount([关联1, 关联2])->... - 约束关联计数 模型::withCount([关联 => function($query){ $query->where(关联数据约束) }])->...查询: - $model->$relation_count #动态属性访问计数######## 关联过滤(过滤条件施加到关联数据上) ########## 查询结果 - 关联数据 ##- 模型::with([$relation => function($query){ $query->关联数据约束、排序等 }])->... #with声明时关联过滤- $model->$relation()->where #关联结果进一步过滤## 查询结果 - 主对象 ###1. 关联数据存在性 约束 - 模型::has(关联名 [,比较符, 数量])->... #关联名中可进一步使用点语法来声明关联数据下属数据的存在性 - 模型::whereHas|orWhereHas(关联名, function($query){ $query->where(关联数据约束) })->...#2. 关联数据不存在性 约束 - 模型::doesntHave(关联名)->... - 模型::whereDoesntHave(关联名, function($query){ $query->where(关联数据约束) })->...
插入/更新 关联数据
## 正向关系 ###$parent->relations()->save($child)$parent->relations()->saveMany(array $childs)$parent->relations()->create(array $child1_data)## 反向关系 ##$child->relation()->associate($parent) && $child->save()$child->relation()->dissociate($parent) && $child->save()## 多对多关联 ##--通过id处理-- - $modelA->relations()->attach($modelB_ID | $modelB_IDS [, array 中间表更新]) #中间表追加绑定关系 - $modelA->relations()->detach($modelB_ID | $modelB_IDS) #中间表解除绑定关系 - $modelA->relations()->detach() #中间表解除A的所有绑定 - $modelA->relations()->sync($modelB_IDS) #中间表重置绑定关系 - $modelA->relations()->syncWithoutDetaching($modelB_IDS) #中间表重置绑定关系(但是不解除已有绑定) - $modelA->relations()->toggle($modelB_IDS) #中间表切换绑定关系(已绑的解绑, 未绑的绑定)--通过对象处理-- - 使用正向关系里的所有方法 - $modelA->relations()->save($modelB [, array 中间表更新]) #中间表追加绑定关系 - $modelA->relations()->updateExistingPivot($modelB_ID , array 中间表更新) #更新中间表某条绑定关系的数据
集合
普通集合 - Illuminate\Support\Collection
类
- 递归序列化
$collection|$model->toArray()
$collection|$model->toJson()
- 数组转集合
collect($array)
- 动态属性方式 调用 高阶信息方法
contains,each,every,filter,first,map,partition,reject,sortBy,sortByDesc,sum
- $users->each->markAsVip();
- $users->sum->votes;
ORM集合 - Illuminate\Database\Eloquent\Collection
- 继承于基础集合 Illuminate\Support\Collection
缓存
Cache::store($storeType)->...;# 切换缓存的store驱动Cache->get($key【, $default】);Cache->put($key, $value, $minutes|$expiresAt);Cache::forget($key); //删除缓存Cache::flush(); //清空所有缓存Cache->add($key, $value, $minutes|$expiresAt); //不存在时才更新,实际写入时返回trueCache->forever($key【, $default】); //永久缓存Cache::has($key);Cache::increment|decrement($key【, $step】);Cache::remember($key, $minutes, Closure); //获取值,不存在则闭包更新Cache::pull($key); // 一次性获取然后删除# 缓存标签Cache::tags(array $tags)->put($key, $value, $minutes);Cache::tags(array $tags)->get($key);Cache::tags(array $tags)->flush();# EventServiceProvider中可注册监听缓存事件
事件
绑定
- 常规绑定 -
EventServiceProvider->listen
中注册绑定
* 根据绑定配置自动创建 `php artisan event:generate` * protected $listen = [ 事件类 => [ 监听器类1, .... ], ];
- 绑定闭包事件处理器 - 在
EventServiceProvider::boot()
中注册
public function boot(){ parent::boot(); # 单一事件监听 Event::listen('event.事件名', function($data){}); # 全局事件监听 Event::listen('event.*', function($eventName, array $eventData){});}
单事件监听器
- 停止事件传播:
handle
中返回false
将会 - 队列化监听器
- 声明实现
ShouldQueue
接口 public $connection|$queue
定制 连接&队列public function failed(OrderShipped $event, $exception)
中处理FailedJob
- 声明实现
多事件订阅者
EventServiceProvider $subscribe
中注册 namespace App\Listeners;class MyEventSubscriber{ public function onMyAction($event){...} # 处理订阅逻辑 public function subscribe($events) { $events->listen(事件类, 'MyEventSubscriber@onMyAction'); }}
派发事件 event(new MyEvent())
账户认证
快速搭建
php artisan make:auth
生成认证相关的路由、视图、home示例php artisan migrate
数据表准备config/auth.php
配置- guards - 账号认证模式(内置
web session、api token
两种) - providers - 持久层账号读取模式(内置
Eloquent、Database
两种)- 密码字段60+字节
- 存在可空、100字节的
remember_token
字段(用于记住我)
- guards - 账号认证模式(内置
- 框架已预置User模型
- 框架已预置4个Auth控制器
- RegisterController
- LoginController
- 定制认证字段
username()
(默认email
字段) - 登录限流
use ThrottlesLogins
(认证字段+IP限流试登次数/Min)
- 定制认证字段
- ForgotPasswordController
- ResetPasswordController
Auth控制器定制
- 定制认证后跳转地址
$redirectTo | redirectTo()
(默认/home
) - 定制
Guard
认证模式guard()
登录检查
- 手工检查
Auth::check()
auth
路由中间件检查- 默认
guard
:->middleware('auth')
- 指定
guard
:->middleware('auth:api')
- 默认
获取用户
$request->user();Auth::user();Auth::id();
自主认证
$credentials = ['email' => $email, 'password' => $password, ...] #凭据Auth::[guard('web')]->attempt($credentials [, $boolRememberMe]) #登录(返回bool,登录成功则启动认证session)Auth::logout() #登出(清除session登录信息)Auth::once($credentials) #仅认证一次当前请求(no session)return redirect()->intended(备用地址) #跳至被认证中间件截断的原先意向页面Auth::viaRemember() #检查用户是否通过`RememberMe cookie`登录(返回bool)
模拟身份
# 登入为指定用户Auth::[guard('web')]->login($user [, $boolRemember])Auth::[guard('web')]->loginUsingId($userId [, $boolRememberMe])
自定义Guard
# 定义Illuminate\Contracts\Auth\Guard# AuthServiceProvider::boot()下注册Auth::extend('my_guard_driver', function($app, $name, array $config){ return new MyGuard(Auth::createUserProvider($config['provider']));});# config('auth.guards')下配置'guards' => [ 'my_guard' => [ 'driver' => 'my_guard_driver', 'provider' => 'users', ],],
自定义Providor
# 定义Illuminate\Contracts\Auth\UserProvider# AuthServiceProvider::boot()下注册Auth::provider('my_provider_dirver', function($app, array $config) { return new MyProvider($app->make('riak.connection'));});# config('auth.guards')下配置'providers' => [ 'my_provider' => [ 'driver' => 'my_provider_dirver', ],],
认证事件
# Illuminate\Auth\Events空间下事件- Registered- Attempting- Authenticated- Login- Failed- Logout- Lockout
密码重置
内建账户系统的重置机制User
模型配置use Notifiable
implement CanResetPassword
(use CanResetPassword
来实现)
- 创建
reset token
表 - 忘记密码、重置密码 的 路由/控制器/视图
- 定制处理
ForgotPasswordController|ResetPasswordController::broker()
定制Password Broker
User::sendPasswordResetNotification()
定制重置邮件的通知类
加密解密
- 基于OpenSSL提供AES加密(并使用MAC消息认证码签名)
- 如果值不能被正确解密则抛出异常
- 加解密(加密前准备
APP_KEY
)- 原档预先序列化
encrypt|decrypt()
(支持字符串、对象、数组) - 原档不做序列化
\Crypt::encryptString|decryptString()
(支持字符串)
- 原档预先序列化
HASH
Hash
提供Bcrypt
散列处理(默认用于内建账户系统的密码存储)- 散列使用
\Hash::make($str);
\Hash::check($str, $hashedStr);
鉴权
Gate鉴权 简易、闭包式、资源无关的 鉴权(定义在AuthServiceProvider::boot())
## 定义 ##Gate::define(权限名, function ($user [, $model]) { return bool});Gate::define(权限名, '策略类@操作')Gate::resource(资源名, 策略类名) #资源式Gate(默认 资源.view、create、update、delete)## 鉴权 ##Gate::allows|denies(权限名 [, $model]) #默认当前用户Gate::forUser($user)->allows|denies(权限名 [, $model]) #明确指定用户
Policy鉴权 鉴权特定资源的几个操作
## 定义 ##php artisan make:policy MyPolicy [--model=My]AuthServiceProvider::$policies #注册以关联 策略&资源MyPolicy::before($user, $ability) #策略过滤以预鉴权(返回null才进入policy鉴权)## 鉴权 ##$user->can|cant(权限名, $model|Model::class) #未通过则返回falseController->authorize(权限名, $model|Model::class) #未通过则抛出AuthorizationException## Blade模板鉴权 ##can|cannot(权限名, $model|Model::class) xxxelsecan|elsecannot(权限名, $model|Model::class) xxxendcan|endcannot
Artisan命令
创建命令
- 新建
php artisan make:command 命令名
(默认目录app/Console/Commands
) - 参数
- 必选参数
{name=default}
- 可选参数
{name?}
- 数组参数
{name*}
- 必选参数
- 选项
- 开关选项
{--opt}
(选项简写{--O|opt}
) - 参数选项
{--opt=default}
- 数组选项
{--opt*}
- 开关选项
- 选项/参数 加注释
: description
- 读取数据(无则返回
null
)- 参数:指定参数
$this->argument('name')
、所有参数$this->arguments()
- 选项:指定选项
$this->option('name')
、所有选项$this->options()
- 参数:指定参数
交互
- 提示输入
- 明文输入
$answer = $this->ask('question?');
- 密文输入
$answer = $this->secret('question?');
- 明文输入
- 确认提醒
if ($this->confirm('Are you sure?'))
- 自动完成
$name = $this->anticipate('Whats your name?', ['Tom', 'Jim']);
- 选择项
$name = $this->choice('Whats your name?', ['Tom', 'Jim'], 'defaultName');
输出
- 文字输出
- 无色
$this->line()
- 绿色
$this->info()
- 红色
$this->error()
$this->question()
$this->comment()
- 无色
- 表格输出
$this->table([$field1...], [$value1...])
- 进度条
$bar = $this->output->createProgressBar($num); $bar->advance(); $bar->finish();
注册命令
- 框架默认注册
$this->load(__DIR__.'/Commands');
- 手工注册
Kernel::$commands
编程调用命令
- 常规方式:
Artisan::call($command, array $args);
- 队列化调用:
Artisan::queue($command, array $args)->onConnection(连接名)->onQueue(队列名);
- 命令中调用命令
- 普通场景
$this->call($command, array $args);
- 静默场景
$this->callSilent($command, array $args);
- 普通场景
其他命令
- 闭包命令 - 注册在
routes/console.php
Artisan::command('sig:nature {arg}' function($arg){...})->describe(命令描述);
- 定时任务 - 注册在Kernel::schedule( )
内置服务器
php artisan serve # 启动本地开发服务器localhost:8000php artisan down --message='系统升级中' # 进入维护模式:关闭服务、队列(默认视图resources/views/errors/503.blade.php)php artisan up # 服务重启
Schedule
定义调度任务
- 定义在
App\Console\Kernel::schedule(Schedule $schedule)
- 系统配置
* * * * * php 项目路径/artisan schedule:run >> /dev/null 2>&1
调度模式
- 闭包模式
$schedule->call(function(){. . .})->daily();
- Artisan命令模式
$schedule->command('xxx:yyy --force')->daily(); $schedule->command(XxxCommand::class, ['--force'])->daily();
- 系统命令模式
$schedule->exec('echo HelloWorld')->daily();
调度设置
调度频率
->cron('* * * * * *') #自定义频率->everyMinute()->hourly() | ->hourlyAt(20)->daily() | ->dailyAt('13:00')->weekly()->monthly() | ->monthlyOn(4, '15:00')->quarterly()->yearly()
额外约束
->when(闭包)->at('13:00')->between('8:00', '17:00')->>mondays|weekdays|sundays|...()->timezone('America/New_York') #设置时区
任务输出
# 只适用于 $schedule->command() 方法->appendOutputTo|sendOutputTo|emailOutputTo($filePath)
任务钩子
- 前后置钩子
->before|after(闭包)
- 前后置
Ping
钩子->pingBefore|thenPing(闭包)
(依赖Guzzle
)
特殊设定
- 避免任务重叠
->withoutOverlapping()
- 常用于耗时任务上的配置
- 上一次任务还在运行则不再重叠运行,即仅在任务尚未运行时才运行
- 维护模式下也强制运行
->evenInMaintenanceMode()
测试
覆盖面:
- 所有http请求类型
- 正常场景:数据 &结构&状态码
- 异常场景:状态码
- 筛选参数
- 数据分页
# mock数据(视情况可选)$mock = \Mockery::mock(XxxRepository::class);$mock->shouldReceive($methodName)->andReturn($model);$this->app->instance(XxxRepository::class, $mock); #容器针对某类绑定到mock实例# 发起请求$this->get/post($api [, array $headers]);# 测试响应$this->seeStatusCode(200);$this->seeHeader($headName【, $headVal】);$this->seeJsonStructure(array $structure);$this->seeJsonContains(array $structure);$this->seeJsonEquals(array $structure); # 要求完整数据结构$this->seeInDatabase($tableName, array $data);$this->assertCount($num, array $testData);
文件系统
config/filesystems.php
中配置文件系统连接及其相应驱动
Public文件系统 - 公共访问磁盘
- 默认
local
驱动(根路径storage/app
) - 软连接
public/storage->storage/app/public
(php artisan storage:link
) - 访问资源
asset('storage/myfile.txt')
FTP文件系统
- 增加驱动配置
'ftp' => [ 'driver' => 'ftp', 'host' => 'ftp.example.com', 'username' => 'your-username', 'password' => 'your-password', // 'port' => 21, // 'root' => '', // 'passive' => true, // 'ssl' => true, // 'timeout' => 30,],
文件系统操作
$storage = Storage::disk(文件系统连接);->url|get|exists|size|lastModified|getVisibility($path)->copy|move($fromPath, $toPath)->delete($filePath|$files)->put|prepend|append($path, $content|$resource) //大文件建议使用资源句柄# 自动流式处理,返回完整文件名路径* ->putFile($saveDir, Illuminate\Http\File|Illuminate\Http\UploadedFile) #自动生成文件名* ->putFileAs($saveDir, Illuminate\Http\File|Illuminate\Http\UploadedFile,$saveName) #指定文件存储名# 上传文件处理,返回完整文件名路径* Illuminate\Http\UploadedFile $request->file($name)->store($saveDir [, 文件系统连接]) #自动生成文件名* Illuminate\Http\UploadedFile $request->file($name)->storeAs($saveDir, $saveName [, 文件系统连接]) #指定文件存储名# 目录->files|directories($dir) #不含子目录->allFiles|allDirectories($dir) #包含子目录->makeDirectory|deleteDirectory($dir) #目录增删
增加文件系统驱动
1. composer 安装驱动包2. 新建ServiceProvidor,boot方法中拓展文件系统驱动 public function boot() { Storage::extend($fileDriverName, function ($app, $config) { $client = new XxxClient(); return new Filesystem(new XxxAdapter($client)); }); }、3. 基于新驱动配置新文件系统连接
国内云存储驱动
- 七牛云、又拍云、OSS、COS
composer require yangyifan/upload:v0.2.1
上线部署
- 清理旧缓存
php artisan cache:clear
(注意把redis
缓存库 和 队列、session
库分离开) - 缓存配置
php artisan config:cache
- 缓存路由
php artisan route:cache
classmap
生成php artisan optimize --force
- 自动加载优化
composer dumpautoload --optimize
Redis
存储Session
- 启用
OpCache
- 静态资源合并