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

綁定javascript

* 僅當須要修改容器中綁定的對象時才進行手工綁定 *
$this->app->bind(類名, 閉包); # 簡單綁定
$this->app->bind(接口名, 類名); # 接口實現綁定
$this->app->singleton(類名, 閉包); # 單例綁定
$this->app->instance(類名, 對象); # 實例綁定
$this->app->when(類名)->needs(類名)->give(閉包); # 場景綁定
$this->app->when(類名)->needs('$變量名')->give(變量值); # 數據綁定

解析php

* $this->app->make(類名);
* app(類名)
* resolve(類名);
* 自動依賴注入

容器事件css

* 每當服務容器解析一個對象前就會觸發一個事件 - 前置回調 *
$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 路由

路由方法html

  • 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 }}

佈局和區塊vue

  • 繼承區塊@extends(區塊名)
  • 定義區塊內容
    • @section(區塊名, 'content')
    • @section(區塊名) @parent xxx @endsection #@parant指令引入繼承區塊的內容
  • 顯示區塊內容@yield(區塊名)

組件和插槽
用於視圖組件的重用java

  1. 定義視圖組件,設定插槽注入動態數據
    • 默認插槽{{ $slot }}
    • 命名插槽{{ $varname }}
  2. 調用視圖組件
component(組件視圖名 [, array $data])
	@slot('varname')
		命名插槽數據
	@endslot
	
	默認插槽數據
endcomponent

子視圖webpack

  • 父視圖變量在子視圖可用
  • @include(視圖名 [, $data])
  • @includeif(視圖名 [, $data])
  • 集合渲染@each(視圖名, $collection, 'itemName', 集合空的候選視圖)

數據輸出laravel

  • 常規變量{{ $var }}
  • 非轉義變量{!! $var !!}
  • 函數結果 {{ func() }}
  • 抑制變量解析@{{ $var }}(最終結果雙括號保留,@符剔除)
  • 抑制一段內容裏的變量解析@verbatim xxx @endverbatim

控制流web

  • 條件語句
    • @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
  • 靜態資源合併
相關文章
相關標籤/搜索