Laravel学习笔记 - 麦拂沙的个人空间 - 开源中国

[Laravel学习笔记 - 麦拂沙的个人空间 - 开源中国]

j. Laravel笔记

安装

composer create-project --prefer-dist laravel/laravel projectname
chmod -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=singlesingle、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
  • 所有Providerregister完毕后才调用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'=>[..]])
$url = route('profile', Array $context) # 从命名路由生成url
return redirect()->route('profile'); # 重定向到命名路由

## 当前路由信息 ##
$route = Route::current();
$name = Route::currentRouteName();
$action = Route::currentRouteAction();

## 控制器中调用路由参数 ##
$this->route('ParaName')

## 表单方法伪造 ##
{{ method_field('PUT') }}

# 生成路由缓存(仅对基于控制器实现的路由有效)
php artisan route:clear
php artisan route:cache

HTTP中间件

  • 中间件用于过滤进入应用程序的 HTTP 请求
  • 中间件注册

    • Laravel app/Http/Kernel.php
    • lumen bootstrap/app.php
  • 中间件类型

    • 前置中间件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头部类型

    1. <meta name="csrf-token" content="{{ csrf_token() }}">
    2. $.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(区块名)

组件和插槽
用于视图组件的重用

  1. 定义视图组件,设定插槽注入动态数据

    • 默认插槽{{ $slot }}
    • 命名插槽{{ $varname }}
  2. 调用视图组件
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 "<?php echo xxx; ?>";
});

本地化

  • 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 dev
npm run production

# 监控文件变动自动重新构建
npm run watch
npm 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(顺序加载)
<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/app.js"></script>

# `npm run环境`的检测
mix.inProduction()

browserSync支持

1. mix.browserSync(域名|{详细配置})  #浏览器:3000 -> browserSyn -> WebServer -> 文件监控
2. npm run watch

环境变量

  • .envMIX_打头的变量
  • 使用:process.env.变量名

数据校验

  • 验证失败后

    • 常规请求返回一个redirect至先前位置
    • ajax请求返回json错误信息及422状态码
  • 校验字段可通过点语法来嵌套
  • 所有的验证错误会被自动flashsession
  • 错误信息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 # 回滚所有migration
php artisan migrate:refresh 【--step=num】 # 先重置后重载migration

Schema::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字段被转换成Carbon
protected $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,timestamp
protected $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::boot
use 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); //不存在时才更新,实际写入时返回true
Cache->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())

账户认证

快速搭建

  1. php artisan make:auth生成认证相关的路由、视图、home示例
  2. php artisan migrate数据表准备
  3. config/auth.php配置

    • guards - 账号认证模式(内置 web session、api token两种)
    • providers - 持久层账号读取模式(内置Eloquent、Database两种)

      • 密码字段60+字节
      • 存在可空、100字节的remember_token字段(用于记住我)
  4. 框架已预置User模型
  5. 框架已预置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 CanResetPassworduse 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) #未通过则返回false
Controller->authorize(权限名, $model|Model::class) #未通过则抛出AuthorizationException

## Blade模板鉴权 ##
can|cannot(权限名, $model|Model::class)
    xxx
elsecan|elsecannot(权限名, $model|Model::class)
    xxx
endcan|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:8000
php 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/publicphp 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
  • 静态资源合并

Original url: Access

Created at: 2018-10-10 17:19:40

Category: default

Tags: none

请先后发表评论
  • 最新评论
  • 总共0条评论