laravel 5 學習筆記 · 2017-10-02 · laravel 5 .env 檔案 在 laravel 5 使用.env...
Post on 30-May-2020
61 Views
Preview:
TRANSCRIPT
1.1
1.2
1.2.1
1.2.2
1.3
1.3.1
1.3.2
1.3.2.1
1.3.2.2
1.3.2.3
1.3.2.4
1.3.3
1.3.3.1
1.3.3.2
1.3.3.3
1.3.4
1.3.4.1
1.4
1.4.1
1.4.2
1.4.2.1
1.4.3
1.4.3.1
1.5
1.6
1.6.1
1.6.2
目錄
介紹
環境
.env檔案
Homestead
資料庫
Migration(遷移)
EloquentModel(模型)
設定
關聯
魔術函式
使用Eloquent
常見問題
無法取得查詢Log
使用大量資料的方式新增時無法新增
使用中繼模型繼承Eloquent模型造成無法使用大量資料新增
PostgreSQL
安裝PostgreSQLODBCdriver
HTTP
請求
路由(Route)
子網域路由
中介層(Middleware)
ETagMiddleware
視圖(View)
服務
認證登入(Auth)
郵件(Mail)
2
1.6.2.1
1.6.2.2
1.6.3
1.6.3.1
1.6.3.2
1.6.4
1.6.4.1
1.6.5
1.6.5.1
1.6.6
1.6.6.1
1.6.6.2
1.6.6.3
1.6.7
1.6.8
1.6.9
1.6.10
1.6.10.1
1.7
1.7.1
1.7.2
1.7.3
1.7.3.1
1.7.4
1.8
1.8.1
1.8.1.1
1.8.2
1.8.2.1
1.8.3
使用Gmail寄信
使用Mailgun寄信
隊列(Queue)
database
非同步(async)
輔助方法(Helpers)
自定義輔助方法
單元測試(UnitTest)
PostCSRF錯誤
錯誤與日誌
在單元測試顯示例外
日誌記錄層級
日誌巨集
加密
雜湊
任務排程
Elixir
使用Elixir合併CSS與JS
設計模式
服務容器
PSR
Model模型
架構設計準則
學習資源
套件
Debug
Artisantail
工具
Carbon
設計模式
3
1.8.4
1.8.5
1.8.6
1.8.6.1
1.8.6.1.1
1.8.6.1.2
1.8.6.1.3
1.8.6.1.4
1.8.6.1.5
1.8.6.2
1.9
1.9.1
1.9.2
1.9.3
1.9.4
1.9.5
1.9.6
1.9.7
1.10
1.10.1
1.10.2
1.10.3
1.11
1.11.1
1.11.2
1.11.3
1.11.4
1.11.5
1.11.6
1.11.7
文字
視圖
認證
OAuth2
ClientCredentials
PasswordGrant
RefreshToken
PasswordGrantwithScope
驗證AccessToken
JWT
正式主機環境
安裝Nginx
安裝php7
安裝composer
安裝MySQL5.7
安裝Memcached
安裝Redis
安裝Let'sEncrypt
其他常見問題
CalltoundefinedmethodgetCachedCompilePath()
變更專案目錄名稱導致View無法讀取
Laravel5.1目錄結構異動
學習資源
官方
社群
會議議程
工作
文件
文章
套件
4
1.11.8
1.11.9
1.11.10
1.11.11
1.11.12
1.11.13
服務工具
教學影片
教學網站
編輯開發
主機
成功案例
5
Laravel5學習筆記
作者:KeJyun
書籍網址
項目 網址
GithubPage http://kejyun.github.io/Laravel-5-Learning-Notes-Books/
GitBook http://kejyuntw.gitbooks.io/laravel-5-learning-notes/
聯絡資訊
項目 網址
Email kejyun@gmail.com
LinkedIn https://tw.linkedin.com/in/kejyun
Github https://github.com/kejyun
Facebook http://fb.me/kejyunTaiwan
Blog http://blog.kejyun.com
所有KeJyun著作
HighScalingWebsitesStructureLearningNotes大型網站架構學習筆記
Laravel4學習筆記
Laravel5學習筆記
SEO學習筆記
gulp學習筆記
WebDeveloperLearningResource網頁開發學習資源
MacOSX新手入門
RubyonRails學習筆記
介紹
6
介紹
7
Laravel5環境
這裏會介紹一些Laravel5會用到的機器環境相關問題
環境
8
.env檔案
Laravel4.env檔案
在Laravel4的時候,我們通常會在 /bootstrap/start.php中,去設定我們的
hostname是屬於哪一種開發環境,再針對不同的開發環境有不同的設定檔
( .env.*.php)
<?php
$env=$app->detectEnvironment(array(
'local'=>array(
'KeJyun-Macbook'
),
'dev'=>[],
'testing'=>[],
'staging'=>[],
));
.env.*.php設定檔通常放在根目錄下,這些檔案不會在版本控制當中
app/
bootstrap/
public/
vendor/
.env.php
.env.local.php
.env.dev.php
.env.testing.php
.env.staging.php
在Laravel4.env設定檔案長的會像是這樣:
.env檔案
9
<?php
return[
'DB_USERNAME'=>'root',
'DB_PASSWORD'=>'password',
];
我們的 config檔案就可以使用 $_ENV去讀取當前環境的設定檔資料
<?php
$_ENV['DB_USERNAME']
$_ENV['DB_PASSWORD']
Laravel5.env檔案
在Laravel5使用 .env檔案的方式跟Laravel4有很大的不同,在Laravel5中就只有 .env與 .env.example這兩個檔案而已, .env檔案不會在版本控制
中, .env.example則會在版本控制中
自己可以根據自己的環境設定目前的.env狀況,而.env.example則是可以讓大家
參考.env的範例用的,自己根據自己目前的環境設定是什麼樣到開發還境
(local、dev、stage、production...etc)。
在Laravel5.env設定檔案長的會像是這樣:
APP_ENV=local
APP_DEBUG=true
APP_KEY=VDqhX1LiHKEReHH16YNEzxUZziOdZVtT
DB_HOST=localhost
DB_DATABASE=homestead
DB_USERNAME=homestead
DB_PASSWORD=secret
CACHE_DRIVER=file
SESSION_DRIVER=file
.env檔案
10
然後我們在設定檔中,我們可以使用 env()函式去取得我們設定檔案中的設定,
會設定像是這樣的設定
//config/app.php
[
'debug'=>env('APP_DEBUG',false),
]
//config/database.php
[
'pgsql'=>[
'driver'=>'pgsql',
'host'=>env('DB_HOST','localhost'),
'database'=>env('DB_DATABASE','forge'),
'username'=>env('DB_USERNAME','forge'),
'password'=>env('DB_PASSWORD',''),
'charset'=>'utf8',
'prefix'=>'',
'schema'=>'public',
],
]
env()中第一個參數是 .env檔案中的設定鍵值名稱,第二個參數是預設值,
若讀取不到環境設定鍵值則會使用預設值
這樣在每個人的開發環境只要去管理自己的 .env檔案就好,不需要像Laravel4需要在config資料夾中建立各種環境變數設定的資料夾,像是
config/local/database.php或 config/stage/database.php。
參考資料
EnvironmentsandConfiguration-Laravel5Fundamentals
.env檔案
11
Homestead常常我我們需要開發WebApplication時候,都需要花很長的時間把環境建置起來,
而Homestead以讓我們透過簡單的設定檔,輕鬆的建置好整個執行環境,讓我們能
夠更專注於在開發上
OSXElCapitan10.11.3
Laravel5.2
Vagrant1.8.1
Virtualbox5.0.14
安裝Virtualbox從VirtualBox官方網站下載並安裝VirtualBox
下載連結:http://download.virtualbox.org/virtualbox/5.0.14/VirtualBox-5.0.14-105127-OSX.dmg
安裝Vagrant從Vagrant官方網站下載並安裝Vagrant
下載連結:https://releases.hashicorp.com/vagrant/1.8.1/vagrant_1.8.1.dmg
安裝HomesteadVagrantBox在VirtualBox及Vagrant安裝完後,使用下列指令將Homestead加入您的虛擬主
機中
vagrantboxaddlaravel/homestead
Homestead
12
使用Git複製Laravelhomestead並建立Homestead設定檔
$gitclonehttps://github.com/laravel/homestead.gitHomestead
$cdHomestead
~/Homestead$bashinit.sh
建立sshkey
$ssh-keygen-trsa-C"kejyun@homestead"
編輯Homestead設定檔
$vim~/.homestead/Homestead.yaml
確認設定檔中sshkey有對應到正確的路徑
authorize:~/.ssh/id_rsa.pub
keys:
-~/.ssh/id_rsa
設定虛擬主機提供者
vagrant支援 virtualbox, vmware_fusion或 vmware_workstation這幾個
虛擬主機,我們使用VirtualBox所以就填入virtualbox
provider:virtualbox
設定本機程式碼路徑對應到測試環境的路徑共用資料夾
Homestead
13
map:本機路徑
to:Vagrant測試機路徑
程式路徑
folders:
-map:~/Code
to:/home/vagrant/Code
-map:~/laravel52
to:/home/vagrant/laravel52
網站路徑
sites:
-map:kejyun.app
to:/home/vagrant/laravel52/public
Homestead
14
設定 /ect/hosts對應本機網址到Vagrant
$sudovim/etc/hosts
加入下列設定
Homestead
15
192.168.10.10kejyun.app
IP位址為Homestead.yaml設定中的ip設定值,kejyun.app則是參照設定檔
中的sitesmap設定有哪些則加進去hosts設定檔中
啟動Vagrant
~/Homestead$vagrantup
在剛剛使用Git複製下來的homestead資料夾中執行vagrantup指令,會自動下
載virtualbox相關的虛擬主機設定,並依照設定檔設置虛擬主機。
設定完成後可以開啟瀏覽器到http://kejyun.app看看Homestead是否有正常啟動,
若看到下列畫面表示可以正常運作了!!!
Homestead
16
備註
若要關閉Homestead虛擬機器,則可以使用下列指令關閉
vagranthalt
若設定檔有修改要重新讀取,則可以使用下列指令重新讀取設定
vagrantprovision
SSLread:error:00000000:lib(0):func(0):reason(0),errno60
Homestead
17
若在啟動homestead時出現SSLread的錯誤訊息的話,可以重新加入新的
homesteadvagrandbox
$vagrantup
Bringingmachine'default'upwith'virtualbox'provider...
==>default:Box'laravel/homestead'couldnotbefound.Attempt
ingtofindandinstall...
default:BoxProvider:virtualbox
default:BoxVersion:>=0.4.0
==>default:Loadingmetadataforbox'laravel/homestead'
default:URL:https://atlas.hashicorp.com/laravel/homestead
==>default:Addingbox'laravel/homestead'(v0.4.4)forprovide
r:virtualbox
default:Downloading:https://atlas.hashicorp.com/laravel/bo
xes/homestead/versions/0.4.4/providers/virtualbox.box
Anerroroccurredwhiledownloadingtheremotefile.Theerror
message,ifany,isreproducedbelow.Pleasefixthiserrorand
try
again.
SSLread:error:00000000:lib(0):func(0):reason(0),errno60
加入新的homesteadvagrandbox
vagrantboxadd--insecure-claravel/homesteadhttp://atlas.has
hicorp.com/laravel/boxes/homestead
Homestead
18
$vagrantboxadd--insecure-claravel/homesteadhttp://atlas.h
ashicorp.com/laravel/boxes/homestead
==>box:Loadingmetadataforbox'http://atlas.hashicorp.com/la
ravel/boxes/homestead'
Thisboxcanworkwithmultipleproviders!Theprovidersthatit
canworkwitharelistedbelow.Pleasereviewthelistandchoos
e
theprovideryouwillbeworkingwith.
1)virtualbox
2)vmware_desktop
Enteryourchoice:1
==>box:Addingbox'laravel/homestead'(v0.4.4)forprovider:v
irtualbox
box:Downloading:https://atlas.hashicorp.com/laravel/boxes/
homestead/versions/0.4.4/providers/virtualbox.box
==>box:Successfullyaddedbox'laravel/homestead'(v0.4.4)for
'virtualbox'!
加入完成後,再重新啟動homestead即可
$vagrantup
參考資料
SayHellotoLaravelHomestead2.0Laravel-HomesteadVirtualboxVagrantVagrant常用指令
SSLread:error·Issue#401·Varying-Vagrant-Vagrants/VVVVagrant:Fixforerror60/SSLread-Slick
Homestead
19
Homestead
20
資料庫
這裏會介紹一些Laravel使用的資料庫
資料庫
21
Migration這裏會介紹如何在Laravel5使用Migration管理資料庫
Migration指令
建立Migration
$phpartisanmake:migrationcreate_users_table--create="users"
Migration建立之後的檔案會放在
database/migrations/2015_04_11_134630_create_users_table.php
Migration檔案最前面的日期會依照你建立Migration的時間自動產生,所以每
個人看到的檔名皆會不同在後面加了 --create的參數可以告訴
Migration,我們要做建立 user資料表的動作,檔案內容會像這樣:
Migration(遷移)
22
<?php
//database/migrations/2015_04_11_134630_create_users_table.php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classCreateUsersTableextendsMigration{
/**
*Runthemigrations.
*
*@returnvoid
*/ㄒ
publicfunctionup()
{
Schema::create('users',function(Blueprint$table)
{
$table->increments('id');
$table->timestamp();
});
}
/**
*Reversethemigrations.
*
*@returnvoid
*/
publicfunctiondown()
{
Schema::drop('users');
}
}
異動資料表欄位資料
如果我們要在 users資料表中加欄位(或是其他改變資料表結構),我們可以用
這樣的指令去建立Migration
Migration(遷移)
23
$phpartisanmake:migrationadd_email_to_users_table--table="u
sers"
在後面加了 --table的參數可以告訴Migration,我們要做異動 user資料
表的動作,檔案內容會像這樣:
Migration(遷移)
24
<?php
//database/migrations/2015_04_12_154720_add_email_to_users_tabl
e.php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classAddEmailToUsersTableextendsMigration{
/**
*Runthemigrations.
*
*@returnvoid
*/
publicfunctionup()
{
Schema::table('users',function(Blueprint$table)
{
$table->string('email',180);
});
}
/**
*Reversethemigrations.
*
*@returnvoid
*/
publicfunctiondown()
{
Schema::table('users',function(Blueprint$table)
{
$table->dropColumn('email');
});
}
}
原先的建立資料表會用 Schema::create(),而異動資料表則會用
Schema::table()去做異動
Migration(遷移)
25
列出目前所有Migration狀態
$phpartisanmigrate:status
執行Migration
$phpartisanmigrate
恢復上一版本的Migration
$phpartisanmigrate:rollback
清除所有版本的Migration
$phpartisanmigrate:reset
清除所有版本的Migration並重新執行
$phpartisanmigrate:refresh
備註
欄位異動
若做欄位異動Migration後需要rollback,若丟出例外錯誤時,則使用composer安裝 doctrine/dbal後即可解決rollback的問題
$composerrequiredoctrine/dbal
Migration(遷移)
26
安全性
在剛開始開發產品的時候,有時候資料表有做小小的修改或異動,為了圖方便,我
們常常會使用 migrate:reset或 migrate:refresh去清空我們的資料,重建
資料表。
但如果產品已經上線了,這個指令就會是一個非常危險的指令,企業產品最重要的
資產就是 資料,這個指令會導致所有的資料都被清除,所以請上線後小心謹慎去
使用。
參考資料
遷移和資料填充-Laravel.twMigrations-Laracasts
Migration(遷移)
27
EloquentModel這裏會介紹如何在Laravel5使用EloquentModel管理資料庫
EloquentModel(模型)
28
Eloquent設定
設定可以大量新增的欄位
Eloquent為了避免特定欄位(像是id,created_at...)被使用者故意傳入大量
(Mass)資料去進行修改,所以Eloquent會自動保護欄位不被大量異動(MassAssignment),像是:
//新增
App\User::Create([
'first_name'=>'KeJyun',
'last_name'=>'Hong',
'email'=>'kejyun@gmail.com',
]);
//更新
$user=App\User::find('1');
$user->update([
'email'=>'hello@gmail.com',
]);
如果我們需要異動這些欄位,需要在Model裡面設定 $fillable的欄位,這樣
就可以使用大量資料的方式,去新增或異動資料表欄位資料。
classUserextendsModel{
protected$fillable=['first_name','last_name','email'];
}
設定需要被保護的欄位
設定
29
我們也可以使用 $guarded指定某些欄位需要被保護,能被大量新增或異動
classUserextendsModel{
protected$guarded=['id','password'];
}
我們也可以設定所有欄位都不能被大量新增或異動
classUserextendsModel{
protected$guarded=['*'];
}
設定欄位為時間資料欄位
我們可以很簡單的使用Carbon去做時間的資料處理,預設的 created_at與updated_at是使用Carbon當作儲存的資料格式
設定
30
$article=\App\Article::find(1);
//created_at='2014-03-1823:59:59'
//取得created_at年份
//2014
dd($article->created_at->year);
//取得created_at月份
//03
dd($article->created_at->month);
//取得created_at6天後的時間
//2014-03-2423:59:59
dd($article->created_at->addDays(6));
//取得created_at格式化為Y-m的時間
//2014-03
dd($article->created_at->format('Y-m'));
//取得created_at格式化為人可閱讀的時間
//1yearago
dd($article->created_at->diffForHumans());
若我們自己新增了時間的欄位像是 published_at,則Model沒有自動將此欄位
的資料設為Carbon的資料格式
我們可以在Model中設定 $dates欄位中的資料,可以指定欄位資料格式為
Carbon的資料格式
classArticleextendsModel{
protected$dates=['published_at'];
}
設定資料庫的連線
設定
31
我們也可以使用 $connection指定模型需要用哪個資料庫連線去做查詢
classUserextendsModel{
protected$connection='custom_connection_name';
}
設定主鍵不要自動新增
使用Eloquent去建立模型(Model)時,預設主鍵會使用自動新增(Auto-increment)的方式去新增,若要自行定義主鍵時,則要設定 $incrementing為false,將自動新增的功能關閉~
classUserextendsModel{
public$incrementing=false;
}
設定主鍵欄位名稱
使用Eloquent去建立模型(Model)時,預設會將主鍵欄位名稱設為 id,若有
需要異動主鍵欄位名稱的話,則要設定 $primaryKey變數,設為自行定義的欄
位名稱
classUserextendsModel{
protected$primaryKey='my_primary_column_name';
}
參考資料
Eloquent101-Laracast
設定
32
Dates,Mutators,andScopes-LaracastEloquentORM-Laravel.tw
設定
33
Eloquent關聯
假如我們有兩個模型,「文章(Article)」及「使用者(Users)」,假設一個情
境,1個使用者可以寫多篇的文章,但1篇文章只能被1個使用者發表
如果我們想要透過關聯關係,從使用者模型去取得使用者的文章,就像:
//取得使用者編號1的物件
$user=\App\Users::find(1);
//取得使用者的所有發表的文章
$user->articles();
我們會想要使用者模型內設定這樣的關聯關係,就像:
classUsersextendsModel{
//設定使用者擁有許多文章
publicfunctionarticles(){
return$this->hasMany('App\Article');
}
}
如果我們想透過關聯關係,從文章模型去取得是哪一個使用者發表文章,就像:
//取得文章編號1的物件
$article=\App\Article::find(1);
//取得發表文章的使用者資訊
$user=$article->user();
關聯
34
classArticleextendsModel{
//設定文章屬於某一的使用者
publicfunctionuser(){
return$this->belongsTo('App\User');
}
publicfunctionowner(){
return$this->belongsTo('App\User');
}
publicfunctionwriter(){
return$this->belongsTo('App\User');
}
}
設定關聯屬性的函式名稱可以自訂,看自己覺得什麼樣的名稱適合自己就可以了,
自訂完後一樣可以使用關聯的方式,撈取出發表文章使用者的資訊
//取得文章編號1的物件
$article=\App\Article::find(1);
//取得發表文章的使用者資訊
$owner_user=$article->owner();
$writer_user=$article->writer();
設定完之後,必須確定文章(Article)資料表有使用者編號(user_id)的外來鍵欄
位
關聯
35
<?php
//database/migrations/2015_04_13_154720_create_article_table.php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classCreateArticleTableextendsMigration{
publicfunctionup()
{
Schema::table('article',function(Blueprint$table)
{
//發表文章使用者編號
$table->integer('user_id')->unsigned();
//設定外來鍵
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
});
}
publicfunctiondown()
{
Schema::drop('users');
}
}
參考資料
EloquentRelationships-Laracasts
關聯
36
Eloquent魔術函式
預先處理被異動的欄位資料
在使用Eloquent新增或異動資料時,我們可能想要對輸入的資料做預先的處理,
我們可以使用Laravel提供的魔術函式 setNameAttribute()去預先處理欄位資
料。
如果我們要預先處理文章模型(Article)的發布時間欄位(published_at),我們
的魔術函式就會是像:
classArticleextendsModel{
publicfunctionsetPublishedAtAttribute($date){
//將傳入的Y-m-d時間設為datetime格式的現在時間
$this->attributes['published_at']=Carbon::createFromFo
rmat('Y-m-d',$date);
//將傳入的Y-m-d時間設為datetime格式的凌晨零時00:00:00
$this->attributes['published_at']=Carbon::parse($date)
;
}
}
魔術函式 setNameAttribute()中,若遇到欄位名稱有底線的狀況,則將名
稱設為駝峰式大小寫(Camel-Case),像是 published_at則變成
PublishedAt
自定義query處理函式
假如我們要讀取發表的文章,但是發表的時間 published_at必須過去的時間,
設定於未來發表時間的文章不能被撈取出來,我們可以用這樣的方式去撈取:
魔術函式
37
//取得已經發表的文章,依照發表時間降冪(Desc)排序
$article=\App\Article::latest('published_at')
->where('published_at','<=',Carbon::now())
->get();
我們可以簡化這個query,把它寫在Model用函式的方式做處理,這樣我們就可以
用這樣去取得以發表的文章:
//取得已經發表的文章,依照發表時間降冪(Desc)排序
$article=\App\Article::latest('published_at')
->published()
->get();
而Model裡面我們用 scopeName魔術函式的方式去設定 published():
classArticleextendsModel{
publicfunctionscopePublished($query){
$query->where('published_at','<=',Carbon::now());
}
}
若我們想要取得尚未被發表的文章資訊,我們模式函式也可以設定成:
classArticleextendsModel{
publicfunctionscopeUnpublished($query){
$query->where('published_at','>',Carbon::now());
}
}
這樣我們就可以用 unpublished()去設定取得文章資訊了
魔術函式
38
//取得已經發表的文章,依照發表時間降冪(Desc)排序
$article=\App\Article::latest('published_at')
->unpublished()
->get();
這樣的優點是:
1. 簡化程式的長度
2. 讓我們在不同的地方不需要寫同樣落落長的查詢
3. 讓查詢的可讀性增加, published()與 unpublish()我們不需要看查詢
的語法條件就可以知道這個地方是要做什麼樣的查詢了
參考資料
Eloquent101-LaracastDates,Mutators,andScopes-LaracastEloquentORM-Laravel.tw
魔術函式
39
使用Eloquent
新增資料
大量指定新增資料
//新增
\App\User::Create([
'first_name'=>'KeJyun',
'last_name'=>'Hong',
'email'=>'kejyun@gmail.com',
]);
填入要新增的資料
//使用者的資料
$user_data=[
'first_name'=>'KeJyun',
'last_name'=>'Hong',
'email'=>'kejyun@gmail.com',
];
$user=new\App\User;
//填入要新增的資料
$user->fill($user_info);
//儲存資料
$user->save();
使用Eloquent
40
資料庫常見問題
這裏會列出一些Laravel5在處理資料庫會遇到的一些常見的問題
問題列表
無法取得查詢Log使用大量資料的方式新增時無法新增
使用中繼模型繼承Eloquent模型造成無法使用大量資料新增
常見問題
41
資料庫常見問題:無法取得查詢Log在Laravel4為了要確定下的SQL語法有符合我們預期,我們常常在做完資料庫查
詢後,使用 DB::getQueryLog();去取得做資料庫查詢的QeuryLog,但因為
Laravel會把這些Log都記錄在記憶體中,如果做了大量的新增的查詢,記憶體會
使用過多可能會造成系統Crash。
所以Laravel5預設把記錄QueryLog的機制關閉,若需要做QueryDebug,需要
自行打開QeuryLog功能
<?php
//啟用QueryLog功能
DB::connection()->enableQueryLog();
這樣我們就可以使用 DB::getQueryLog();去取得做資料庫查詢的QeuryLog摟!!要得到執行過的查詢紀錄陣列,你可以使用getQueryLog方法:
<?php
//取得資料庫查詢的QeuryLog
$queries=DB::getQueryLog();
var_dump($queries);
參考資料
資料庫使用基礎查詢日誌記錄-Laravel.twHowtogetthequeryexecutedinLaravel5?DB::getQueryLogreturningemptyarray
無法取得查詢Log
42
使用大量資料的方式新增(MassAssignment)時無法新增
在Laravel若沒有在模型(Model)中 同時設定「可以新增的欄位變數
$fillable」及「需要保護的欄位變數 $guarded」時,為了安全性著想,在
做大量的新增或異動資料時(MassAssignment),會無法正確的去新增或異動資
料。
設定「可以新增的欄位變數 $fillable」
設定你覺得允許做大量新增的欄位名稱
classUserextendsModel{
protected$fillable=['first_name','last_name','email'];
}
設定「需要保護的欄位變數 $guarded」
我們可以指定某些欄位,不能被使用大量新增或異動,去變更欄位的資料值
classUserextendsModel{
protected$guarded=['id','password'];
}
若我們想要讓模型(Model)可以被大量新增,且我們沒有需要保護的欄位時,我
們還是需要設定 $guarded變數為空陣列 [],否則Laravel預會保護所有的欄
位資料,讓你無法進行大量的新增或異動資料
使用大量資料的方式新增時無法新增
43
classUserextendsModel{
protected$fillable=['id','password','first_name','last
_name','email'];
protected$guarded=[];
}
參考資料
EloquentORM新增、更新、刪除-Laravel.twLaravelEloquentSavetoDBUsingCreate-UnhelpfulErrorEloquentCreateMethod-Alwaysinsertsblankentries.UnabletocreateamodelwithEloquentcreatemethod.ErrortellingMassAssignMentException
使用大量資料的方式新增時無法新增
44
使用中繼模型繼承(extends)Eloquent模型造成無法使用大量資料新增(MessAssignment)大部份的情況可能專案較小,所以我們會直接使用模型(Model)去新增資料,但
若專案較大時,且不同的模型之間有共用的方法的話,我會會希望這些模型繼承同
一個Eloquent模型的中繼類別物件,就像這樣:
Eloquent模型的中繼類別物件
classCustomBaseModelextendsModel
{
public$someVariable=null;
publicfunctiondoSomething()
{
}
}
使用者模型繼承「Eloquent模型的中繼類別物件」
classUserextendsCustomBaseModel{
protected$fillable=['first_name','last_name','email'];
}
使用這樣的中繼類別時,如果我們只有 設定變數或 實作中繼模型類別方法時,我
們可以運作的很正常,但是如果我們需要實作中繼類別的 建構子
__construct()時,我們必須要時做原本EloquentModel類別的建構子,否鑿會
無法正常的運作原有的Eloquent模型
在
vendor/laravel/framework/src/Illuminate/Database/Eloquent/Model.php
Eloquent模型的檔案中,我們可以看到 建構子__construct()有需要傳入資料
使用中繼模型繼承Eloquent模型造成無法使用大量資料新增
45
表欄位的屬性值 $attributes。
//vendor/laravel/framework/src/Illuminate/Database/Eloquent/Mod
el.php
abstractclassModelimplementsArrayAccess,Arrayable,Jsonable
,JsonSerializable,QueueableEntity,UrlRoutable{
publicfunction__construct(array$attributes=array())
{
$this->bootIfNotBooted();
$this->syncOriginal();
$this->fill($attributes);
}
}
這個部分是用來做大量資料新增或異動時(MassAssignment)需要用到的資料,
所以如果我們在中繼類別沒有實作這個 建構子__construct(),會讓我們的完整
EloquentModel出現問題
所以在Eloquent中繼類別中我們必須要時作的 建構子__construct()會長的像這
樣:
classCustomBaseModelextendsModel
{
public$someVariable=null;
function__construct(array$attributes=array())
{
parent::__construct($attributes);
//做中繼類別建構子想要做的事
$this->someVariable='5566';
}
}
使用中繼模型繼承Eloquent模型造成無法使用大量資料新增
46
我們的中繼類別,需要傳入資料表欄位的屬性值 $attributes,並執行母類別
EloquentModel的建構子,這樣我們的Eloquent模型就能夠正常運作了!
使用中繼模型繼承Eloquent模型造成無法使用大量資料新增
47
PostgreSQL這裏會介紹如何在Laravel使用PostgreSQL資料庫
PostgreSQL
48
安裝PostgreSQLODBCdriver環境:OSX日期:2015-03-29PHP:5.6
在我們在Laravel使用PostgreSQL去做Migration的時候,我們會看到像下面這
樣的錯誤訊息:
$phpartisanmigrate
[PDOException]
couldnotfinddriver
$
這表示我們沒有相關的連線驅動程式去連線到PostgreSQL,所以我們需要安裝我
們所需要的驅動程式
在OSX的PHP相關環境我是用brew去安裝的,如果你也是用brew去安裝,可
以先看看自己的套件是用哪一個版本的PHP
$brewlist
autoconfgitlibpngmhashphp56readli
nezlib
freetypeicu4clibtoolnvmphp56-mcryptunixod
bc
gettextjpegmcryptopensslpostgresqlwget
然後搜尋現在brew支援的PostgreSQL驅動程式
$brewsearchpgsql
osm2pgsqlphp54-pdo-pgsqlphp55-pdo-pgsqlphp56-pdo-pgsql
我們找到我們php5.6版本的驅動程式了,可以用下面的指令去安裝
$brewinstallphp56-pdo-pgsql
安裝PostgreSQLODBCdriver
49
安裝完成後就可以正常的使用Migration或相關的DB指令去存取PostgreSQL了~~!!
參考資料
Laravel:Error[PDOException]:CouldnotFindDriverinPostgreSQL
安裝PostgreSQLODBCdriver
50
HTTP這裏會介紹如何在Laravel5處理HTTP請求
HTTP
51
HTTP請求
這裏會介紹如何在Laravel5驗證HTTP請求的資料
建立新的請求驗證
如果我們有文章(Article)的模型,我們在每次請求過程中想要驗證傳入的資料,
我們可以使用系列指令建立要驗證的請求:
$phpartisanmake:requestCreateArticleRequest
請求驗證的檔案會被建立在 app\Http\Requests目錄下,建立的檔案內容如下
<?phpnamespaceApp\Http\Requests;
//app\Http\Requests\CreateArticleRequest.php
useApp\Http\Requests\Request;
classCreateArticleRequestextendsRequest{
/**
*Determineiftheuserisauthorizedtomakethisrequest.
*驗證使用者是否要登入狀態
*
*@returnbool
*/
publicfunctionauthorize()
{
returntrue;
}
/**
*Getthevalidationrulesthatapplytotherequest.
*驗證請求的資料規則
*
*@returnarray
請求
52
*/
publicfunctionrules()
{
return[
//使用|設定驗證規則
'title'=>'required|min:3',
'body'=>'required|min:30',
//使用陣列設定驗證規則
'published_at'=>[
'required',
'date',
],
];
}
}
在驗證請求的CreateArticleRequest中的rules()函式,除了僅回傳驗證規則外,
你也可以判斷不同的狀況去加入不同的規則再回傳,像是:
請求
53
<?phpnamespaceApp\Http\Requests;
//app\Http\Requests\CreateArticleRequest.php
useApp\Http\Requests\Request;
classCreateArticleRequestextendsRequest{
publicfunctionrules()
{
$rules=[
//使用|設定驗證規則
'title'=>'required|min:3',
'body'=>'required|min:30',
//使用陣列設定驗證規則
'published_at'=>[
'required',
'date',
],
];
//其他條件判斷
if($condition){
$rules['something_else']='required';
}
return$rules;
}
}
指定Controller函式處理指定的請求驗證
在我們使用Controller去處理請求時,我們可以再傳入變數內設定要怎麼處理請
求:
請求
54
classArticleControllerextendsController{
//新增文章
publicfunctionstore(App\Http\Requests\CreateArticleRequest
$request)
{
Article::create(Request:all());
//OR
//Article::create($request->all());
returnredirect('articles');
}
}
這樣設定之後,所有的HTTP請求的Input資料都會經過
App\Http\Requests\CreateArticleRequest驗證,如果有經過驗證才會繼續
執行後面的新增文章動作,否則的話則會丟出驗證錯誤的物件到原頁面。
驗證錯誤訊息
這裏要注意到,視圖(View)的每一頁Laravel都會將驗證錯誤物件
(Illuminate\Support\ViewErrorBag)包成 $errors變數,所以你可以在每一頁
去印出 $errors值, $errors變數儲存的是任何資料驗證錯誤的結果
判斷是否有任何的錯誤並顯示錯誤訊息
//任一blade視圖(View)皆可以接收此錯誤變數
@if($errors->any())
//有錯誤訊息
<ul>
$foreach($errors->all()as$error)
<li>{{$error}}</li>
@endforeach
</ul>
@endif
請求
55
使用Controller內建的validate驗證請求的資料
除了建立驗證Request物件,也可以直接使用Controller內建的validate去驗證請
求
如果不想要使用內建處理HttpResponseException的例外,你也可以自己trycatch並自己處理例外狀況
classArticleControllerextendsController{
//新增文章
publicfunctionstore(Requests$request)
{
try{
$this->validate($request,[
'title'=>'required|min:3',
'body'=>'required|min:30',
'published_at'=>'required|date',
]);
}catch(Exception$e){
//自己處理例外狀況
}
Article::create(Request:all());
//OR
//Article::create($request->all());
returnredirect('articles');
}
}
參考資料
FormRequestsandControllerValidation-Laracasts
請求
56
請求
57
路由(Route)這裡會介紹一些路由的相關技巧
路由(Route)
58
子網域路由(Sub-DomainRoute)我們可能會因為有多個子網域,而我們希望各個不同的子網域有自己的路由設定,
像是我們希望各個子網域的首頁能夠藍道不同的頁面,這個時候我們可以透過子網
域路由去幫我們達成這樣的工作
加入您的子網域到hosts設定
如果是正式環境則不用做此設定,如果是測試環境也想要達到子網域路由的效果,
則必須做此設定
開啟 /etc/hosts檔案,並加入您需要的子網域
127.0.0.1resume.kejyun.dev
127.0.0.1book.kejyun.dev
Homestead加入此子網域的虛擬主機設定
sites:
-map:resume.kejyun.dev
to:/home/vagrant/Code/KeJyunProject/public
-map:book.kejyun.dev
to:/home/vagrant/Code/KeJyunProject/public
重新讀取Homestead設定
若設定檔有修改要重新讀取,則可以使用下列指令重新讀取設定
vagrantprovision
加入子網域路由
子網域路由
59
在 route.php檔案中加入子網域路由
Route::group(['domain'=>'resume.kejyun.dev'],function()
{
Route::get('/',function(){
return'KeJyunResume';
});
});
Route::group(['domain'=>'book.kejyun.dev'],function()
{
Route::get('/',function(){
return'KeJyunBook';
});
});
這樣我們就可以在http://resume.kejyun.dev及http://book.kejyun.dev這兩個子網
域看到不同的首頁了!
參考資料
HomesteadandSubdomains
子網域路由
60
中介層(Middleware)這裏會介紹如何在Laravel5使用中介層處理資料,Middleware在Laravel4叫做
Filter,他可以在處理資料之前,先過濾條件判斷,符合條件的再繼續處理之後的
Http請求。
就像實作一個部落格,使用者發表文章的時候,一定要登入,否則就會被導到登入
頁(或首頁),判斷登入條件的部分在Laravel5可以用中介層去實現。
檢視中介層類別
我們可以看看內建的驗證使用者是否有登入的Authenticate中介層
<?phpnamespaceApp\Http\Middleware;
//app\Http\Middleware\Authenticate.php
useClosure;
useIlluminate\Contracts\Auth\Guard;
classAuthenticate{
protected$auth;
/**
*Createanewfilterinstance.
*建立過濾器實例,建構時注入Guard類別並存到auth變數
*
*@paramGuard$auth
*@returnvoid
*/
publicfunction__construct(Guard$auth)
{
$this->auth=$auth;
}
/**
中介層(Middleware)
61
*Handleanincomingrequest.
*處理request
*
*@param\Illuminate\Http\Request$request
*@param\Closure$next
*@returnmixed
*/
publicfunctionhandle($request,Closure$next)
{
if($this->auth->guest())
{
if($request->ajax())
{
returnresponse('Unauthorized.',401);
}
else
{
returnredirect()->guest('auth/login');
}
}
return$next($request);
}
}
有設定HttpRequest中介層時,所有的請求都會丟給中介層的 handle()函式做
處理,第一個變數傳入的是Request本身,第二個變數是若檢查驗證成功之後要執
行的函數,並把Request丟給下一層處理 $next($request)。
註冊中介層變數
中介層設定好之後,必須要到 app\Http\Kernel.php去註冊你的中介層
<?phpnamespaceApp\Http;
//app\Http\Kernel.php
中介層(Middleware)
62
useIlluminate\Foundation\Http\KernelasHttpKernel;
classKernelextendsHttpKernel{
/**
*Theapplication'sglobalHTTPmiddlewarestack.
*全域中介層堆疊
*
*@vararray
*/
protected$middleware=[
'Illuminate\Foundation\Http\Middleware\CheckForMaintenan
ceMode',//檢查應用程式是不是維護中
'Illuminate\Cookie\Middleware\EncryptCookies',
//加密Cookies
'Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse'
,//加入QueuedCookies到Response
'Illuminate\Session\Middleware\StartSession',
//開啟Session
'Illuminate\View\Middleware\ShareErrorsFromSession',
//從Session中共享錯誤資訊
'App\Http\Middleware\VerifyCsrfToken',
//驗證CSRFToken
];
/**
*Theapplication'sroutemiddleware.
*路由中介層
*
*@vararray
*/
protected$routeMiddleware=[
'auth'=>'App\Http\Middleware\Authenticate',
'auth.basic'=>'Illuminate\Auth\Middleware\Authenticate
WithBasicAuth',
'guest'=>'App\Http\Middleware\RedirectIfAuthenticated'
,
];
}
中介層(Middleware)
63
在 app\Http\Kernel.php類別中, $middleware變數是設定全域中介層堆疊
清單,每一個HttpRequest都會依序經過 $middleware所有的中介層做判斷
$middleware中介層判斷完後都沒問題,才丟給路由中介層
$routeMiddleware做處理(若路由有設定要使用哪個中介層的話,沒有設定則
略過)。
當我們有自己的中介層,我們可以依自己需求看要將中介層設定加到哪一個變數設
定中,如果需要每一個Request都做檢查的話,則將中介層設定到
$middleware,否則設定在。 $routeMiddleware並指定中介層名稱即可。
在Controller使用中介層
我們可以強制設定,當使用者要存取文章的資源時都必須要登入,所以在
ArticleController控制器的建構子,我們可以用 $this->middleware('auth');設定全部ArticleController中的函式皆使用 auth中介層。
auth中介層名稱是參照 app\Http\Kernel.php中的
$routeMiddleware變數設定
classArticleControllerextendsController{
publicfunction__construct(){
$this->middleware('auth');
}
//新增文章
publicfunctionstore(Requests$request)
{
}
}
我們也可以使用 only方式,指定中介層只有在指定的函式中才使用,或是使用
except方式指定除了某些函式不使用中介層外,其他都要使用中介層當作過濾
中介層(Middleware)
64
//只有設定的函式使用中介層
$this->middleware('auth',['only'=>'create']);
//只有設定的函式"不要"使用中介層
$this->middleware('auth',['except'=>'index']);
在Route使用中介層
//app\Http\routes.php
Route::get('about',[
'middleware'=>'auth',
'uses'=>'HomeController@about'
]);
建立自己的中介層
我們可以使用指令建立自己的Middleware,假如我建立一個 KeJyunMiddleware為名稱的中介層,可以在命令列輸入:
$phpartisanmake:middlewareKeJyunMiddleware
建立的中介層會放在 app\Http\Middleware\KeJyunMiddleware.php中
中介層(Middleware)
65
<?phpnamespaceApp\Http\Middleware;
//app\Http\Middleware\KeJyunMiddleware.php
useClosure;
classKeJyunMiddleware{
publicfunctionhandle($request,Closure$next)
{
return$next($request);
}
}
建立好自訂的中介層之後,到 app\Http\Kernel.php註冊中介層後即可使用
<?phpnamespaceApp\Http;
//app\Http\Kernel.php
classKernelextendsHttpKernel{
protected$routeMiddleware=[
'kejyun'=>'App\Http\Middleware\KeJyunMiddleware',
];
}
參考資料
OgresAreLikeMiddleware-Laracasts
中介層(Middleware)
66
ETagMiddleware在我們的網站若資料未變更,我們會希望告訴請求資源的使用者,本資源未修改
(304NotModified),所以不用重複讀取資料,這樣可以節省我們傳輸資料頻寬
我可以用Middleware來達到ETag的效果
建立ETagMiddlewareApp\Http\Middleware\ETagMiddleware.php
ETagMiddleware
67
<?php
namespaceApp\Http\Middleware;
useClosure;
classETagMiddleware{
/**
*ImplementEtagsupport
*
*@param\Illuminate\Http\Request$request
*@param\Closure$next
*@returnmixed
*/
publicfunctionhandle($request,Closure$next)
{
//Getresponse
$response=$next($request);
//如果是getrequest
if($request->isMethod('get')){
//產生回應內容的etag
$etag=md5($response->getContent());
$requestEtag=str_replace('"','',$request->getETa
gs());
//檢查etag是否變更
if($requestEtagAND($requestEtag[0]==$etagOR$re
questEtag[0]=='W/'.$etag)){
//若etag相同,設定表頭為資料未修改
$response->setNotModified();
}
//設定etag
$response->setEtag($etag);
}
//傳送回應
return$response;
}
}
ETagMiddleware
68
設定EtagMiddlewareapp/Http/Kernel.php
<?php
namespaceApp\Http;
useIlluminate\Foundation\Http\KernelasHttpKernel;
classKernelextendsHttpKernel
{
protected$middlewareGroups=[
'web'=>[
\App\Http\Middleware\ETagMiddleware::class,
],
];
這樣我們就完成EtagMiddleware的設定了!
參考資料
SettingEtagsinLaravel5-MatthewDaly'sBlog
ETagMiddleware
69
視圖
這裏會介紹如何在Laravel5處理視圖(View)
Laravel的視圖是放在 resource/views目錄內
建立共用的視圖
我們網頁常常會出現header跟footer在不同的視圖中為相同的狀況,唯一有變的
只有中間的內容隨著不同的請求而有變動,如果有這樣的設計需求,我們可以替所
有視圖建立共用的視圖,假設我們把這個共用的視圖放在
resource/view/app.blade.php下,其內容可能是:
<!--resource/view/app.blade.php-->
<!doctypehtml>
<htmllang="zh-TW">
<head>
<metacharset="UTF-8">
<title>我的網站</title>
</head>
<body>
<divclass="container">
@yield('content')
</div>
@yield('other_info')
</body>
</html>
如果我們要顯示文章的資訊在 content中,文章的說明在 other_info中,我
們可以在blade中這樣設定:
視圖(View)
70
<!--resource/view/article.blade.php-->
@extend('app')
@section('content')
<h1>文章標題</h1>
<p>
Loremipsumdolorsitamet,consecteturadipisicingelit
,seddoeiusmod
temporincididuntutlaboreetdoloremagnaaliqua.Ute
nimadminimveniam,
quisnostrudexercitationullamcolaborisnisiutaliqui
pexeacommodo
consequat.
</p>
@stop
@section('other_info')
其他資訊
@stop
這樣Laravel就會幫我們把相對應的資訊塞到 app.blade.php當中相對應的位置
引入共用的視圖
假如我們要在特定的某幾頁使用Facebook留言板,像是:
視圖(View)
71
<divid="fb-root"></div>
<script>(function(d,s,id){
varjs,fjs=d.getElementsByTagName(s)[0];
if(d.getElementById(id))return;
js=d.createElement(s);js.id=id;
js.src="//connect.facebook.net/zh_TW/sdk.js#xfbml=1&version=
v2.3&appId=12345566";
fjs.parentNode.insertBefore(js,fjs);
}(document,'script','facebook-jssdk'));</script>
<divclass="fb-comments"data-href="http://laravel5-book.kejyun.
com/"data-numposts="5"data-colorscheme="light"></div>
但我們不想要這些相同的程式碼片段散落在各個不同的視圖中,我們可以把它整理
在 resource/views/vendor/_fbcomment.blade.php當中
然後在文章視圖當中我們就可以這樣去引入Facebook留言板:
<!--resource/view/article.blade.php-->
@extend('app')
@section('content')
<h1>文章標題</h1>
<p>
Loremipsumdolorsitamet
</p>
@include('vendor/_fbcomment')
@include('vendor._fbcomment')
@stop
@section('other_info')
其他資訊
@stop
傳入變數到引用的視圖當中
視圖(View)
72
在我們新增與編輯文章的視圖當中,幾乎所有的視圖都是一樣的,不一樣的地方可
能只有「表單處理的action不同(create&edit)」、「送出的按鈕文字不同(新
增違章&編輯文章)」
但我們還是希望兩個視圖能夠一起被引用,把其他不同的地方當作變數傳入,就可
以達到視圖重構的效果,避免類似的視圖重複出現在不同地方,像是:
<!--resource/view/partials/articles/_form.blade.php-->
{!!Form::label('title','標題')!!}
{!!Form::text('title',null)!!}
{!!Form::label('content','內文')!!}
{!!Form::text('content',null)!!}
{!!Form::submit($submitButtonText)!!}
當我們要引用表單的視圖,則必須把按鈕的文字傳送給表單,像是:
<!--resource/view/article.blade.php-->
@extend('app')
@section('content')
@include('partials/articles/_form',['submitButtonText'=>'新
增文章'])
@include('partials/articles/_form',['submitButtonText'=>'編
輯文章'])
@stop
@section('other_info')
其他資訊
@stop
備註
引用或載入視圖路徑
視圖(View)
73
在使用blade中的 @extend()或 @include()函數,他所參照的視圖相對位置
是從 resource/views/開始的
所以如果你的視圖是放在 resource/views/partials/other.blade.php中,
你要引用或載入的話則可以用 .或 /去指定相對的視圖位置,像是:
<!--引用-->
@extend('partials._other')
@extend('partials/_other')
<!--載入-->
@include('partials._other')
@include('partials/_other')
設計模式
樣板檔案名稱
通常若不是完整的視圖,僅是部分的視圖,通常會將檔案名稱最前面加上底線
_,用來告知團隊程式設計師這個blade視圖不是完整的視圖
參考資訊
ViewPartialsandFormReuse-Laracasts
視圖(View)
74
服務(Services)這裏介紹一些Laravel5提供的一些服務
服務
75
認證登入(Auth)
設定
Laravel內建認證的設定檔案放在 config/auth.php中,預設會使用
App\User的類別當作驗證的Eloquent模型
[
'model'=>App\User::class,
]
如果我們用Model模型設計模式去設計我們的程式架構,我們User實體模型的程
式可能會放在 App\KeJyunApp\User\Entities\User.php中,這時候我們的認
證模型設定可以設定成像這樣(依照命名空間去設定):
[
'model'=>App\KeJyunApp\User\Entities\User::class,
]
這樣Laravel內建的認證就可以用我們指定的實體模型去進行認證了!!
手動登入認證
Laravel內建的認證使用Auth去進行身份認證,如果我們要用使用者的「email」及「密碼」做登入,我們的登入程式可能會像:
$email='kejyun@gmail.com';
$password='1234';
if(Auth::attempt(['email'=>$email,'password'=>$password]))
{
//已登入成功!!!
}
認證登入(Auth)
76
使用 Auth:attempt()的方式去驗證使用者時,Laravel會先到User資料表透過
Email抓取使用者的資料,產生出來的SQL會像:
SELECT*FROM"users"WHERE"email"='kejyun@gmail.com'LIMIT1
;
抓取完使用者之料後再將password欄位用雜湊的 Hash::check()方式去比對驗
證密碼是否正確,再記錄使用者的SESSION資料
如果我們的使用者有限制啟用帳號的人才可以登入,所以我們要用使用者的
「email」、「密碼」及「啟用狀態」做登入,我們的登入程式可能會像:
$email='kejyun@gmail.com';
$password='1234';
$status='active';
if(Auth::attempt(['email'=>$email,'password'=>$password,'
status'=>$status]))
{
//已登入成功!!!
}
產生出來的SQL會像:
SELECT*FROM"users"WHERE"email"='kejyun@gmail.com'AND"st
atus"='active'LIMIT1;
Laravel一樣是先抓取使用者資料後,再做密碼驗證的動作!
使用記住我的方式登入
認證登入(Auth)
77
$email='kejyun@gmail.com';
$password='1234';
$remember_me=true;
if(Auth::attempt(['email'=>$email,'password'=>$password],
$remember_me))
{
//已使用記住我登入成功!!!
}
如果你是自己建立自己User資料表的Migration,記得在自己的Migration中加入 $table->rememberToken();的設定,加入 remember_token欄位,
這個欄位可以讓Laravel使用 記住我的方式去記住使用者的sessiontoken
判斷使用者是否已登入
我們可以使用 Auth::check()的方式,判斷使用者是否已登入
if(Auth::check()){
//已登入
}
取得登入使用者的物件
$user=Auth::user();
//KeJyun
var_dump($user->name);
使用特定使用者的ID登入
$user_id='1';
Auth::loginUsingId($user_id);
認證登入(Auth)
78
郵件
這裏會介紹Laravel5使用郵件寄信的相關說明
郵件(Mail)
80
使用Gmail寄信
在測試機測試的時候,為了節省郵件服務的開銷,我們可以使用Gmail當作我們測
試的郵件服務,所以我們來介紹如何使用Gmail寄信
設定 config/mail.php
driver設為 smtp
host設為 smtp.gmail.com
port設為 587
username設為你要用來寄信的Gmail帳號 kejyun@gmail.com
password設為Gmail帳號的密碼
pretend設為 true,這樣才可以正常使用Gmail寄送
設定完後會像這樣:
//config/mail.php
return[
'driver'=>'smtp',
'host'=>'smtp.gmail.com',
'port'=>587,
'from'=>['address'=>'kejyun@gmail.com','name'=>'K
eJyun'],
'encryption'=>'tls',
'username'=>'kejyun@gmail.com',
'password'=>'abcdefghijklmnopqrstuvwxyz123456',
'sendmail'=>'/usr/sbin/sendmail-bs',
'pretend'=>false,
];
測試使用Gmail寄信
使用Gmail寄信
81
Mail::raw('測試使用Laravel5的Gmail寄信服務',function($message)
{
$message->to('kejyun@gmail.com');
});
這樣我們就可以使用Gmail去當作我們的郵件寄送服務了!!!
參考資料
AttemptingtogetEmailtoworkinLaravel5郵件-Laravel.tw
使用Gmail寄信
82
使用Mailgun寄信
Mailgun對於初期的產品是一個不錯的郵件服務,每個月可以免費寄送10000封信,對於初期的應用應該是綽綽有餘,而且Laravel5預設有支援Mailgun的服
務,所以我們來介紹如何使用Mailgun寄信
設定 config/mail.php
driver設為 mailgun
host設為 smtp.mailgun.org
port設為 587
username設為 postmaster@mailgun.kejyun.com,這個帳號可以登入後
到Domains頁選擇你設定的Domains,找到 DefaultSMTPLogin就可以
看到這個帳號
password設為你自己的密碼,Mailgun顯次的欄位為 DefaultPassword,密碼長度為32碼
pretend設為 true,這樣才可以正常使用Mailgun寄送
設定完後會像這樣:
使用Mailgun寄信
83
//config/mail.php
return[
'driver'=>'mailgun',
'host'=>'smtp.mailgun.org',
'port'=>587,
'from'=>['address'=>'kejyun@gmail.com','name'=>'K
eJyun'],
'encryption'=>'tls',
'username'=>'postmaster@mailgun.kejyun.com',
'password'=>'abcdefghijklmnopqrstuvwxyz123456',
'sendmail'=>'/usr/sbin/sendmail-bs',
'pretend'=>false,
];
設定 config/services.php
domain設定為你自己定義的domain,若沒有自己定義domain的話,可以使
用Mailgun替你產生的domain,可以看看 DefaultSMTPLogin後面的
sandboxXXXXXX.mailgun.org,這個為Mailgun產生的domain
secret設為Mailgun提供的 APIKey,會長的像 key-abcdefghijklmnopqrstuvwxyz123456
設定完後會像這樣:
//config/services.php
[
'mailgun'=>[
'domain'=>'mailgun.kejyun.com',
'secret'=>'key-abcdefghijklmnopqrstuvwxyz123456',
],
]
測試使用Mailgun寄信
使用Mailgun寄信
84
Mail::raw('測試使用Laravel5的Mailgun寄信服務',function($messa
ge)
{
$message->to('kejyun@gmail.com');
});
這樣我們就可以使用Mailgun去當作我們的郵件寄送服務了!!!
參考資料
MailgunSettingupMailgunwithLaravel5郵件-Laravel.tw
使用Mailgun寄信
85
隊列(Queue)這裏會介紹一些Laravel5使用隊列(Queue)的方法
Laravel隊列的設定檔在 config/queue.php,在這裡你可以設定你想要用什麼
樣的隊列(Queue)服務去執行你的隊列,而Laravel預設有支援
database、Beanstalkd、IronMQ、AmazonSQS、Redis這幾種隊列的服務。
我們通常會將一些需要花比較久時間處理的工作丟給隊列去背景執行,讓使用者能
夠快速的的到網站的回應,像是我們在寄送帳號認證信件時,因為透過郵件伺服器
去寄送可能會花費比較久的時間,所以我們會將這類的工作丟到隊列去執行,所以
使用者的認證信件就會延遲的發送到他們的信箱,但是使用者在瀏覽網站時卻可以
有更好的體驗!
指令
在使用Queue去幫我們做工作的時候,我們在系統背景需要執行傾聽Queue是否
有工作的指令,像是 phpartisanqueue:listen,這樣Queue中有新工作需
要做,才能夠正常的去執行。
$phpartisanqueue:listen
執行Queue指令有一些相關的參數,可以依照自己的環境去調校
$phpartisanqueue:listen[--queue[="..."]][--delay[="..."]][-
-memory[="..."]][--timeout[="..."]][--sleep[="..."]][--tries[
="...”]]
隊列(Queue)
86
參數 說明 指令
queue 設定優先執行的工作順序phpartisan
queue:listen--
queue=high,low
delay 在執行的工作發生錯誤時,要延遲多久重新執行(單位:秒),預設0秒
phpartisan
queue:listen--
delay=10
memory 執行工作最多能夠使用的記憶體上限(單位:MB),預設128MB
phpartisan
queue:listen--
memory=1024
timeout 執行的工作做長執行的時間是多長(單位:秒),預設60秒
phpartisan
queue:listen--
timeout=3600
sleep在沒有找到可以做的工作時,需要間隔多少秒再去檢查有無新的工作(單位:秒),預設3秒
phpartisan
queue:listen--
sleep=10
tries 工作執行失敗時,最多重新嘗試執行幾次(單位:次數),預設0,不重新嘗試
phpartisan
queue:listen--
tries=3
隊列(Queue)
87
資料庫隊列(DatabaseQueue)我們可以使用 database的隊列設定,在自己的資料庫建立隊列資料表
產生隊列資料表
我們可以使用 phpartisanqueue:table指令去產生隊列的Migration
$phpartisanqueue:table
所以執行命令後,你可以找到像是
database/migrations/2015_05_26_225627_create_queue_jobs_table.php
這樣的隊列Migration檔案
Migration檔名日期 2015_05_26_225627每個人皆不同,會依照你建立當時
的時間去產生
產生的隊列Migration會長的像這樣:
database
88
<?php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classCreateQueueJobsTableextendsMigration{
/**
*Runthemigrations.
*
*@returnvoid
*/
publicfunctionup()
{
Schema::create('jobs',function(Blueprint$table)
{
$table->bigIncrements('id');
$table->string('queue');
$table->text('payload');
$table->tinyInteger('attempts')->unsigned();
$table->tinyInteger('reserved')->unsigned();
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}
/**
*Reversethemigrations.
*
*@returnvoid
*/
publicfunctiondown()
{
Schema::drop('jobs');
}
}
database
89
建立隊列資料表
執行 phpartisanmigrate將隊列資料表新增至資料庫
設定隊列驅動
在 config/queue.php檔案中設定資料庫隊列驅動設定,設定如下:
//config/queue.php
return[
'default'=>'database',
'connections'=>[
'database'=>[
'driver'=>'database',
'table'=>'jobs',
'queue'=>'default',
'expire'=>60,
'connection_name'=>'',
],
],
];
建立隊列工作
我們可以使用 \Queue::push('App\Commands\SendEmail@fire',$queue_data);的方法去新增要執行的隊列
第一個參數是執行隊列需要呼叫的類別名稱位置( App\Commands\SendEmail)
及方法( fire)
類別名稱需要正確的指定類別的命名空間(namespace),可以指定這個隊列要執
行的類別方法,只要將方法使用 @加在後方即可( @customMethod)
若沒有指定用哪個方法,Laravel預設會執行 fire的類別方法( @fire)
我們使用隊列來寄送Email,設定隊列的方式大概像這樣:
database
90
//需要傳送給隊列處理的資料
$queue_data=[
'email'=>'kejyun@gmail.com',
'name'=>'KeJyun',
];
//建立隊列
$queue_id=\Queue::push('App\Commands\SendEmail@fire',$queue_d
ata);
在 App\Commands\Sendmail.php檔案大概會像這樣:
<?phpnamespaceApp\Commands;
classSendEmail{
/**
*執行隊列
*
*@returnvoid
*/
publicfunctionfire($job,$data)
{
//寄送Email
\Mail::send('emails.welcome',[],function($message)use
($data)
{
$message->to($data['email'],$data['name'])->subject(
'歡迎使用Laravel5資料庫隊列寄送Email!!!');
});
//執行成功,刪除隊列
$job->delete();
}
}
database
91
fire方法中的 $job變數會接受該隊列的實例, $data變數會接收建立隊列
時傳入的資料
像我要使用隊列寄送Email,則會將使用者的相關資訊傳送到這個隊列來,讓隊列
能正確的發送正確的Email資訊給使用者!
在隊列執行完成無誤後,我們必須要使用 $job->delete();將隊列資料刪除,
若沒有刪除Laravel下次再出理到該資料時,則會視為隊列處理失敗,進而嘗試重
新處理
監聽隊列工作
我們會在shell中執行 phpartisanqueue:listen去持續的監聽隊列資料的狀
況,若有新增隊列到資料表時,Laravel則會開始處理隊列的資料
$phpartisanqueue:listen
這樣我們就可以正常的使用隊列去幫我們寄信摟!!
參考資料
隊列-Laravel.twQueuesinLaravelwithRedis
database
92
非同步資料庫隊列(AsyncDatabaseQueue)在我們使用Laravel提供的資料庫隊列(DatabaseQueue)時,我們需要在命令列
執行 phpartisanqueue:listen指令,持續的去監聽是否有需要執行的
Queue。
barryvdh/laravel-async-queue隊列套件,可以讓我們不用持續的監聽隊列資料,
並在使用隊列時,立即的使用shell在背景執行隊列的工作。
目前(2015-06-01)套件0.4.x版本有支援Laravel5
安裝
$composerrequire'barryvdh/laravel-async-queue:0.4.*@dev'
加入ServiceProvider在 config/app.php檔案中加入 'Barryvdh\Queue\AsyncServiceProvider'
//config/app.php
return[
'providers'=>[
'Barryvdh\Queue\AsyncServiceProvider',
]
];
產生隊列資料表
barryvdh/laravel-async-queue隊列套件使用原生的資料庫隊列資料表(DatabaseQueue)去時做的,所以我們可以使用 phpartisanqueue:table指令去產生
隊列的Migration
非同步(async)
93
$phpartisanqueue:table
所以執行命令後,你可以找到像是
database/migrations/2015_05_26_225627_create_queue_jobs_table.php
這樣的隊列Migration檔案
Migration檔名日期 2015_05_26_225627每個人皆不同,會依照你建立當時
的時間去產生
產生的隊列Migration會長的像這樣:
非同步(async)
94
<?php
useIlluminate\Database\Schema\Blueprint;
useIlluminate\Database\Migrations\Migration;
classCreateQueueJobsTableextendsMigration{
/**
*Runthemigrations.
*
*@returnvoid
*/
publicfunctionup()
{
Schema::create('jobs',function(Blueprint$table)
{
$table->bigIncrements('id');
$table->string('queue');
$table->text('payload');
$table->tinyInteger('attempts')->unsigned();
$table->tinyInteger('reserved')->unsigned();
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}
/**
*Reversethemigrations.
*
*@returnvoid
*/
publicfunctiondown()
{
Schema::drop('jobs');
}
}
非同步(async)
95
建立隊列資料表
執行 phpartisanmigrate將隊列資料表新增至資料庫
設定隊列驅動
在 config/queue.php檔案中設定非同步資料庫隊列(AsyncDatabaseQueue)驅動設定,設定如下:
//config/queue.php
return[
'default'=>'async',
'connections'=>[
'async'=>[
'driver'=>'async',
'table'=>'jobs',
'queue'=>'default',
'expire'=>60,
'connection_name'=>'',
],
],
];
建立隊列工作
我們可以使用 \Queue::push('App\Commands\SendEmail@fire',$queue_data);的方法去新增要執行的隊列
第一個參數是執行隊列需要呼叫的類別名稱位置( App\Commands\SendEmail)
及方法( fire)
類別名稱需要正確的指定類別的命名空間(namespace),可以指定這個隊列要執
行的類別方法,只要將方法使用 @加在後方即可( @customMethod)
若沒有指定用哪個方法,Laravel預設會執行 fire的類別方法( @fire)
我們使用隊列來寄送Email,設定隊列的方式大概像這樣:
非同步(async)
96
//需要傳送給隊列處理的資料
$queue_data=[
'email'=>'kejyun@gmail.com',
'name'=>'KeJyun',
];
//建立隊列
$queue_id=\Queue::push('App\Commands\SendEmail@fire',$queue_d
ata);
在 App\Commands\Sendmail.php檔案大概會像這樣:
<?phpnamespaceApp\Commands;
classSendEmail{
/**
*執行隊列
*
*@returnvoid
*/
publicfunctionfire($job,$data)
{
//寄送Email
\Mail::send('emails.welcome',[],function($message)use
($data)
{
$message->to($data['email'],$data['name'])->subject(
'歡迎使用Laravel5資料庫隊列寄送Email!!!');
});
}
}
這樣我們就可以正常的使用隊列去幫我們寄信摟!!
目前(2015-06-01)barryvdh/laravel-async-queue在執行完隊列時,無法直
接刪除隊列資料,待作者修復這個bug
非同步(async)
97
參考資料
隊列-Laravel.twQueuesinLaravelwithRedisbarryvdh/laravel-async-queue-packagistLaravel5AsyncQueueDriver-Github
非同步(async)
98
輔助方法(Helpers)這裏介紹一些Laravel5的一些輔助方法用法
輔助方法(Helpers)
99
自定義輔助方法
Laravel中有提供許多的輔助方法(Helpers),但有時候我們會想要自訂自己的輔
助方法,我們可以這樣做
加入自定義引用的 Helpers.php檔案到/app/Support/Helpers/Helpers.php路徑下
<?php
///app/Support/Helpers/Helpers.php
//Helper檔案路徑
$helpers=[
'CustomHelper.php'
];
//載入Helper檔案
foreach($helpersas$helperFileName){
include__DIR__.'/'.$helperFileName;
}
以後若有其他的Helper需要加入,僅需要加到 Helpers.php檔案中的
$helpers變數當中即可
在 composer.json中自動載入加入該Helper.php
自定義輔助方法
100
/*composer.json*/
{
"autoload":{
"classmap":[
"database"
],
"psr-4":{
"App\\":"app/"
},
"files":[
"app/Support/Helpers/helpers.php"
]
}
}
重新編譯 autoload.php
$composerdump-autoload
Generatingautoloadfiles
這樣我們就可以自動的載入我們自定義的Helper函式了!!
BestpracticesforcustomhelpersonLaravel5
自定義輔助方法
101
單元測試
這裏會介紹一些在Laravel5做單元測試的一些技巧
參考資料
AddLaravelUnitTestsDirectlyFromChrome-LaravelNews
單元測試(UnitTest)
102
POSTCSRF錯誤
當我們在使用Unittest做POST測試時,測試的程式可能像:
<?php
classUserTestextendsTestCase{
/**
*測試註冊
*/
publicfunctiontestSignup()
{
$parameters=[
'email'=>'kejyun@gmail.com',
'name'=>'KeJyun'
];
//傳送參數
$response=$this->call('POST','/signup',$parameters);
$this->assertEquals(200,$response->getStatusCode());
}
}
在執行單元測試後,你會收到一個 TokenMismatchException的例外錯誤,這個
部分是MiddlewareVerifyCsrfToken的驗證錯誤
這是因為Laravel5在所有的 POST、 PUT、 DELETE的路由方法中,都會預設
加入CSRFToken的檢查,他會檢查POST過來的資料中 _token的資料值與
Session中的token是否相符,或是驗證標頭中的 X-CSRF-TOKEN是否相符。
所以在我們每一次做 POST、 PUT、 DELETE的請求時,我們都必須要將
CSRFToken帶入檢查,才能執行後面的程式動作,我們可以用這樣的方式帶入
CSRFToken:
PostCSRF錯誤
103
<?php
classUserTestextendsTestCase{
/**
*測試註冊
*/
publicfunctiontestSignup()
{
//開啟Session
Session::start();
//參數加入CSRFtoken
$parameters=[
'_token'=>csrf_token(),
'email'=>'kejyun@gmail.com',
'name'=>'KeJyun'
];
//傳送參數
$response=$this->call('POST','/signup',$parameters);
$this->assertEquals(200,$response->getStatusCode());
}
}
在使用 csrf_token()方法時,都必須要先使用 Session::start();將Session開啟,以紀錄當時的CSRFToken做驗證,並將 _token當作參數傳送
到 POST、 PUT、 DELETE的路由當中,就可以正常執行單元測試了!
參考資料
HTTP路由-Laravel.twTestingLaravel5RouteswithCSRFProtectionUsingPHPUnit
PostCSRF錯誤
104
錯誤與日誌
這裏介紹Laravel5處理一些錯誤例外的狀況
錯誤與日誌
105
在單元測試顯示例外(ShowExceptioninCLI)在Laravel5做單元測試時,使用trycatch丟出例外時,Laravel5會自動地將例外
的錯誤訊息處理成網頁的樣式呈現,這樣的好處是在網頁中做操作發生例外狀況
時,可以直接看到例外的錯誤訊息,但是在寫單元測試(Unittest)時,他只會將這
些錯誤先記錄在log檔案裡面( storage/log/laravel-yyyy-mm-dd.log),我
們要看到這些錯誤的狀況必須要再另開終端機去執行 phpartisantail去觀看
這些例外Log的狀況,這樣在做測試的時候是相當麻煩的。
在Laravel5中所有的例外(Exception)都會被丟到
app/Exceptions/Handler.php中的 render()去處理
<?php
//app/Exceptions/Handler.php
classHandlerextendsExceptionHandler{
/**
*RenderanexceptionintoanHTTPresponse.
*將例外錯誤轉為HTTP回應
*
*@param\Illuminate\Http\Request$request
*@param\Exception$e
*@return\Illuminate\Http\Response
*/
publicfunctionrender($request,Exception$e)
{
returnparent::render($request,$e);
}
}
我們如果需要在CLI就顯示例外錯誤訊息的話,必須修改 render()函式,而我
想要保有原本在網頁做除錯的彈性,所以我在環境變數 .env檔案中加入例外處
理(Exception)顯示的開關,當有設定開啟時,才直接顯示例外訊息。
在單元測試顯示例外
106
<!--.env-->
EXCEPTION_DISPLAY_SWITCH=true
.env設定好後,就將 render()函式修改為這樣
<?php
//app/Exceptions/Handler.php
classHandlerextendsExceptionHandler{
/**
*RenderanexceptionintoanHTTPresponse.
*將例外錯誤轉為HTTP回應
*
*@param\Illuminate\Http\Request$request
*@param\Exception$e
*@return\Illuminate\Http\Response
*/
publicfunctionrender($request,Exception$e)
{
//預設不直接顯示例外訊息
$exception_display_switch=env('EXCEPTION_DISPLAY_SWITC
H',false);
if($exception_display_switch){
throw$e;
}
returnparent::render($request,$e);
}
}
這樣設定完後,也保留原本系統處理例外狀況的彈性,也可以讓我在單元測試
(Unittest)中可以正常的去顯示例外訊息了!!
參考資料
在單元測試顯示例外
107
錯誤與日誌-laravel.twhowdoIcreatecustomerrorpageinlaravel5Laravel5.0-CustomErrorPages
在單元測試顯示例外
108
日誌記錄層級
在系統發生例外錯誤時,我們會希望紀錄當時的例外訊息,以便之後我我們好進行
除錯,而Log紀錄的訊息會依照日期被放到像 storage/logs/laravel-2015-06-06.log的地方
Log記錄在Laravel有七個級別:debug、info、notice、warning、error、critical和alert,紀錄的方式會像這樣:
Log::debug('===Log訊息===');
Log::info('===Log訊息===');
Log::notice('===Log訊息===');
Log::warning('===Log訊息===');
Log::error('===Log訊息===');
Log::critical('===Log訊息===');
Log::alert('===Log訊息===');
他們在Log檔紀錄的樣子會像:
[2015-06-0616:22:00]testing.DEBUG:===Log訊息===
[2015-06-0616:22:00]testing.INFO:===Log訊息===
[2015-06-0616:22:00]testing.NOTICE:===Log訊息===
[2015-06-0616:22:00]testing.WARNING:===Log訊息===
[2015-06-0616:22:00]testing.ERROR:===Log訊息===
[2015-06-0616:22:00]testing.CRITICAL:===Log訊息===
[2015-06-0616:22:00]testing.ALERT:===Log訊息===
我們透過不同的記錄層級,讓我們容易找到層級比較嚴重的Bug先進行修復
參考資料
錯誤與日誌-Laravel.tw
日誌記錄層級
109
日誌巨集
前言
我們會用Laravel內建的Response去回應服務的訊息,我們可能會用的回應會像
這樣:
//建立JSON回應
returnResponse::json(['name'=>'KeJyun','Country'=>'Taiwan'
]);
returnresponse()->json(['name'=>'KeJyun','Country'=>'Taiwa
n']);
//建立JSONP回應
returnResponse::json(['name'=>'KeJyun','Country'=>'Taiwan'
])
->setCallback($request->input('callback'));
returnresponse()->json(['name'=>'KeJyun','Country'=>'Taiwa
n'])
->setCallback($request->input('callback'));
//建立檔案下載的回應
returnresponse()->download($pathToFile);
returnresponse()->download($pathToFile,$name,$headers);
returnresponse()->download($pathToFile)->deleteFileAfterSend(tr
ue);
在這樣的使用下,我們可以很容易的回應訊息給使用者,但是在伺服器發生程式例
外錯誤(Exception)時,我們可能也需要回應像是這樣的資料:
日誌巨集
110
returnResponse::json(['status'=>'failure','error_code'=>'5
566']);
returnresponse()->json(['status'=>'failure','error_code'=>
'5566']);
在我們用Laravel做API給手機用的時候,更需要有這些錯誤狀態的資料,所以我
們沒辦法直接像網頁一樣跳出整個的錯誤debug畫面
但我們若想在API回應給手機這樣的錯誤資訊時,也能夠將例外錯誤記錄下來,以
便我們進行除錯,我們可以做一個Response的巨集,去處理紀錄我們的回應
建立服務提供者
我們在命令列輸入 phpartisanmake:providerResponseServiceProvider建立回應的服務提供者
$phpartisanmake:providerResponseServiceProvider
該服務提供者檔案會被建立在 app/Providers/ResponseServiceProvider.php中,命名空間為 App\Providers\ResponseServiceProvider
我們不一定要將服務提供者的檔案放到 app/Providers目錄中,我們可以
依照自己專案的需求,將他移動到像是 app/KeJyun/Providers目錄中,這
樣命名空間就會變成
App\KeJyun\Providers\ResponseServiceProvider,檔案放置的位置隨
自己專案需求而定,只要遵照PSR-4的規定去設定命名空間及檔案位置即可
我新增了一個名稱為jsonLog的Response巨集,該巨集會回應json資料,並依
照記錄層級紀錄我們傳給他的資訊,ResponseServiceProvider程式會像這樣
<?phpnamespaceApp\KeJyun\Providers;
//app/KeJyun/Providers/ResponseServiceProvider.php
useIlluminate\Support\ServiceProvider;
useResponse,Log;
classResponseServiceProviderextendsServiceProvider{
日誌巨集
111
/**
*Bootstraptheapplicationservices.
*
*@returnvoid
*/
publicfunctionboot()
{
/**
*註冊Response記錄錯誤巨集
*
*@paramArray$response_data
回傳的json資料
*@paramArray|Object|String$log_data
紀錄的資料
*@paramString$log_level
紀錄資料的等級(預設為info)
*
*@returnResponse$response
回應的json資料
*
*@accesspublic
*@authorKeJyunkejyun@gmail.com
*@date2015-06-06
*/
Response::macro('jsonLog',function(
$response_data,
$log_data='NoDataBelog!!!',
$log_level='info'
){
//增加Log檔案錯訊息間距以便閱讀
Log::debug("\n\n\n\n\n");
Log::debug($response_data);
Log::debug("\n\n");
switch($log_level){
case'debug':
Log::debug($log_data);
break;
case'notice':
日誌巨集
112
Log::notice($log_data);
break;
case'warning':
Log::warning($log_data);
break;
case'error':
Log::error($log_data);
break;
case'critical':
Log::critical($log_data);
break;
case'alert':
Log::alert($log_data);
break;
case'info':
default:
Log::info($log_data);
break;
}
//增加Log檔案錯訊息間距以便閱讀
Log::debug("\n\n\n\n\n");
returnResponse::json($response_data);
});
}
/**
*Registertheapplicationservices.
*
*@returnvoid
*/
publicfunctionregister()
{
//
}
}
日誌巨集
113
設定服務提供者
設定完自己的jsonLog紀錄巨集後,我們需要到 config/app.php設定這個
Response服務提供者,我的命名空間為
App\KeJyun\Providers\ResponseServiceProvider,所以設定會像這樣:
//config/app.php
'providers'=>[
//其他的服務提供者
'App\KeJyun\Providers\ResponseServiceProvider',
],
設定完之後,Laravel在啟動時就會自動載入該服務提供者了
使用自定的Response巨集jsonLog在我們撰寫商業邏輯時若發生無法預期的例外狀況,我們會想要紀錄該例外狀況的
資料,我們就可以這樣使用ResponsejsonLog巨集:
try{
//商業邏輯處理
}catch(Exception$exception){
$response_data=[
'status'=>'failure',
'error_code'=>5566,
];
returnresponse()->jsonLog($response_data,$exception,'aler
t');
}
這樣在系統發生預期之外的例外時,我們也有參考的資料可以幫我們進行除錯
了!!
參考資料
日誌巨集
114
HTTP回應:回應巨集-Laravel.tw服務提供者-Laravel.tw
日誌巨集
115
加密
使用情境
我們若需要在資料庫儲存一些敏感資料(像是信用卡的資料),但我們又為了避免
資料庫遭到入侵,而導致所有使用者相關的敏感資料全都被竊取,我們可以使用
Laravel提供的「加密與解密」演算法,將我們的敏感資料加密儲存到資料庫,待
我們讀取資料的時候,再將其資料解密出來處理。
設定
在Laravel做「加密與解密」演算法時,會使用 config/app.php中的key值去
當作加解密的salt,自己的應用需要設定自己的key值,若沒有設定的話被加密過
的值還是有可能被暴力破解出來,所以要記得去設定,而這個key值若變更了,雜
湊的驗證也不會相同喔~
使用
//加密
$original_data='需要加密的資料';
$encrypt_data=Crypt::encrypt($original_data);
//解密
$decrypted=Crypt::decrypt($encrypt_data);
備註
重複加密相同的資料得到的密文不會一樣,所以不要使用像md5的方式去比對密
文資料是否相同
使用md5比較密文
加密
116
$original_data='需要加密的資料';
//第1次使用md5加密的資料
$first_md5_hash_data=md5($original_data);
//第2次使用md5加密的資料
$second_md5_hash_data=md5($original_data);
//資料相同
//true
var_dump($first_md5_hash_data===$second_md5_hash_data);
使用加密演算法比較密文
$original_data='需要加密的資料';
//第1次使用加密演算法加密的資料
$first_encrypt_data=Crypt::encrypt($original_data);
//第2次使用加密演算法加密的資料
$second_encrypt_data=Crypt::encrypt($original_data);
//資料不相同
//false
var_dump($first_encrypt_data===$second_encrypt_data);
參考資料
加密-Laravel.tw
加密
117
雜湊
使用情境
使用者輸入的密碼,通常我們將其加密再存到資料庫中,但這類的資料我們通常不
需要反解回來處理,所以我們不需要使用加密的演算法去加密資料
因為加密演算法需要完整的解回原先的資料,所以若資料越長密文也會越長,但雜
湊不需要解回原先的資料,只需要驗證原先的資料,經過再雜湊的檢查是相同的就
好(輸入的密碼雜湊驗證與原先存在資料庫的雜湊資料相同),所以雜湊的資料可
以有固定的長度,像是md5的雜湊資料長度固定為32,而Laravel提供的Hash雜湊演算法,資料長度固定為60。
設定
在Laravel做「雜湊」演算法時,會使用 config/app.php中的key值去當作雜
湊的salt,自己的應用需要設定自己的key值,若沒有設定的話被加密過的值還是
有可能被暴力破解出來,所以要記得去設定,而這個key值若變更了,雜湊的驗證
也不會相同喔~
使用
雜湊
//雜湊
$original_password='密碼明碼';
$hash_password=Hash::make($original_password);
驗證
雜湊
118
//雜湊
$original_password='密碼明碼';
$hash_password=Hash::make($original_password);
//驗證
$check_result=Hash::check($original_password,$hash_password);
//true
var_dump($check_result);
備註
重複雜湊相同的資料得到的密文不會一樣,所以不要使用像md5的方式去比對密
文資料是否相同
使用md5比較密文
$original_password='密碼明碼';
//第1次使用md5加密的資料
$first_md5_hash_password=md5($original_password);
//第2次使用md5加密的資料
$second_md5_hash_password=md5($original_password);
//資料相同
//true
var_dump($first_md5_encrypt_password===$second_md5_encrypt_pas
sword);
雖然每次雜湊的結果都不一樣,但你可以放心的將任何一次雜湊的資料存放到資料
庫中,因為雖然密文不同,但Laravel的雜湊演算法,還是可以比對出來是不是由
相同的資料去做雜湊的
使用雜湊演算法比較資料是否相同
雜湊
119
$original_password='密碼明碼';
//第1次使用雜湊演算法雜湊的資料
$first_hash_password=Hash::make($original_password);
//第2次使用雜湊演算法雜湊的資料
$second_hash_password=Hash::make($original_password);
//資料不相同
//false
var_dump($first_encrypt_password===$second_encrypt_password);
//驗證雜湊資料true
$first_check_result=Hash::check($original_password,$first_has
h_password);
$second_check_result=Hash::check($original_password,$second_h
ash_password);
參考資料
雜湊-Laravel.tw
雜湊
120
任務排程
我們通常會把一些每小時、每6小時、每日、每週、每月等等之類固定時間要做的
工作丟到Linux系統的crontab中去執行,通常像是每日要統計昨天網站的活動資
訊做數據分析之類的工作,這類的工作通常會花費比較久的時間
在Linux設定排程工作
我們通常會在命令列用 $crontab-e的方式去編輯排程工作
$crontab-e
在用到crontab的時候,我們需要瞭解怎麼設定排程工作的執行時間,整個的
crontab的設定可能會像這樣:
#每天凌晨3點統計昨天的Pageview
03***/usr/bin/php/home/kejyun/laravel4/artisancronjob:sta
tisticYesterdayPageview
在前方可以看到有5個數字可以做設定,依序分別代表的意思為:
分鐘(0-59)小時(0-23)每個月第幾天(1-31)月份(1-12)每週的第幾天(0-6)
0:星期日
1:星期一
2:星期二
3:星期三
4:星期四
5:星期五
6:星期六
任務排程
121
這5個參數之間用空白隔開,每個參數除了設定單一個數字,也可以用逗號(,)去隔
開設定相同單位的時間設定,像是:
#每天凌晨4點及16點寄送廣告信
04,16***/usr/bin/php/home/kejyun/laravel4/artisancronjob:
sendCommercialMail
這裏有一些相關的設定範例可以當作參考:
#每小時的第18分鐘執行
18****
#8點10分執行
108***
#8點的每分鐘執行一次(共執行60次)
*8***
#在每個禮拜二每小時的第18分鐘執行
18***2
#你也可以每隔一段時間去執行crontab
#如果我們每15分鐘要去執行,你可以用這樣的格式*/15
#這樣的意思是將分鐘數,切割成(除以)每15分鐘執行
*/15****
#每2小時執行
0*/2***
#每2小時又20分鐘執行
*/20*/2***
小提醒
系統的crontab運作方式是 每分鐘會到設定的crontab找看看有沒有符合現
在這個時間的排程工作,所以像是 *8***這樣的設定,因為沒有明確指
定分鐘,在8點每分鐘檢查的60次都符合條件,所以會執行60次,若僅要8點時執行一次,請明確設定要執行的分鐘條件,像是 08***
任務排程
122
使用Laravel5.2Scheduling設定排程工作
因為Linux每分鐘都會去檢查當時是否有排程需要執行的工作,符合條件的時間就
會執行,Laravel利用這個特性,告訴排程每分鐘都要執行Laravel的schedule:run的指令
*****php/path/to/artisanschedule:run>>/dev/null2>&1
之後Laravel每分鐘就會執行 app/Console/Command/Kernal@schedule的程
式,Laravel會依照 schedule裡面的設定時間,執行符合條件的排程工作
排程範例
假如我們有一個排程每分鐘都會紀錄他執行的時間,程式碼會放在
app/Console/Command/TestLog.php,程式碼會像是:
任務排程
123
<?php
namespaceApp\Console\Commands;
useIlluminate\Console\Command;
useFile;
classTestLogextendsCommand
{
//命令名稱
protected$signature='test:Log';
//說明文字
protected$description='[測試]Log檔案';
publicfunction__construct()
{
parent::__construct();
}
//Console執行的程式
publicfunctionhandle()
{
//檔案紀錄在storage/test.log
$log_file_path=storage_path('test.log');
//記錄當時的時間
$log_info=[
'date'=>date('Y-m-dH:i:s')
];
//記錄JSON字串
$log_info_json=json_encode($log_info)."\r\n";
//記錄Log
File::append($log_file_path,$log_info_json);
}
}
任務排程
124
以上排程的命令是 phpartisantest:Log,執行之後就會記錄當時執行的時間
排程程式建立好之後,在 app/Console/Command/Kernal.php定義此排程的工
作,並設定每分鐘執行一次,程式碼會像是:
<?php
namespaceApp\Console;
useIlluminate\Console\Scheduling\Schedule;
useIlluminate\Foundation\Console\KernelasConsoleKernel;
classKernelextendsConsoleKernel
{
//定義應用程式的Artisan指令
protected$commands=[
\App\Console\Commands\TestLog::class,
];
//定義應用程式的排程
protectedfunctionschedule(Schedule$schedule)
{
//每分鐘執行Artisan命令test:Log
$schedule->command('test:Log')->everyMinute();
}
}
這樣你就可以在 storage/test.log每分鐘看到像這樣的紀錄,這樣就表示你的
排程設定成功了!!
任務排程
125
{"date":"2016-01-1122:12:05"}
{"date":"2016-01-1122:13:05"}
{"date":"2016-01-1122:14:05"}
{"date":"2016-01-1122:15:05"}
{"date":"2016-01-1122:16:05"}
{"date":"2016-01-1122:17:07"}
{"date":"2016-01-1122:18:09"}
{"date":"2016-01-1122:19:05"}
你也可以使用其他Laravel提供的時間方法去定義要執行的時間
方法 說明
->cron(''); 自訂Cron排成時間
->everyMinute(); 每分鐘執行
->everyFiveMinutes(); 每5分鐘執行
->everyTenMinutes(); 每10分鐘執行
->everyThirtyMinutes(); 每30分鐘執行
->hourly(); 每小時執行
->daily(); 每天執行
->dailyAt('13:00'); 每天13:00執行
->twiceDaily(1,13); 每天1:00及13:00執行
->weekly(); 每週執行
->monthly(); 每月執行
->yearly(); 每年執行
->sundays() 每週日執行
->mondays() 每週一執行
->tuesdays() 每週二執行
->wednesdays() 每週三執行
->thursdays() 每週四執行
->fridays() 每週五執行
->saturdays() 每週六執行
->when(Closure) 每當符合條件就執行(returntrue)
任務排程
126
避免重複執行排程
排程預設每次符合條件就要執行,但若我們執行一個需要跑很久的程式,在下一次
符合條件的時間若上一個同樣的工作還沒有執行完,我們就不執行的話,我們可以
用 ->withoutOverlapping()方法去避免排程程式重複執行,像是
$schedule->command('emails:send')->withoutOverlapping();
輸出執行結果
我們的在執行Artisan指令時,我們通常會在畫面上列印一些執行狀態,像是
$this->info('把我顯示在畫面上');,如果我們想要知道排程執行時,這些顯示
在畫面的文字記錄下來,我們可以用
方法 說明
->sendOutputTo($filePath);將結果輸出到檔案(複寫該檔案)
->appendOutputTo($filePath);將結果附加在檔案後面(不複寫檔案)
-
>emailOutputTo('foo@example.com');將結果寄送到指定Email
將結果輸出到檔案(複寫該檔案)
$schedule->command('emails:send')
->daily()
->sendOutputTo($filePath);
將結果附加在檔案後面(不複寫檔案)
$schedule->command('emails:send')
->daily()
->appendOutputTo($filePath);
將結果寄送到指定Email
任務排程
127
$schedule->command('foo')
->daily()
->sendOutputTo($filePath)
->emailOutputTo('foo@example.com');
排程觸發
我們可以在排程執行前後,分別使用 ->before()或 ->after()去執行排程其
他的工作
$schedule->command('emails:send')
->daily()
->before(function(){
//在排程執行前觸發
})
->after(function(){
//在排程執行完成後觸發
});
參考資料
crontab.guru-thecronscheduleexpressioneditorAvisualcrontabeditorTaskScheduling-Laravel5.2
任務排程
128
Elixir這裏會介紹Laravel5使用NPM套件Elixir管理資源的相關說明
使用需求
因為Laravel的Elixir是透過Node.js的套件,去將Laravel目錄結構下的資源做整
合的工具,所以我們需要安裝Node.js的相關套件,需要安裝的有下列:
NPM(NodePackageManager)NPMgulp套件
NPMlaravel-elixir套件
安裝NPMNPM是Node.js的套件管理工具,Node.js在0.6.3版本開始內建npm,你可以透
過安裝Node.js的方式去安裝NPM,或是透過NPM官方網站提供的安裝指令去安
裝NPM
$curl-Lhttps://www.npmjs.com/install.sh|sh
安裝完NPM後,我們就可以使用NPM去安裝相關的套件了!
安裝NPMgulp套件
gulp是Node.js的自動建構的工具,可以透過它整合並建立資源
我們要到我們Laravel專案的根目錄,然後執行 npminstallgulp去安裝gulp
kejyun@KeJyundeMBP:~/Code/KeJyunLaravel5Proj$npminstallgulp
所有NPM安裝的套件會在專案跟目錄下的 node_modules目錄下,注意!
這個目錄不應該在專案的版本控制當中~
Elixir
129
安裝NPMlaravel-elixir套件
一樣在我們Laravel專案的根目錄,然後執行 npminstalllaravel-elixir去安裝laravel-elixir
$npminstalllaravel-elixir
這些套件安裝完後,我們就可以開始使用Elixir的開發好處了!!
參考資料
Node.jsnpmNPM套件管理工具
gulp.js-thestreamingbuildsystemLaravelElixir-laravel.tw
Elixir
130
使用Elixir合併CSS與JSLaravelCSS與JS相關的資源預設是放在 resources/assets/scss與resources/assets/js下,我們需要把我們的CSS與JS放在這個目錄下,才
可以使用Elixir去編譯我們的CSS與JS檔案
使用Elixir之前請先安裝完所需要的軟體,詳情請點這裡
使用Elixir合併CSS
撰寫CSSresources/assets/scss/a.css
.a{
color:green;
}
resources/assets/scss/b.css
.b{
color:blue;
}
合併CSS資源
在我們專案根目錄 gulpfile.js檔案中,我們撰寫 mix.styles(['a.css','b.css']);,告訴laravel-elixir將我們的CSS資源整合起來
使用Elixir合併CSS與JS
131
//gulpfile.js
//載入laravel-elixir套件
varelixir=require('laravel-elixir');
//使用laravel-elixir套件合併CSS
elixir(function(mix){
mix.styles(['a.css','b.css']);
});
執行gulp整合CSS在我們專案根目錄下執行gulp指令,使用laravel-elixir整合所有CSS資源,大概
會像這樣:
kejyun@KeJyundeMBP:~/Code/KeJyunLaravel5Proj$gulp
[22:33:04]Usinggulpfile~/Code/KeJyunLaravel5Proj/gulpfile.js
[22:33:04]Starting'default'...
[22:33:04]Starting'styles'...
[22:33:04]Merging:resources/assets/css/a.css,resources/assets
/css/b.css
[22:33:04]Finished'default'after89ms
[22:33:04]Finished'styles'after122ms
預設會將CSS檔案整合至 public/css/all.css檔案中,你在 all.css會看
到我們整合CSS檔案的結果,就像這樣
.a{
color:green;
}
.b{
color:blue;
}
/*#sourceMappingURL=all.css.map*/
使用Elixir合併CSS與JS
132
我們也可以在 gulpfile.js檔案中指定我們要編譯檔案的名稱目錄是什麼,就
像下面我把CSS編譯的檔案名稱目錄指定為 public/css/kejyun.css,laravel-elixir就會將編譯的檔案設為您指定的名稱目錄
//gulpfile.js
//載入laravel-elixir套件
varelixir=require('laravel-elixir');
//使用laravel-elixir套件合併CSS
elixir(function(mix){
mix.styles(['a.css','b.css'],'public/css/kejyun.css');
});
使用Elixir合併JS
撰寫JSresources/assets/js/a.js
vara=1;
resources/assets/js/b.js
varb=2;
合併JS資源
在我們專案根目錄 gulpfile.js檔案中,我們撰寫 mix.scripts(['a.js','b.js']);,告訴laravel-elixir將我們的JS資源整合起來
使用Elixir合併CSS與JS
133
//gulpfile.js
//載入laravel-elixir套件
varelixir=require('laravel-elixir');
//使用laravel-elixir套件合併JS
elixir(function(mix){
mix.scripts(['a.js','b.js']);
});
執行gulp整合JS在我們專案根目錄下執行gulp指令,使用laravel-elixir整合所有CSS資源,大概
會像這樣:
kejyun@KeJyundeMBP:~/Code/KeJyunLaravel5Proj$gulp
[22:47:21]Usinggulpfile~/Code/KeJyunLaravel5Proj/gulpfile.js
[22:47:21]Starting'default'...
[22:47:21]Starting'scripts'...
[22:47:21]Merging:resources/assets/js/a.js,resources/assets/j
s/b.js
[22:47:21]Finished'default'after86ms
[22:47:21]Finished'scripts'after122ms
預設會將JS檔案整合至 public/js/all.js檔案中,你在 all.js會看到我
們整合JS檔案的結果,就像這樣
vara=1;
varb=2;
//#sourceMappingURL=all.js.map
我們也可以在 gulpfile.js檔案中指定我們要編譯檔案的名稱目錄是什麼,就
像下面我把JS編譯的檔案名稱目錄指定為 public/js/kejyun.js,laravel-elixir就會將編譯的檔案設為您指定的名稱目錄
使用Elixir合併CSS與JS
134
//gulpfile.js
//載入laravel-elixir套件
varelixir=require('laravel-elixir');
//使用laravel-elixir套件合併JS
elixir(function(mix){
mix.scripts(['a.js','b.js'],'public/js/kejyun.js');
});
使用Elixir同時合併CSS與JS檔案
我們可以透過 JavascriptMethodChaining的方式連續的指定我們要合併的
資源,這樣我們執行gulp時,就可以同時整合這些資源
//gulpfile.js
//載入laravel-elixir套件
varelixir=require('laravel-elixir');
//使用laravel-elixir套件合併資源
elixir(function(mix){
mix
.scripts(['a.js','b.js'],'public/js/kejyun.js')
.styles(['a.css','b.css'],'public/css/kejyun.css');
});
laravel-elixir還可以整合其他的資源,像是Less、SCSS、SASS、CoffeeScript...等等之類的資源,詳情請看LaravelElixir-laravel.tw的說明即可!
參考資料
ElixirImprovementsLaravelElixir-laravel.tw
使用Elixir合併CSS與JS
135
設計模式
這裏會介紹一些Laravel5會用到的設計模式
設計模式
136
服務容器(ServiceContainer)Laravel的服務容器就像IoC容器一樣,可以讓你很容易的反轉控制物件
假如我們沒有注入類別去進行反轉控制,則我們每次使用Mail類別去寄送郵件時都
要去new它,如果這個Mail類別在裡面是會被很頻繁的使用時,這樣會讓我們很
惱人。
//通知類別
classNotification{
//通知會員有新訊息
publicfunctionnoticeNewMessage(){
$mail=newMail();
$mail->send();
}
//通知會員有新文章
publicfunctionnoticeNewArticle(){
$mail=newMail();
$mail->send();
}
}
為了能夠讓通知類別 Notification能夠隨時取用 Mail類別,我們會希望將
此類別直接注入,讓通知類別可以直接去進行反轉控制。
在我們使用反轉控制(IoC)時,我們時常需要在建構子 __construct()或方法
function()中注入需要反轉控制的類別,讓被注入的類別可以直接控制其物
件,就像:
服務容器
137
//通知類別
classNotification{
public$mail;
publicfunction__construct(Mail$mail){
$this->mail=$mail;
}
//通知會員有新訊息
publicfunctionnoticeNewMessage(){
$this->mail->send();
}
//通知會員有新文章
publicfunctionnoticeNewArticle(){
$this->mail->send();
}
}
我們在通知類別 Notification建構子中注入 Mail類別給內部 mail變數,之後要使用此Mail類別時,就直接使用傳送信件 send()的功能即可。
我們也可以在個別的函式中分別注入需要反轉控制的類別,就像:
//通知類別
classNotification{
//通知會員有新訊息
publicfunctionnoticeNewMessage(Mail$mail){
$mail->send();
}
//通知會員有新文章
publicfunctionnoticeNewArticle(Mail$mail){
$mail->send();
}
}
服務容器
138
但是在Laravel4要注入類別之前,必須先對類別名稱對app進行綁定,讓
Laravel4認得這個要注入的類別是什麼物件
App::bind('Mail',function($app)
{
returnnewSomeEmailService;
});
但是在Laravel5除了可以用這樣手動綁定類別的方式,也有提供強大的自動綁定
功能,你不需要在app內事先定義所有的類別,當Laravel5在app內找不到該類
別的時候,就會自動找所有引入(include)的類別中有沒有此類別,自動進行注入
綁定!
參考資料
TheServiceContainer-Laracast服務容器-Laravel.twIoC容器
DependencyInjectionwithLaravel’sIoC
服務容器
139
PSR(phpstandardrecommendations)為了讓大家開發的套件,能夠更輕鬆地整合到自己的專案當中,在PHP社群中大
家一起定義了一些標準的程式碼撰寫規則
但是Laravel5.0.x版本之前,Laravel都沒有真正的遵照PSR的規範去撰寫程式
碼,直到Laravel5.1LTS版本時,Laravel終於將所有的程式碼遵照PSR-2及PSR-4的程式碼撰寫規則了,詳細的規則說明大家可以自己參考相關的說明文件。
而為了讓自己專案的開發也能夠遵照PSR規則,除了自己一個檔案一個檔案自己
修改外,也可以用PHPCodingStandardsFixer套件去幫我們 自動地將程式修改
成遵照PSR規則的程式!
PHPCodingStandardsFixer安裝使用教學
使用compser下載套件
使用composer將php-cs-fixer安裝到全域(global)目錄下
$composerglobalrequirefabpot/php-cs-fixer
設定composerbin目錄到環境變數中
我們必須要將我們家目錄下的全域 ~/.composer/vendor/bin目錄,設到環境變
數中,這樣我們在命令列就可以直接執行 ~/.composer/vendor/bin下面的可執
行檔案了
$exportPATH="$PATH:$HOME/.composer/vendor/bin"
我們可以直接在命令列下這樣的指令就可以了,但每次開啟新的Terminal視窗
時,都要再重新的設定一次這樣的環境變數,所以我們也可以把這個設定寫在
~/.bash_profile檔案中,這樣每次執行Terminal時,就會自動將
~/.composer/vendor/bin設到環境變數中了!
PSR
140
使用php-cs-fixer修正PHP檔案
設定完成後,我們就可以使用 php-cs-fixerfix/path/to/project--level=psr2這樣的指令去修正我們專案目錄下的檔案了
一些php-cs-fixer相關的指令會像這樣:
$php-cs-fixerfix/path/to/project--level=psr0
$php-cs-fixerfix/path/to/project--level=psr1
$php-cs-fixerfix/path/to/project--level=psr2
$php-cs-fixerfix/path/to/project--level=symfony
設定Sublime使用php-cs-fixer修正程式碼
在Sublime上方工具列 Tools\BulidSystem\NewBuildSystem我們可以新
增一個新的建立指令,指令中我們輸入像這樣的指令:
{
"shell_cmd":"php-cs-fixerfix$file--level=psr2"
}
將新的指令檔案名稱取為 php-cs-fixer.sublime-bulid,這樣我們回到
Sublime去開啟任一PHP檔案,只要按下 Command(⌘)+ B,Sublime就會自
動幫我們執行php-cs-fixer的shellscript指令,去修正我們的PHP檔案了!!
php-cs-fixer使用小技巧
我們可以將修改 ~/.bash_profile檔案,將使用php-cs-fixer修正Terminal目前目錄的PHP指令加入,這樣我們只要用Terminal瀏覽到我們想要做php-cs-fixer的目錄下,我們每次只需要下 phpCSFixerThisFolder指令就可以了,這
樣就不用記住也不用打那麼落落長的php-cs-fixer指令了!
aliasphpCSFixerThisFolder="php-cs-fixerfix./--level=psr2"
PSR中文文件
PSR
141
PSR-1-BasicCodingStandardPSR-2-CodingStyleGuidePSR-3-日誌介面
PSR-4-Autoloader
參考資料
PHP-FIG—PHPFrameworkInteropGroupPHP:TheRightWay-繁體中文
PHPCodingStandardsFixerPHP-CS-FixerAdoptingPSR-2-laracasts
PSR
142
Model模型設計模式
我們在使用任何的Framework中,都會聽到MVC模型,V(View)是負責畫面顯
示,C(Controller)是負責控制程式呼叫模型的邏輯,而最重要的M(Model)是
負責整個資料庫的操作,以及撈取資料的邏輯
我們常常把模型用來作為處理資料的商業邏輯,不管是任何的「資料樣式的轉
換」、「資料撈取的邏輯」、「資料格式的驗證」、「資料處理的順序及商業邏
輯」...等等都是放在模型(Model)去處理
資料樣式的轉換
//2016-01-0100:00:00.123789
$now=Carbon::now();
//2016/01/01
$now_date=$now->format('Y/m/d');
資料撈取的邏輯
撈取所有的女會員資料,年紀小於30歲
User::where('gender'=>'female')
->where('age','<',30)
->get();
撈取所有的男會員資料,年紀大於30歲
User::where('gender'=>'male')
->where('age','>',30)
->get();
資料格式的驗證
Model模型
143
$validator=Validator::make(Request::all(),[
'title'=>'required|unique:posts|max:255',
'content'=>'required',
]);
資料處理的順序及商業邏輯
/**
*發送Email及簡訊給所有女會員
*/
//取得所有女會員資料
$users=User::where('gender'=>'female')
->get();
//發送Email
foreach($usersas$u){
Mail::send('emails.hello',['user'=>$u],function($mail)use
($u){
$mail->to($u->email,$u->name)
->subject('安安!');
});
}
//發送簡訊
foreach($usersas$u){
SMS::send('sms.hello',['user'=>$u],function($sms)use($u)
{
$sms->to($u->mobile_phone,$u->name)
->content('安安!');
});
}
如果把這些不同類別的資料全部丟到Model模型去處理會變得很亂,程式碼難以維
護,所以我們會用設計模式來降低程式碼的耦合性,讓程式變得容易維護,我們會
將Model分成:
實體(Entity)
Model模型
144
資源庫(Repository)服務(Service)表單驗證(Form)
資料呈現(Presenter)ARCA架構檔案結構
實體(Entity)實體就是我們用來設定EloquentModel的相關設定,像是資料表名稱($table)、
主鍵名稱($primaryKey)等等,裡面除了Eloquent相關設定以外,不要擺任何的
商業邏輯或資料撈取方法
實體與資料表的關係是「1對1」的關係,有幾個資料表就有幾個實體
詳情請見EloquentModel(模型)-設定
資源庫(Repository)資源庫是我們要用來撈取資料表資料的各個邏輯,我們資料表會有不同的欄位,不
同的欄位條件代表不同的意義,像是:
撈取所有的女會員資料,年紀小於30歲
User::where('gender'=>'female')
->where('age','<',30)
->get();
撈取所有的男會員資料,年紀大於30歲
User::where('gender'=>'male')
->where('age','>',30)
->get();
這些不同的撈取資料邏輯,我會將它包在資源庫中,該資源庫長得會像這樣:
Model模型
145
classUserRepository{
/**
*撈取所有的女會員資料,年紀小於30歲
*/
publicfunctiongetYoungFemale()
{
returnUser::where('gender'=>'female')
->where('age','<',30)
->get();
}
/**
*撈取所有的男會員資料,年紀大於30歲
*/
publicfunctiongetOldMale()
{
returnUser::where('gender'=>'male')
->where('age','>',30)
->get();
}
}
這樣我們撈取這些不同資料邏輯時就可以這樣去撈取:
$userRepository=newUserRepository();
//撈取所有的女會員資料,年紀小於30歲
$young_female_user=$userRepository->getYoungFemale();
//撈取所有的男會員資料,年紀大於30歲
$old_male_user=$userRepository->getOldMale();
這樣除了可以讓程式碼易讀性提高之外,撈取資料的邏輯也可以抽離出來,下次如
果有需要撈取同樣的資料時,就可以重複的去使用它,而且不會有重複的程式碼出
現在專案的各個地方,讓管理程式碼變得簡單
資源庫與實體的關係是「1對1」的關係,有幾個實體就有幾個資源庫,每個資源
庫是代表那個實體的各個不同的資料撈取邏輯
Model模型
146
服務(Service)服務代表我們程式要處理資料的商業邏輯,我會將各個功能邏輯獨立成一個服務,
像是使用者「註冊身份驗證」是一個服務,而使用者「個人隱私設定」也是一個服
務
服務與資源庫的關係是「多對1」的關係,像是同樣使用者資料,有「註冊身份驗
證」及「個人隱私設定」2種不同類型的服務
使用者「註冊身份驗證」服務
/**
*使用者「註冊身份驗證」服務
*/
classUserAuthService
{
/**
*註冊
*/
publicfunctionsignup()
{
}
/**
*登入驗證
*/
publicfunctionsignin()
{
}
}
使用者「個人隱私設定」服務
Model模型
147
/**
*使用者「個人隱私設定」服務
*/
classUserPrivacyServiceextendsAnotherClass
{
/**
*取得使用者隱私設定
*/
publicfunctiongetUserPrivacy()
{
}
/**
*設定使用者隱私
*/
publicfunctionsetUserPrivacy()
{
}
}
不同類型的服務,只要彼此耦合性很低,我傾向把他分成不同的服務去處理,這樣
可以很清楚的知道哪個個服務是專門處理哪一種商業邏輯,程式也比較好管理,在
異動程式時也比較不會影響到彼此,避免牽一髮動全身的狀況發生
表單驗證(Form)
我們設計後端程式的原則,是不要相信任何第三方傳來的資料,在資料做進一步處
理時都需要對資料格式做檢查,若於我們設定的資料格式相符,我們才會去做進一
步的資料商業邏輯處理
但是我們可能會在控制器(Controller)做表單資料的驗證,但是服務
(Service)、資源庫(Repository)或實體(Entity)為了保護自己的程式邏輯,
也有可能去做表單資料的驗證,若每一個階段都做表單資料的驗證,這樣不僅造成
Model模型
148
了資料發生重複驗證的狀況,也會降低程式的執行速度,更慘的是會造成驗證程式
重複出現,如果有驗證規則要修改,我們就必須要確保所有有驗證表單資料的地
方,都有正確的被修改,不然程式的商業邏輯可能會沒辦法順利的去執行。
因為我們對模型做了分層地處理,所以模型的層級架構會像:
控制器(Controller)>服務(Service)>資源庫(Repository)>實體
(Entity)
控制器會根據他需要的商業邏輯,呼叫不同的服務來處理他的程式邏輯,而且每個
控制器,而且每個控制器可能會有不同類型的服務,可能會有使用者(User)的資
料、文章(Posts)的資料...等等需要做資料的驗證,所以驗證資料的規則複雜度會
很多。
我自己會傾向將所有的表單資料驗證都放在服務(Service)中去驗證,不同的商業
邏輯可能需要驗證的資料規則不同,但是我們可以確定的是,同一個服務會是同一
個類型的資料,像是使用者「註冊身份驗證」服務及使用者「個人隱私設定」服務
<裡面的資料一定是使用者相關的資料,若我們也有文章的服務(PostService),
我們也一定可以確保裡面的驗證資料一定是文章相關的資料。
所以除了服務(Service)層去做資料的驗證外,其他的層級都不需要做任何的資料
驗證!
資料呈現(Presenter)我們會將需要處理不同資料樣式的邏輯,使用laracasts/presenter去做實體
(Entity)的分層處理,不要將有程式邏輯的功能出現在實體(Entity)中
ARCA架構檔案結構
我會將Model的檔案結構依照Domain去區分,檔案結構大概會像這樣
Model模型
149
/app
/KeJyunApp
/User
/Entities
User.php
UserPrivacy.php
/Repositories
UserRepository.php
UserPrivacyRepository.php
/Service
UserAuthService.php
UserPrivacyService.php
/Form
UserForm.php
UserPrivacyForm.php
/Presenter
UserPresenter.php
UserPrivacyPresenter.php
/Post
/Entities
Post.php
/Repositories
/Service
/Form
/Presenter
這樣區分的好處是,類似功能的程式可以方便集中管理,當我們在撰寫某一功能的
程式,我們可以很快地在同一個資料夾中找到這些檔案,若要找其他功能的程式
時,也可以在同一個資料夾很快地去找到
如果我們將程式檔案依照功能去放置,可能會像這樣
Model模型
150
/app
/SomeApp
/Entities
User.php
Post.php
Tags.php
News.php
Event.php
...
/Repositories
/Service
UserAuthService.php
UserPrivacyService.php
UserStatisticService.php
PostManageService.php
PostRankService.php
PostStatisticService.php
TagsService.php
NewsService.php
EventService.php
...
/Form
/Presenter
當專案還小,只有少數幾個模型資料需要管理時,還沒有什麼大的問題,但是當我
們撰寫很多複雜功能時,這樣檔案管理的方式會是個很大的夢靨,像是服務
(Service)與資源庫(Repository)的關係是「多對1」的關係,所以服務
(Services)資料夾的檔案可能有40~50個以上,在我們要找相關的檔案時,就很
考驗我們的眼力了(工程師的眼睛是很珍貴的,我們要好好的珍惜~)
參考資料
在Laravel4使用資源庫(Repositories)及服務(Services)去降低程式的耦合
性
胖胖Model減重的五個方法byhowtomakeaturnPHP也有Day#16-胖胖Model減重的五個方法by尤川豪
Model模型
151
Model模型
152
架構設計準則
傳統的MVC(Model,View,Controller)框架,當Controller收到請求之後,我們
會在Controller內直接透過Model去撈取資料庫的資料,並在Controller做資料驗
證、整合、快取、商業邏輯判斷...等等的工作。
當系統越來越大,會發現很多類似的商業邏輯的程式都散在各地,沒有辦法重複再
利用,當程式需要異動或修改的時候,就要去搜尋所有程式碼,把許多相同商業邏
輯的程式碼去做異動,但需要修改的地方若太多,往往會東漏西漏,導致系統出現
錯誤,並造成往後開發的時間成本增加。
所以我們會想要做到 減少重複的程式碼、 提高維護開發的效率,所以將程式碼依
照 分類及 分層抽出獨立控管,讓不同類型的程式專心處理自己相關的商業邏
輯,讓開發維護更容易。
隨著程式架構的演進會發展出更多不同的架構,所以這個設計架構準則也是會隨著
時間做演進的。
資料層級需求分析
Service(服務)
我們原本在Controller處理請求時,會針對使用者的請求,做不同商業邏輯的處
理,而同樣的商業邏輯可能會被不同的Controller存取,為了讓同個商業邏輯程式
能夠重複使用,所以我會分出一層 Service(服務),將這些商業邏輯放在裡
面,供不同的Controller存取。
在 Service方法中,會針對目前商業邏輯的資料進行蒐集與彙整,處理過後再回
傳給 Controller
Repository(資源庫)
在不同 Service可能會需要對資料庫撈取同樣的資料,為了避免撈取資料的邏輯
重複出現在不同的地方,我們會分出一層 Repository(資源庫),將同樣撈取
Model(模型)資料的邏輯都寫在一起,供不同的 Service存取。
架構設計準則
153
在 Repository中,會在function名稱中指出這個方法是要撈什麼樣的資料,這
樣方法可以重用,也可以讓程式有可讀性,不需要再去看 Modelsql條件的邏
輯,判斷是在做什麼樣的處理
e.g.PostRepository->getWeekTopPosts();//取得本週熱門文章
Model(模型)
在Model中我們僅會寫對資料表對Eloquent相關的設定,像是 primaryKey(主鍵名稱)或 table(資料表名稱),讓Model能夠越乾淨越好,將一些撈取資料的
邏輯都往 Repository集中整理。
Presenter(資料呈現)
在Model我們有一些資料可能會想要有不同的呈現方式,像是文章發布時間
created_at我們可能會想要呈現出像是 5秒前、 15分鐘前、 6小時前這樣的資料,這些資料因為是隨著時間不同而去動態變化,所以需要用程式去計算與
created_at的時間差,再去看要如何呈現這筆資料。
為了能夠讓這些不同資料呈現方式的邏輯能夠重複使用,且不污染 Model的程
式,所以我會分出一層 Presenter(資料呈現)去輔助 Model資料的呈現
e.g.Post->presenter()->created_at_human_time;
laracasts/Presenter
Cache(快取)
在使用 Repositoy去對 Model做存取得時候,如果我們資料沒有做異動,我
們會將資料存放在快取中,直接讀取快取的資料,而減少對資料庫的存取,提高資
料的存取效率
而我們需要對 資料快取的設定、清除及 快取鍵值去做管理,快取像是協助
Repository做資料的存取,所以我會分出一層 Cache(快取)去輔助
Repository做快取的處理
Validator(驗證器)
為了讓資料驗證方法能夠重複使用,不需要在不同的Controller去驗證相同的東
西,這樣會造成驗證邏輯重複出現,若有需要異動驗證規則時,會難以維護。
架構設計準則
154
而每個商業邏輯需要驗證的資料不同,有些欄位在不同商業邏輯會有必填與非必填
不同的差異,像是在做使用者身份驗證時,若有Email驗證及手機驗證,在Email驗證時,手機欄位為非必填欄位,在做手機驗證時,Email為非必填欄位,但兩者
皆為使用者的資料,無法強制使用者兩個欄位資料皆為必填,但在某些商業頁邏輯
是必要的,所以我沒有選擇將這些驗證的邏輯寫在 Repository或 Model中。
所以我會分出一層 Validator(驗證器)輔助 Service做資料驗證。
Concrete(服務組合)
我們會在 Controller去呼叫各個不同的 Service去做資料的判斷處理,有時
候不同的 Controller會有相同呼叫 Service的邏輯順序及組合的資料,為了
減少重複程式,所以我會分出一層 Concrete(服務組合)去協助 Controller去做不同 Service資料的整合撈取
像是我們在許多 Controller方法中,我們都要使用 $PostService->find($post_id);去找文章資料,並用 $UserService->find($post_user_id);去找文章作者相關資訊,這些在 Controller重複出
現的呼叫 Service邏輯我們就可以寫在 Concrete中去做呼叫
Constant(常數)
我們在做 Model資料的撈取或是條件判斷時,可能會需要用
Post::where('status',1)->get()或 $Post->status==1去做資料的存
取,但像 status為1這種資料對我們是難以做閱讀的,需要知道1代表是什
麼意思才知道此段程式的邏輯。
所以我會分出一層 Constant(常數)輔助做資料的識別,我會將狀態設為可識別
的靜態變數,像是 PostConstant::STATUS_PUBLISH=1;,所以在做程式判斷
時,可以用 Post::where('status',PostConstant::STATUS_PUBLISH)->get()或 $Post->status==PostConstant::STATUS_PUBLISH去做判斷。
在 Constant的變數皆為 靜態變數,所以可以供任何類別去做存取。
Support(支援)
我可能會對一資源的資料做簡單的邏輯判斷,不需要依賴任何的 資料控制結構,
像是 Service、 Repository或 Model,所以我會分出一層 Support(常數)去做共用的輔助方法。
架構設計準則
155
像是我們可以用 PostSupport::getAllPostStatus()去撈取文章的所有狀態,
或者用 GoogleAnalyticSupport::api()去對GA的API做存取。
在 Support的方法皆為 靜態方法,所以可以供任何類別去做存取。
ExceptionCode(例外代碼)
我們在做API的資料存取時,會針對不同的例外狀況回傳不同的 錯誤代碼
(error_code),而同一個錯誤代碼可能在不同的 Controller或 Service被回傳,像是文章找不到我們會回傳錯誤代碼 10000001,為了管理及閱讀性方
便,我會分出一層 ExceptionCode(例外代碼)去做共用的例外代碼管理。
像是找不到文章的代碼我會設為 PostExceptionCode::POST_NOT_FOUND=10000001;,所以在程式中只要看到 PostExceptionCode::POST_NOT_FOUND就可以馬上知道這個是找不到文章的錯誤代碼。
在 ExceptionCode的變數皆為 靜態變數,所以可以供任何類別去做存取。
資料層級結構
資料控制結構
Controller(控制器)Concrete(服務組合)
Service(服務)Repository(資源庫)
Model(模型)Presenter(資料呈現)
Cache(快取)Validator(驗證器)
獨立結構
Constant(常數)Support(支援)ExceptionCode(例外代碼)
架構設計準則
156
架構圖
結構說明
資料控制結構
架構設計準則
157
結構名稱 說明
Controller(控制器)
控制要使用哪些Service或Concrete的商業邏輯,去組合出使用者請求需要的資料,並做資料的資料交易控制(transaction)
Service(服務)
做資料的驗證,並組合不同的Repository資料成商業邏輯,供Controller或Concrete存取
Repository(資源庫)
撈取屬於自己Model不同條件下的資料,並做快取控制,供Service存取
Model(模型) 資料表存取相關設定
Presenter(資料呈現) 協助Model做資料呈現處理
Cache(快取) 協助Repository做快取資料的控制
Validator(驗證器) 協助Service做資料驗證
Concrete(服務組合) 協助Controller組合不同Service的資料成商業邏輯
獨立結構
結構名稱 說明
Constant(常數) 共用變數名稱設定
Support(支援) 共用方法
ExceptionCode(例外代碼) 共用例外代碼設定
架構使用原則
資料控制結構
存取限制
不能跨2階層以上存取
Controller不能存取RepositoryService不能存取Model
架構設計準則
158
低階層的不能存取高階層的資料
Model不能存取RepositoryRepository不能存取Service
同一個資料類型,不能互相呼叫
避免同一類型類別呼叫,造成new物件的時候有無窮迴圈
PostService存取UserService,UserService存取PostsService造成無窮迴圈
Concrete不能呼叫ConcreteService不能呼叫ServiceValidator不能呼叫ValidatorRepository不能呼叫RepositoryCache不能呼叫Cache
結構名稱 可存取 可被存取
Controller(控制器) Concrete、Service、DBtrancsction -
Concrete(服務組合) Service Controller
Service(服務) Repository、Validator Controller、Concrete
Validator(驗證器) - Service
Repository(資源庫) 自己的Model、自己的Cache Service
Cache(服務) - Repository
Model(模型) 自己的Presenter Repository
Presenter(資料呈現) - Model
View(視圖)使用限制
View的職責是負責顯示資料,所有的資料應由 Controller準備好再傳給
View,所以 不要在 View內有複雜的程式判斷邏輯,在 View裡面只有
if, for, foreach跟 echo列印的程式,僅需要將資料呈現在對的HTML裡面,不要再對資料重複處理過。
架構設計準則
159
像是文章的網址可能會因為類型不同會有不同的網址,像是一般文章網址可能為
http://kejyun.com/post/1,而影音文章網址可能為
http://kejyun.com/video/2,兩者的資料皆為Post資料表的資料,在
View中要顯示網址應為 echo$Post->post_url;將網址印出, post_url
則是在傳給 View之前就經過邏輯判斷的資料,而不是在 View中判斷不同文
章類型( PostConstant::POST_TYPE_NORMAL,PostConstant::POST_TYPE_VIDEO)在View中顯示不同的網址資料。
之後若文章網址邏輯需要修改,則需要到各個View中去修改,很容易漏改道造成
系統程式出錯
<ahref="{{$Post->post_url}}">{{$Post->Title}}</a>
結構範例說明
Controller(控制器)可以存取結構: Concrete、 Service、 DBtrancsction
可以被存取結構:無
處理HTTP請求的入口,依照需求呼叫Concrete或Service去做資料的存取,大
部分情況呼叫Service去組合需要的資料就好,若相同的組合邏輯在不同的
Controller都有用到,那就使用Concrete去組合不同的Service
要確保所有Service商業邏輯都正確跑完才允許對資料做異動,並避免Transaction在Controller及Service被重複呼叫,導致無法正確鎖定資料狀態,所以使用
Controller當作資料交易(Transaction)的控制點
classPostControllerextendsController
{
publicfunction__construct(
PostConcrete$PostConcrete,
CommentService$CommentService
)
{
$this->PostConcrete=$PostConcrete;
$this->PostService=$PostService;
架構設計準則
160
$this->CommentService=$CommentService;
}
//顯示文章
publicfunctionshow($post_id){
try{
//撈取文章
$Post=$this->PostConcrete->findPost($post_id);
//撈取文章留言
$Comment=$this->CommentService->getCommentByPostId
(post_id);
}catch(Exception$exception){
throw$exception
}
}
//更新文章
publicfunctionupdate($post_id){
try{
//交易開始
DB::beginTransaction();
//更新文章
$Post=$this->PostService->update($post_id,$update
_data);
//交易結束
DB::commit();
}catch(Exception$exception){
//交易失敗
DB::rollBack();
throw$exception
}
}
}
Concrete(服務組合)
可以存取結構: Service
可以被存取結構: Controller
架構設計準則
161
使用不同Service撈取資料,將不同資料組合成商業邏輯,供Controller做存取
classPostConcrete{
publicfunction__construct(
PostService$PostService,
UserService$UserService
)
{
$this->PostService=$PostService;
$this->UserService=$UserService;
}
//撈取文章資料
publicfunctionfindPost($post_id){
try{
//撈取文章
$Post=$this->PostService->findPost($post_id);
//撈取文章作者資料
$user_id=$Post->user_id;
$Post->user=$this->UserService->findUser($user_id)
;
return$Post;
}catch(Exception$exception){
throw$exception
}
}
}
Service(服務)可以存取結構: Validator、 Repository
可以被存取結構: Controller、 Concrete
透過Validator驗證傳入的資料,並使用不同的Repository撈取資料,將不同資料
組合成商業邏輯使用Repository的Cache清除Service中撈取資料的快取
classPostService{
架構設計準則
162
publicfunction__construct(
PostRepository$PostRepository,
PostTagRepository$PostTagRepository,
PostValidator$PostValidator
)
{
$this->PostRepository=$PostRepository;
$this->PostTagRepository=$PostTagRepository;
$this->PostValidator=$PostValidator;
}
//撈取文章
publicfunctionfindPost($post_id){
try{
//驗證傳入資料是否正確
$input=[
'post_id'=>$post_id
];
$this->PostValidator->validateFindPost($input);
//撈取文章
$Post=$this->PostRepository->find($post_id);
//撈取文章標籤
$Post->tag=$this->PostTagRepository->getByPostId($
post_id);
return$Post;
}catch(Exception$exception){
throw$exception
}
}
publicfunctionclearPostCache($post_id){
try{
//驗證傳入資料是否正確
$input=[
'post_id'=>$post_id
];
$this->PostValidator->validateClearPostCache($input)
;
$Post=$this->findPost($post_id);
架構設計準則
163
//清除文章快取
$this->PostRepository->cache()->clearPostCache($Post
);
//清除文章標籤快取
$this->PostTagCache->clearPostTagCache($Post->tag);
}catch(Exception$exception){
throw$exception
}
}
}
Validator(驗證器)
可以存取結構:無
可以被存取結構: Service
協助Service驗證資料的正確性,若驗證錯誤則丟處例外,Controller根據例外代
碼去做處理
classPostValidator{
publicfunctionvalidatefindPost($input){
//驗證找尋文章資料是否正確
thrownewException(
'文章編號格式錯誤',
PostExceptionCode::POST_ID_FORMAT_ERROR
);
}
}
Repository(資源庫)
可以存取結構:自己的Model、自己的Cache
可以被存取結構: Service
僅能撈取屬於自己的Model資料,像 PostRepository僅能存取 PostModel(模型)的資料,並使用不同條件撈取Model的資料,供Service做存取
架構設計準則
164
classPostRepository{
publicfunction__construct(
Post$Post,
PostCache$PostCache
)
{
$this->Post=$Post;
$this->PostCache=$PostCache;
}
publicfunctioncache(){
return$this->PostCache;
}
publicfunctionfind($post_id){
try{
$cache_key=$this->cache()->getPostCacheKey($post_i
d);
if(Cache::has($cache_key)){
//有快取資料
$Post=Cache::get($cache_key);
return$Post;
}
//撈取資料庫文章資料
$Post=$this->Post->find($post_id);
if(!is_null($Post)){
//紀錄快取
$this->cache()->putPost($Post);
}
return$Post;
}catch(Exception$exception){
throw$exception
}
}
publicfunctionfindLatestPost(){
try{
架構設計準則
165
$cache_key=$this->cache()->getLatestPostCacheKey($
post_id);
if(Cache::has($cache_key)){
//有快取資料
$$Posts=Cache::get($cache_key);
return$Post;
}
//撈取資料庫文章資料
$Post=$this->Post
->order('created_at','desc')
->first();
if(!is_null($Post)){
//紀錄快取
$this->cache()->putLatestPost($Post);
}
return$Post;
}catch(Exception$exception){
throw$exception
}
}
}
Cache(快取)
可以存取結構:無
可以被存取結構: Repository
協助Repository做快取資料的控制,快取鍵值的管理、資料讀取及清除
架構設計準則
166
classPostCache{
publicfunctiongetPostCacheKey($post_id){
//撈取文章快取鍵值
}
publicfunctionputPost(Post$Post){
//紀錄文章快取
}
publicfunctionclearPostCache(Post$Post){
//清除文章快取
}
}
Model(模型)可以存取結構:無
可以被存取結構: Repository
Eloquent存取資料表相關設定,使用Eloquent直接存取資料表資料
classPostextendsModel
{
protected$table='post';
protected$fillable=[];
protected$primaryKey='id';
protected$dates=['created_at','updated_at'];
protected$presenter='PostPresenter';
}
Presenter(資料呈現)
架構設計準則
167
可以存取結構:無
可以被存取結構: Model
提供Model的資料用其他方式呈現
classPostPresenterextendsPresenter
{
publicfunctioncreated_at_human_time()
{
return$this->created_at->diffForHumans();
}
}
Constant(常數)可以存取結構:無
可以被存取結構:無限制
資料皆為靜態變數,可以供所有資料層級(e.g.Controller、Service、Repository)做存取
classPostConstant{
constPOST_TYPE_PUBLIC='P';
constPOST_TYPE_DELETE='D';
}
Support(支援)可以存取結構:無
可以被存取結構:無限制
方法皆為靜態變數,可以供所有資料層級(e.g.Controller、Service、Repository)做存取
若有其他可供全域共用的方法皆寫在Support靜態方法供大家存取
架構設計準則
168
classPostSupport{
//撈取所有文章類型
publicstaticfunctiongetAllPostType(){
$all_post_type=[
PostConstant::POST_TYPE_PUBLIC,
PostConstant::POST_TYPE_DELETE,
];
return$all_post_type;
}
}
ExceptionCode(例外代碼)可以存取結構:無
可以被存取結構:無限制
資料皆為靜態變數,可以供所有資料層級(e.g.Controller、Service、Repository)做存取
classPostExceptionCode{
constPOST_ID_FORMAT_ERROR=10000001;
constPOST_NOT_FOUND=10000002;
constPOST_TAG_NOT_FOUND=10000003;
}
架構設計準則
169
學習資源
文章
框架不應該有「MODELS」資料夾
投影片
胖胖Model減重的五個方法byhowtomakeaturn
影片
PHP也有Day#16-胖胖Model減重的五個方法by尤川豪
學習資源
170
套件
這裏會介紹一些Laravel5一些常用的套件
套件
171
Debug套件
這裏會介紹一些在Laravel5會用到的Debug套件
Debug
172
Artisantail在Laravel4我們常常用 phpartisantail去即時顯示系統的Log,以便我們
進行除錯,但在Laravel5預設是將這個套件拿到的,所以如果我們要繼續使用的
話,必須要手動的將套件加回去。
安裝Laraveltail套件
首先在命令列執行 composerrequirespatie/laravel-tail安裝tail套件
$composerrequirespatie/laravel-tail
接著在 config/app.php檔案中的 providers加入tailServiceProvider
//config/app.php
'providers'=>[
...
'Spatie\Tail\TailServiceProvider',
...
];
安裝完畢之後就可以繼續使用 phpartisantail去觀看你的Log了!
$phpartisantail
tail遠端主機Log檔tail命令可以讓我們即時觀看遠端的Log檔案,我們只要將遠端主機的相關設定加
入即可,可以使用下列指令建立 config/tail.php檔案
$phpartisanvendor:publish--provider="Spatie\Tail\TailService
Provider"
Artisantail
173
之後只要在tail指令後面加入要觀看的主機名稱,即可立即觀看該主機的Log檔摟
$phpartisantailproduction
參考資料
ThemissingtailcommandforLaravel5
Artisantail
174
工具套件
這裏會介紹一些Laravel5常用的一些套件工具
工具
175
Carbon時間套件
Carbon是一個很方便的轉換時間的工具,可以很方便地將時間進行轉換,取得我
們想要的特定日期或格式
安裝
Laravel5預設就會安裝Carbon套件,若沒有安裝的話可以透過下列的方式進行安
裝:
#使用Composer下載Carbon
$composerrequirenesbot/carbon
<?php
//載入composerautoload檔案
require'vendor/autoload.php';
//使用Carbon類別
useCarbon\Carbon;
printf("Now:%s",Carbon::now());
快速切換前後日期
Carbon
176
<?php
useCarbon\Carbon;
$now=Carbon::now();
echo$now;//2015-03-2600:36:47
$today=Carbon::today();
echo$today;//2015-03-2600:00:00
$tomorrow=Carbon::tomorrow('Europe/London');
echo$tomorrow;//2015-03-2700:00:00
$yesterday=Carbon::yesterday();
echo$yesterday;//2015-03-2500:00:00
建立特定日期的時間
Carbon
177
<?php
useCarbon\Carbon;
$timezone='Asia/Taipei';
//從「年月日」建立
Carbon::createFromDate($year,$month,$day,$timezone);
//從「時分秒」建立
Carbon::createFromTime($hour,$minute,$second,$timezone);
//從完整的「年月日時分秒」建立
Carbon::create($year,$month,$day,$hour,$minute,$second,$ti
mezone);
//從指定的格式建立
Carbon::createFromFormat($format,$time,$tz);
echoCarbon::createFromFormat('Y-m-dH','1975-05-2122')->toDat
eTimeString();//1975-05-2122:00:00
//從時間戳記建立
echoCarbon::createFromTimeStamp(-1)->toDateTimeString();
//1969-12-3118:59:59
echoCarbon::createFromTimeStamp(-1,'Europe/London')->toDateTim
eString();//1970-01-0100:59:59
echoCarbon::createFromTimeStampUTC(-1)->toDateTimeString();
//1969-12-3123:59:59
轉換日期
Carbon
178
<?php
useCarbon\Carbon;
//透過文字移動日期
$knownDate=Carbon::create(2001,5,21,12);//create
testingdate
Carbon::setTestNow($knownDate);//setth
emock
echonewCarbon('tomorrow');//2001-0
5-2200:00:00...noticethetime!
echonewCarbon('yesterday');//2001-0
5-2000:00:00
echonewCarbon('nextwednesday');//2001-0
5-2300:00:00
echonewCarbon('lastfriday');//2001-0
5-1800:00:00
echonewCarbon('thisthursday');//2001-0
5-2400:00:00
取得日期資料
<?php
useCarbon\Carbon;
$dt=Carbon::parse('2012-9-523:26:11.123789');
//取的指定時間資料的資訊(整數)
var_dump($dt->year);//
int(2012)
var_dump($dt->month);//
int(9)
var_dump($dt->day);//
int(5)
var_dump($dt->hour);//
int(23)
var_dump($dt->minute);//
Carbon
179
int(26)
var_dump($dt->second);//
int(11)
var_dump($dt->micro);//
int(123789)
var_dump($dt->dayOfWeek);//
int(3)
var_dump($dt->dayOfYear);//
int(248)
var_dump($dt->weekOfMonth);//
int(1)
var_dump($dt->weekOfYear);//
int(36)
var_dump($dt->daysInMonth);//
int(30)
var_dump($dt->timestamp);//
int(1346901971)
var_dump(Carbon::createFromDate(1975,5,21)->age);//
int(39)calculatedvsnowinthesametz
var_dump($dt->quarter);//
int(3)
//回傳與UTC差異的秒數
var_dump(Carbon::createFromTimestampUTC(0)->offset);//
int(0)
var_dump(Carbon::createFromTimestamp(0)->offset);//
int(-18000)
//回傳與UTC差異的時數
var_dump(Carbon::createFromTimestamp(0)->offsetHours);//
int(-5)
//找出當天日否有日光節約時間
var_dump(Carbon::createFromDate(2012,1,1)->dst);//
bool(false)
var_dump(Carbon::createFromDate(2012,9,1)->dst);//
bool(true)
//判斷指定的的時區是否與預設的時區相同
Carbon
180
var_dump(Carbon::now()->local);//
bool(true)
var_dump(Carbon::now('America/Vancouver')->local);//
bool(false)
//判斷是否為UTC的時區時間
var_dump(Carbon::now()->utc);//
bool(false)
var_dump(Carbon::now('Europe/London')->utc);//
bool(true)
var_dump(Carbon::createFromTimestampUTC(0)->utc);//
bool(true)
//取得時區實例
echoget_class(Carbon::now()->timezone);//
DateTimeZone
echoget_class(Carbon::now()->tz);//
DateTimeZone
//取得時區實例的名稱
echoCarbon::now()->timezoneName;//
America/Toronto
echoCarbon::now()->tzName;//
America/Toronto
設定時間資料
Carbon
181
<?php
useCarbon\Carbon;
$dt=Carbon::now();
$dt->year=1975;
$dt->month=13;//年份會強制+1,且月份變為1月
$dt->month=5;
$dt->day=21;
$dt->hour=22;
$dt->minute=32;
$dt->second=5;
$dt->timestamp=169957925;//這個設定不會變更時區
//透過字串或是DateTimeZone實例去設定時區
$dt->timezone=newDateTimeZone('Europe/London');
$dt->timezone='Europe/London';
$dt->tz='Europe/London';
//鏈結設定方式
$dt->year(1975)->month(5)->day(21)->hour(22)->minute(32)->second(
5)->toDateTimeString();
$dt->setDate(1975,5,21)->setTime(22,32,5)->toDateTimeString(
);
$dt->setDateTime(1975,5,21,22,32,5)->toDateTimeString();
$dt->timestamp(169957925)->timezone('Europe/London');
$dt->tz('America/Toronto')->setTimezone('America/Vancouver');
格式化時間資料
Carbon
182
<?php
useCarbon\Carbon;
$dt=Carbon::create(1975,12,25,14,15,16);
var_dump($dt->toDateTimeString()==$dt);//bool(true)
=>uses__toString()
echo$dt->toDateString();//1975-12-25
echo$dt->toFormattedDateString();//Dec25,19
75
echo$dt->toTimeString();//14:15:16
echo$dt->toDateTimeString();//1975-12-25
14:15:16
echo$dt->toDayDateTimeString();//Thu,Dec2
5,19752:15PM
//仍可以使用format()函式
echo$dt->format('ljS\\ofFYh:i:sA');//Thursday2
5thofDecember197502:15:16PM
//常用的時間格式
echo$dt->toAtomString();//in1Jahr
echo$dt->toCookieString();//Thursday,25-Dec-197514:15:16
EST
echo$dt->toIso8601String();//1975-12-25T14:15:16-0500
echo$dt->toRfc822String();//Thu,25Dec7514:15:16-0500
echo$dt->toRfc850String();//Thursday,25-Dec-7514:15:16E
ST
echo$dt->toRfc1036String();//Thu,25Dec7514:15:16-0500
echo$dt->toRfc1123String();//Thu,25Dec197514:15:16-0500
echo$dt->toRfc2822String();//Thu,25Dec197514:15:16-0500
echo$dt->toRfc3339String();//1975-12-25T14:15:16-05:00
echo$dt->toRssString();//Thu,25Dec197514:15:16-0500
echo$dt->toW3cString();//1975-12-25T14:15:16-05:00
Carbon
183
比較時間差異
<?php
useCarbon\Carbon;
echoCarbon::now()->tzName;//America/To
ronto
$first=Carbon::create(2012,9,5,23,26,11);
$second=Carbon::create(2012,9,5,20,26,11,'America/Vancou
ver');
echo$first->toDateTimeString();//2012-09-05
23:26:11
echo$first->tzName;//America/To
ronto
echo$second->toDateTimeString();//2012-09-05
20:26:11
echo$second->tzName;//America/Va
ncouver
//大於、等於、小於
var_dump($first->eq($second));//bool(true)
var_dump($first->ne($second));//bool(false)
var_dump($first->gt($second));//bool(false)
var_dump($first->gte($second));//bool(true)
var_dump($first->lt($second));//bool(false)
var_dump($first->lte($second));//bool(true)
$first->setDateTime(2012,1,1,0,0,0);
$second->setDateTime(2012,1,1,0,0,0);//Remembert
zis'America/Vancouver'
var_dump($first->eq($second));//bool(false)
var_dump($first->ne($second));//bool(true)
Carbon
184
var_dump($first->gt($second));//bool(false)
var_dump($first->gte($second));//bool(false)
var_dump($first->lt($second));//bool(true)
var_dump($first->lte($second));//bool(true)
//時間區間比較
$first=Carbon::create(2012,9,5,1);
$second=Carbon::create(2012,9,5,5);
var_dump(Carbon::create(2012,9,5,3)->between($first,$second)
);//bool(true)
var_dump(Carbon::create(2012,9,5,5)->between($first,$second)
);//bool(true)
var_dump(Carbon::create(2012,9,5,5)->between($first,$second,
false));//bool(false)
//時間大小比較
$dt1=Carbon::create(2012,1,1,0,0,0);
$dt2=Carbon::create(2014,1,30,0,0,0);
echo$dt1->min($dt2);//2012-01-01
00:00:00
$dt1=Carbon::create(2012,1,1,0,0,0);
$dt2=Carbon::create(2014,1,30,0,0,0);
echo$dt1->max($dt2);//2014-01-30
00:00:00
//nowisthedefaultparam
$dt1=Carbon::create(2000,1,1,0,0,0);
echo$dt1->max();
//時間差異運算
echoCarbon::now('America/Vancouver')->diffInSeconds(Carbon::now(
'Europe/London'));//0
Carbon
185
$dtOttawa=Carbon::createFromDate(2000,1,1,'America/Toronto'
);
$dtVancouver=Carbon::createFromDate(2000,1,1,'America/Vanco
uver');
echo$dtOttawa->diffInHours($dtVancouver);
//3
echo$dtOttawa->diffInHours($dtVancouver,false);
//3
echo$dtVancouver->diffInHours($dtOttawa,false);
//-3
$dt=Carbon::create(2012,1,31,0);
echo$dt->diffInDays($dt->copy()->addMonth());
//31
echo$dt->diffInDays($dt->copy()->subMonth(),false);
//-31
$dt=Carbon::create(2012,4,30,0);
echo$dt->diffInDays($dt->copy()->addMonth());
//30
echo$dt->diffInDays($dt->copy()->addWeek());
//7
$dt=Carbon::create(2012,1,1,0);
echo$dt->diffInMinutes($dt->copy()->addSeconds(59));
//0
echo$dt->diffInMinutes($dt->copy()->addSeconds(60));
//1
echo$dt->diffInMinutes($dt->copy()->addSeconds(119));
//1
echo$dt->diffInMinutes($dt->copy()->addSeconds(120));
//2
echo$dt->addSeconds(120)->secondsSinceMidnight();
//120
時間狀態
Carbon
186
<?php
useCarbon\Carbon;
$dt=Carbon::now();
$dt->isWeekday();
$dt->isWeekend();
$dt->isYesterday();
$dt->isToday();
$dt->isTomorrow();
$dt->isFuture();
$dt->isPast();
$dt->isLeapYear();
$dt->isSameDay(Carbon::now());
$born=Carbon::createFromDate(1987,4,23);
$noCake=Carbon::createFromDate(2014,9,26);
$yesCake=Carbon::createFromDate(2014,4,23);
var_dump($born->isBirthday($noCake));//bool(false
)
var_dump($born->isBirthday($yesCake));//bool(true)
時間運算
<?php
useCarbon\Carbon;
$dt=Carbon::create(2012,1,31,0);
echo$dt->toDateTimeString();//2012-01-3100:00:00
echo$dt->addYears(5);//2017-01-3100:00:00
echo$dt->addYear();//2018-01-3100:00:00
echo$dt->subYear();//2017-01-3100:00:00
echo$dt->subYears(5);//2012-01-3100:00:00
echo$dt->addMonths(60);//2017-01-3100:00:00
Carbon
187
echo$dt->addMonth();//2017-03-0300:00:00
equivalentof$dt->month($dt->month+1);soitwraps
echo$dt->subMonth();//2017-02-0300:00:00
echo$dt->subMonths(60);//2012-02-0300:00:00
echo$dt->addDays(29);//2012-03-0300:00:00
echo$dt->addDay();//2012-03-0400:00:00
echo$dt->subDay();//2012-03-0300:00:00
echo$dt->subDays(29);//2012-02-0300:00:00
echo$dt->addWeekdays(4);//2012-02-0900:00:00
echo$dt->addWeekday();//2012-02-1000:00:00
echo$dt->subWeekday();//2012-02-0900:00:00
echo$dt->subWeekdays(4);//2012-02-0300:00:00
echo$dt->addWeeks(3);//2012-02-2400:00:00
echo$dt->addWeek();//2012-03-0200:00:00
echo$dt->subWeek();//2012-02-2400:00:00
echo$dt->subWeeks(3);//2012-02-0300:00:00
echo$dt->addHours(24);//2012-02-0400:00:00
echo$dt->addHour();//2012-02-0401:00:00
echo$dt->subHour();//2012-02-0400:00:00
echo$dt->subHours(24);//2012-02-0300:00:00
echo$dt->addMinutes(61);//2012-02-0301:01:00
echo$dt->addMinute();//2012-02-0301:02:00
echo$dt->subMinute();//2012-02-0301:01:00
echo$dt->subMinutes(61);//2012-02-0300:00:00
echo$dt->addSeconds(61);//2012-02-0300:01:01
echo$dt->addSecond();//2012-02-0300:01:02
echo$dt->subSecond();//2012-02-0300:01:01
echo$dt->subSeconds(61);//2012-02-0300:00:00
人類閱讀時間格式
Carbon
188
<?php
useCarbon\Carbon;
//通常會用在留言的時間顯示
//該時間會比較與現在的時間的差異
echoCarbon::now()->subDays(5)->diffForHumans();/
/5daysago
echoCarbon::now()->diffForHumans(Carbon::now()->subYear());/
/1yearafter
$dt=Carbon::createFromDate(2011,8,1);
echo$dt->diffForHumans($dt->copy()->addMonth());/
/1monthbefore
echo$dt->diffForHumans($dt->copy()->subMonth());/
/1monthafter
echoCarbon::now()->addSeconds(5)->diffForHumans();/
/5secondsfromnow
echoCarbon::now()->subDays(24)->diffForHumans();/
/3weeksago
echoCarbon::now()->subDays(24)->diffForHumans(null,true);/
/3weeks
時間常數
Carbon
189
<?php
useCarbon\Carbon;
var_dump(Carbon::SUNDAY);//int(0)
var_dump(Carbon::MONDAY);//int(1)
var_dump(Carbon::TUESDAY);//int(2)
var_dump(Carbon::WEDNESDAY);//int(3)
var_dump(Carbon::THURSDAY);//int(4)
var_dump(Carbon::FRIDAY);//int(5)
var_dump(Carbon::SATURDAY);//int(6)
var_dump(Carbon::YEARS_PER_CENTURY);//int(100)
var_dump(Carbon::YEARS_PER_DECADE);//int(10)
var_dump(Carbon::MONTHS_PER_YEAR);//int(12)
var_dump(Carbon::WEEKS_PER_YEAR);//int(52)
var_dump(Carbon::DAYS_PER_WEEK);//int(7)
var_dump(Carbon::HOURS_PER_DAY);//int(24)
var_dump(Carbon::MINUTES_PER_HOUR);//int(60)
var_dump(Carbon::SECONDS_PER_MINUTE);//int(60)
參考資料
Carbon-AsimplePHPAPIextensionforDateTime.Carbon-docs
Carbon
190
設計模式套件
這裏會介紹一些Laravel5不錯的設計模式套件
Entity資料顯示
laracasts/presenter
設計模式
191
視圖
解析
yangqi/Htmldom-SimpleHTMLDomParser
最小化
Laravel-HTMLMinHTMLMinifierinLaravel5.*
視圖
193
認證套件
這裏會介紹一些在Laravel5會用到的認證套件
參考資料
10ThingsYouShouldKnowaboutTokensCSRFTokennecessarywhenusingStateless(=Sessionless)Authentication?WheretoStoreYourJWTs-CookiesvsHTML5WebStorageCookiesarebadforyou:Improvingwebapplicationsecurity
認證
194
OAuth2認證套件
使用套件
套件名稱:lucadegasperi/oauth2-server-laravel
套件版本:5.0.3
名詞解釋
名詞 描述
Accesstoken 存取標記,用於存取受保護資源的標記
Authorizationcode
授權碼,使用者授權Client的中介標記,Client可以透過此授權碼去取得Accesstoken
Authorizationserver
授權伺服器,使用者授權Client後,用於發送Accesstoken的伺服器
Client 被授權客戶,存取屬於使用者的受保護資源的應用程式(Application),像是Server、手機或其他裝置
Grant 授權方法,存取Accesstoken的方法
Resourceserver
資源伺服器,屬於使用者的受保護資源(像是文章、照片、個人隱私資料...等等)
Scope 資源存取範圍,Accesstoken允許存取的權限
ref:lucadegasperi/oauth2-server-laravel-Terminology
安裝
使用composer安裝套件
在 composer.json加入 "lucadegasperi/oauth2-server-laravel":"5.0.*"並執行 composerupdate安裝套件
OAuth2
195
/*composer.json*/
"require":{
"lucadegasperi/oauth2-server-laravel":"5.0.*"
}
$composerupdate
設定套件
開啟 config/app.php檔案,並將系列套件資訊加入 providers與 aliases
'providers'=>[
LucaDegasperi\OAuth2Server\Storage\FluentStorageServiceProvi
der::class,
LucaDegasperi\OAuth2Server\OAuth2ServerServiceProvider::clas
s,
],
'aliases'=>[
'Authorizer'=>LucaDegasperi\OAuth2Server\Facades\Authorize
r::class,
]
設定middleware開啟 app/Http/Kernel.php,將系列設定加入$middleware與$routeMiddleware中
並將原本在$middleware的\App\Http\Middleware\VerifyCsrfToken::class移至$routeMiddleware,並命名為csrf
注意,原本Laravel會針對每個非Get的Request做CSRF的過濾,因為
OAuth已經取得授權Accesstoken,不需要CSRF驗證,所以如果你需要做
CSRF驗證的話,您可以在Route加入 csrf的middleware去做驗證
OAuth2
196
<?php
classKernelextendsHttpKernel
{
protected$middleware=[
\LucaDegasperi\OAuth2Server\Middleware\OAuthExceptionHan
dlerMiddleware::class
];
protected$routeMiddleware=[
//CSRF
'csrf'=>\App\Http\Middleware\VerifyCsrfToken::class,
//OAuth2
'oauth'=>\LucaDegasperi\OAuth2Server\Middleware\OAuthM
iddleware::class,
'oauth-user'=>\LucaDegasperi\OAuth2Server\Middleware\O
AuthUserOwnerMiddleware::class,
'oauth-client'=>\LucaDegasperi\OAuth2Server\Middleware
\OAuthClientOwnerMiddleware::class,
'check-authorization-params'=>\LucaDegasperi\OAuth2Ser
ver\Middleware\CheckAuthCodeRequestMiddleware::class
];
}
產生套件設定及Migration執行 phpartisanvendor:publish複製套件的 migration與config/oauth.php設定檔到你應用程式的目錄,並執行migration建立OAuth2需要的資料表
$phpartisanvendor:publish
$phpartisanmigrate
設定檔名詞解釋
OAuth2
197
參數名稱 說明資料類
型預設值
grant_types 授權類型設定 Array 無
token_type token類型 String League\OAuth2\Server\TokenType\Bearer
state_param
狀態參數,如果為true的話,在請求Accesstoken時候則必須帶入 &state=隨機字串,在透過授權後Authorizationserver會回覆相同的state字串
Boolean false
scope_param
存取權限參數,若為true,則在每一次的Request都需要帶入該資源的存取參數
Boolean false
scope_delimiter
存取權限區隔字串,在不同的存取權限使用什麼字元去區隔(scope1,scope2)
String ,
default_scope 預設存取權限 String null
access_token_ttl Accesstoken存活時間(單位:秒)
Integer 3600
limit_clients_to_grants是否限制Client允許使用的GrantType,可以在oauth_client_grants
資料表設定
Boolean false
limit_clients_to_scopes限制Client允許的Scope,可以在oauth_client_scopes
資料表設定
Boolean false
limit_scopes_to_grants限制Scope允許在哪個GrantType存取,可以在oauth_grant_scopes
資料表設定
Boolean false
http_headers_only 是否只使用Header做Accesstoken的檢查
Boolean false
OAuth2Accesstoken取得的方式
OAuth2
198
OAuth2總共有下列4種Accesstoken取得的方式
Accesstoken取得
方式說明
ClientCredentials
純Client資料身份驗證,僅需要client_id與client_secret正確即可取得Accesstoken
Password 資源擁有者須登入帳號密碼,確認可以讓Client取得Accesstoken
AuthCodeGrant
資源擁有者授權Client可以存取指定Scope的資源並給予授權碼,Client透過此授權碼取得該Scope的Accesstoken
RefreshTokenGrant
授權Client在取得Accesstoken時也一併取得Refreshtoken,在Accesstoken過期時,Client可以用此Refreshtoken取得新的相同權限的Accesstoken
設定存取Accesstoken路由
不管是何種OAuth2Accesstoken取得的方式,都須透過 相同的路由去取得
Accesstoken,OAuth2會根據每個取得方式參數的不同做不一樣的驗證,所以我
們只有設定下列路由即可
Route::post('oauth/access_token',function(){
returnResponse::json(Authorizer::issueAccessToken());
});
設定client_id與client_secret每一個Client應用程式都會有自己的 client_id與 client_secret,你可以
自己設定你要核發給Client授權client_id與client_secret的頁面,並將資料分別
存放在 oauth_clients資料表的id與secret欄位中
OAuth2
199
Schema::create('oauth_clients',function(BluePrint$table){
$table->string('id',40)->primary();//client_id
$table->string('secret',40);//client_secret
$table->string('name');//Client名稱
$table->timestamps();
$table->unique(['id','secret']);
});
Client資料新增完成之後,之後Client可以透過這個client_id與client_secret去要
求取得Accesstoken,我們這裡以新增id為 KeKyun及secret為KeJyunSecret作為之後的範例
INSERTINTO"oauth_clients"("id","secret","name","created_at"
,"updated_at")
VALUES('KeJyun','KeJyunSecret','KeJyunClient',now(),now())
;
client_id與client_secret欄位長度皆為40,在產生資料時請自行產生長度40的亂數字串,這裡僅用於示範使用非亂數的client_id與client_secret
參考資料
lucadegasperi/oauth2-server-laravelOAuth2.0筆記(1)世界觀
OAuth2.0筆記(2)Client的註冊與認證
OAuth2.0筆記(3)Endpoints的規格
OAuth2.0筆記(4.1)AuthorizationCodeGrantFlow細節
OAuth2.0筆記(4.2)ImplicitGrantFlow細節
OAuth2.0筆記(4.3)ResourceOwnerPasswordCredentialsGrantFlow細節
OAuth2.0筆記(4.4)ClientCredentialsGrantFlow細節
OAuth2.0筆記(5)核發與換發AccessTokenOAuth2.0筆記(6)BearerToken的使用方法
OAuth2.0筆記(7)安全性問題
各大網站OAuth2.0實作差異
IntroductiontoauthenticationwithOAuth2
OAuth2
200
Laravel4OAuth2TestingOAuth2inLaravel5
OAuth2
201
OAuth2ClientCredentials
設定config在 config/oauth2.php檔案中加入下列設定,並設定你的 token存活時間
(access_token_ttl),單位時間為秒
return[
'grant_types'=>[
'client_credentials'=>[
'class'=>'\League\OAuth2\Server\Grant\ClientCred
entialsGrant',
'access_token_ttl'=>3600
]
]
];
取得Accesstoken在我們取得Accesstoken的資料欄位中填入下列欄位
欄位名稱 資料
grant_types client_credentials
client_id KeJyun
client_secret KeJyunSecret
client_id與 client_secret為在OAuth套件說明頁建立的
OAuth2
202
送出到我們設定的 /oauth/access_token路由後,我們就可以直接取得
access_token,並回傳此token失效的時間 expires_in為我們設定的
access_token_ttl
相關資料表
OAuth2會將token記錄在 oauth_access_tokens資料表,並將關聯的使用者
記錄在 oauth_sessions資料表,在 oauth_sessions中的 owner_id則為
oauth_clients資料表中Client的 id
參考資料
ClientCredentials
OAuth2
203
OAuth2PasswordGrant
設定config在 config/oauth2.php檔案中加入下列設定,並設定你的 token存活時間
(access_token_ttl),單位時間為秒,在 callback設定您要用來驗證使用者
帳號密碼的類別函式,OAuth2會透過這個callback去驗證使用者的帳號密碼
return[
'grant_types'=>[
'password'=>[
'class'=>'\League\OAuth2\Server\Grant\PasswordGran
t',
'callback'=>'\App\PasswordVerifier@verify',
'access_token_ttl'=>3600
],
]
];
建立驗證身份callbackOAuth會將Client傳來的 username及 password欄位的資料透過
callback傳入並驗證,當中驗證的規則跟邏輯你可以自行定義,若驗證失敗回
傳 false即可,驗證成功的話可以將使用者的編號回傳,OAuth會將Accesstoken對應的使用者編號(資源擁有者編號)記錄下來
OAuth2
204
useIlluminate\Support\Facades\Auth;
classPasswordGrantVerifier
{
publicfunctionverify($username,$password)
{
$credentials=[
'email'=>$username,
'password'=>$password,
];
//驗證成功
if(Auth::once($credentials)){
returnAuth::user()->id;
}
//驗證失敗
returnfalse;
}
}
取得Accesstoken在我們取得Accesstoken的資料欄位中填入下列欄位
欄位名稱 資料
grant_types password
username kejyun@gmail.com
password 123456
client_id KeJyun
client_secret KeJyunSecret
client_id與 client_secret為在OAuth套件說明頁建立的
username與 password是你專案的使用者驗證資料,端看你驗證的
callback如何定義這兩個欄位的資料,驗證成功後回傳使用者的編號給
OAuth2記錄即可
OAuth2
205
送出到我們設定的 /oauth/access_token路由後,我們就可以直接取得
access_token,並回傳此token失效的時間 expires_in為我們設定的
access_token_ttl
相關資料表
OAuth2會將token記錄在 oauth_access_tokens資料表,並將關聯的使用者
記錄在 oauth_sessions資料表,在 oauth_sessions中的 owner_id則為
我們剛剛回傳的使用者編號
參考資料
PasswordGrant
OAuth2
206
OAuth2RefreshToken
設定config在 config/oauth2.php檔案中加入下列設定,並設定你的 token存活時間
(access_token_ttl)及 refreshtoken存活時間(refresh_token_ttl)單位
時間為秒
注意,若使用RefreshToken時,必須至少有 PasswordGrant或 AuthCodeGrant的這兩種OAuth2驗證方法其中一種,然後在取得Accesstoken時,會一併回傳refreshtoken
return[
'grant_types'=>[
'refresh_token'=>[
'class'=>'\League\OAuth2\Server\Grant\RefreshToken
Grant',
'access_token_ttl'=>3600,
'refresh_token_ttl'=>36000
]
]
];
使用PasswordGrant取得Accesstoken及RefreshToken在我們取得Accesstoken的資料欄位中填入下列欄位
OAuth2
207
欄位名稱 資料
grant_types password
username kejyun@gmail.com
password 123456
client_id KeJyun
client_secret KeJyunSecret
client_id與 client_secret為在OAuth套件說明頁建立的
username與 password是你專案的使用者驗證資料,端看你驗證的
callback如何定義這兩個欄位的資料,驗證成功後回傳使用者的編號給
OAuth2記錄即可
送出到我們設定的 /oauth/access_token路由後,我們就可以直接取得
access_token,並回傳此token失效的時間 expires_in為我們設定的
access_token_ttl,還有 refresh_token可以讓我們直接更新取得新的可用
token
使用RefreshToken取得的新的Accesstoken權限與先前的Accesstoken會完全相同
OAuth2
208
透過RefreshToken取得新的AccessToken在我們取得Accesstoken的資料欄位中填入下列欄位
欄位名稱 資料
grant_types refresh_token
refresh_token Z9pBcvzzBquBzR01MrfYYWMECgVKlkCxTlkkU9zf
client_id KeJyun
client_secret KeJyunSecret
這樣我們就可以直接透過RefreshToken取得新的可使用者AccessToken,而不
用再透過使用者帳號密碼去取得可使用的AccessToken
相關資料表
OAuth2會將token記錄在 oauth_access_tokens資料表,並將關聯的使用者
記錄在 oauth_sessions資料表,在 oauth_sessions中的 owner_id則為
我們剛剛回傳的使用者編號
並在 oauth_refresh_tokens資料表中記錄RefreshToken是屬於哪一個
AccessToken,並透過這個關聯去產生新的AccessToken與新的RefreshToken
OAuth2
209
參考資料
RefreshToken
OAuth2
210
OAuth2PasswordGrantWithScope延續PasswordGrant的OAuth2設定,我們可以在參數中加入我們要取得的 資源
存取範圍(Scope),讓這個AccessToken僅能有特定資源的存取權限
新增資源存取範圍(Scope)資料
OAuth2定義的Scope皆存在 oauth_scopes資料表中,我們在資料表中新增
user_profile及 user_likes這兩個Scope
INSERTINTO"oauth_scopes"("id","description","created_at","
updated_at")
VALUES('user_profile','profile',now(),now());
INSERTINTO"oauth_scopes"("id","description","created_at","
updated_at")
VALUES('user_likes','likes',now(),now());
取得Accesstoken在我們取得Accesstoken的資料欄位中填入下列欄位
欄位名稱 資料
grant_types password
username kejyun@gmail.com
password 123456
client_id KeJyun
client_secret KeJyunSecret
scope user_profile,user_likes
我們的Scope也可以指填入一個,若要填入多個Scope的話,可以看
config/oauth2.php設定檔中的 scope_delimiter要怎麼區別不同的
Scope資源,預設是用 逗號「,」去區別Scope
OAuth2
211
送出到我們設定的 /oauth/access_token路由後,我們就可以直接取得
access_token,並回傳此token失效的時間 expires_in為我們設定的
access_token_ttl
相關資料表
OAuth2會將token記錄在 oauth_access_tokens資料表,並將關聯的使用者
記錄在 oauth_sessions資料表,而 oauth_access_token_scopes資料表則
存放AccessToken相關的Scope資訊
參考資料
SecuringyourAPIendpoints
OAuth2
212
驗證AccessToken
路由設定OAuthmiddleware在我們想要保護的資源中,我們可以加入 oauth的middleware中介層,這樣
//保護的資源設定oauthmiddleware
Route::get('/user-private-resource',['middleware'=>'oauth',fun
ction()
{
//取得資源擁有者編號,若經過PasswordGrant驗證的Token則為使用
者的編號
$user_id=Authorizer::getResourceOwnerId();
$data=[
'user_id'=>$user_id
];
//回傳受保護的資源
returnResponse::json($data);
}]);
透過AccessToken取的受保護的資源
在我們取得受保護資源的請求 Header中填入,這樣OAuth2就會取得我們傳入
的AccessToken,並驗證我們有沒有存取權限,若有存取權限,則會直接回傳該
受保護的資源
欄位名稱 資料
Authorization BearerrXXoWMg5UXzQpxAynCnYYOQiZQ0xDcAjT0ywGvke
OAuth2
213
路由設定包含Scope的OAuthmiddleware若我們限制此路由需要有特定Scope的存取權限的AccessToken才可以存取,我
們可以在middleware後面加上允許的Scope名稱,若同時需要擁有多個Scope權限,則用 加號(+)把Scope連接起來即可
e.g.'oauth:user_profile+user_likes'
//保護的資源設定oauthmiddleware
Route::get('/user-private-resource',['middleware'=>'oauth:user_
profile+user_likes',function()
{
}]);
這樣這個路由就會被指定的Scope所保護著了~
參考資料
SecuringyourAPIendpoints
OAuth2
214
JWT(JSONWebToken)Authentication
使用套件
套件名稱:tymondesigns/jwt-auth
套件版本:0.5.*
安裝
使用composer安裝套件
在 composer.json加入 ""tymon/jwt-auth":"0.5.*"並執行 composerupdate安裝套件
/*composer.json*/
"require":{
"tymon/jwt-auth":"0.5.*"
}
$composerupdate
設定套件
開啟 config/app.php檔案,並將系列套件資訊加入 providers與 aliases
'providers'=>[
Tymon\JWTAuth\Providers\JWTAuthServiceProvider::class,
],
'aliases'=>[
'JWTAuth'=>Tymon\JWTAuth\Facades\JWTAuth::class,
'JWTFactory'=>Tymon\JWTAuth\Facades\JWTFactory::class,
]
JWT
215
產生套件設定
執行 phpartisanvendor:publish--provider="Tymon\JWTAuth\Providers\JWTAuthServiceProvider"複製套件的
config/jwt.php設定檔到你應用程式的設定檔目錄
$phpartisanvendor:publish--provider="Tymon\JWTAuth\Providers
\JWTAuthServiceProvider"
產生JWTSecretKey
每個應用應該有屬於自己的JWTSecretKey用於加密並驗證你的資料
$phpartisanjwt:generate
產生的JWTSecretKey會設定在 config/jwt.php中的 secret中
//config/jwt.php
[
'secret'=>env('JWT_SECRET','your_personal_jwt_secret_key'
)
]
設定檔說明
JWT
216
參數名稱 說明資料類
型預設值
secret 密鑰 String 無
ttl JWTtoken有效時間(單位:分鐘)
Integer 60
refresh_ttl token允許重新更新時間(單位:分鐘)
Integer 20160(2週)
algo 加密演算法 String HS256
user 使用者模型類別位置 String App\User
identifier 使用者資料主鍵欄位 String id
required_claims
必須包含在tokenpayload的資料,若驗證時無這些資料則會拋出TokenInvalidException
例外
Array ['iss','iat','exp','nbf','sub','jti']
blacklist_enabled 開啟黑名單清單 Boolean true
providers.user 找尋使用者類別物件 String Tymon\JWTAuth\Providers\User\EloquentUserAdapter
providers.jwt 用於加解密JWTToken的類別物件
String Tymon\JWTAuth\Providers\JWT\NamshiAdapter
providers.auth 取得受驗證使用者的類別物件
ClosureFunction
Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter
providers.storage儲存黑名單token的快取物件,token將會儲存到它過期為止
ClosureFunction
Tymon\JWTAuth\Providers\Storage\IlluminateCacheAdapter
參考資料
tymondesigns/jwt-auth-Installationtymondesigns/jwt-auth-Configuration使用jsonwebtokenJsonWebTokens:IntroductionWheretoStoreYourJWTs-CookiesvsHTML5WebStorage
JWT
217
JWT
218
主機環境建置
程式寫完後,我們要上線測試時,還是需要一個完整的正式環境可以執行Laravel5,這裡會介紹大家如何不使用Homestead,在正式主機把Laravel5執行環境建
置起來
環境
Ubuntu14.04LTS
Nginx
MySQL5.7
PHP7.0
Redis
Memcached
Let'sEncryptSSL
安裝流程說明
安裝Nginx安裝php7安裝composer安裝MySQL5.7安裝Memcached安裝Redis安裝Let'sEncrypt
正式主機環境
219
安裝Nginx
更新系統套件
sudoapt-getupdate
安裝Nginx使用Ubuntu內建的nginx套件安裝,安裝完後預設的nginx設定檔是
/etc/nginx/sites-available/default,網站目錄會在
/usr/share/nginx/html
sudoapt-getinstallnginx
設定虛擬主機Virtualhost設定
編輯新的virtualhost設定檔案
在這裡通常我會用主機網域名稱當作他的檔案名稱,如果我有一個網域是
kejyun.dev,則我就會用 kejyun.dev當作虛擬主機設定檔名稱
sudovim/etc/nginx/sites-available/kejyun.dev
設定Listen的port
設定主機要使用哪一個port傾聽HTTP請求
listen80;
設定服務主機名稱
安裝Nginx
220
設定你申請的網域名稱,nginx會以HTTPRequest的網域不同導向不同的
Virtualhost,所以一定要設定,以下以 kejyun.dev為例
server_namekejyun.dev;
設定網站根目錄路徑
我們將Laravel5的程式放在使用者 kejyun的家目錄下,而我們必須要將網站
路徑指定到Laravel專案下的 public目錄下才可以正常執行Laravel專案
root"/home/kejyun/laravel52/public";
設定Log路徑
設定當Request發生錯誤的時候,本Virtualhost要將Log存放在哪個檔案
error_log/var/log/nginx/kejyun.dev-error.logerror;
完整虛擬主機設定
設定檔中有包括 設定php檔案處理方式,在這邊我們可以先設定,等之後安裝完
php7時就可以直接使用
server{
#設定Listen的port
listen80;
#設定服務主機名稱
server_namekejyun.dev;
#設定網站根目錄路徑
root"/home/kejyun/laravel52/public";
#設定讀取檔案優先順序
indexindex.htmlindex.htmindex.php;
安裝Nginx
221
#設定網站編碼
charsetutf-8;
location/{
try_files$uri$uri//index.php?$query_string;
}
location=/favicon.ico{access_logoff;log_not_foundoff;
}
location=/robots.txt{access_logoff;log_not_foundoff;
}
access_logoff;
#設定Log路徑
error_log/var/log/nginx/kejyun.dev-error.logerror;
sendfileoff;
client_max_body_size100m;
#設定php檔案處理方式
location~\.php${
fastcgi_split_path_info^(.+\.php)(/.+)$;
fastcgi_passunix:/var/run/php/php7.0-fpm.sock;
fastcgi_indexindex.php;
includefastcgi_params;
fastcgi_paramSCRIPT_FILENAME$document_root$fastcgi_scr
ipt_name;
fastcgi_intercept_errorsoff;
fastcgi_buffer_size16k;
fastcgi_buffers416k;
fastcgi_connect_timeout300;
fastcgi_send_timeout300;
fastcgi_read_timeout300;
}
location~/\.ht{
denyall;
}
安裝Nginx
222
}
連結虛擬主機Virtualhost設定
Nginx虛擬主機設定主要是讀取 /etc/nginx/sites-enabled/目錄下的所有檔
案,如果要讓此設定檔案啟用,則必須要將原設定檔目錄 /etc/nginx/sites-available/的設定檔案使用軟連結連結過去
sudoln-s/etc/nginx/sites-available/kejyun.dev/etc/nginx/site
s-enabled/kejyun.dev
重新啟動nginx重新啟動nginx以讀取新的設定
sudoservicenginxrestart
這樣我們就完成了NginxServer的設定了!!
參考資料
HowToSetUpnginxVirtualHosts(ServerBlocks)onUbuntu12.04LTSphp-HowdoIchangetheNGINXuser?-ServerFault
安裝Nginx
223
安裝php7
加入php7套件資源庫
目前(2016/03)php7沒有在Ubuntu的預設套件庫中,所以若要使用php7的話,則必須要自行加入此套件庫,這樣我們才能在Ubuntu安裝php7
sudoadd-apt-repositoryppa:ondrej/php
更新套件資源庫
加入新的套件資源庫後,必須進行系統套件清單更新,才能夠讀取到新的套件設定
sudoapt-getupdate
安裝php7套件
php7.0-fpm:Nginx解析php檔案的工具
php7.0-mysql:連線mysqlphp7.0-mcrypt:Laravel加解密工具
其他套件是我在Laravel專案中需要的套件,可以依照自己需求去進行安裝
sudoapt-getinstallphp7.0-fpmphp7.0-mysqlphp7.0-mcryptphp7.
0-gdphp7.0-cliphp7.0-curlphp7.0-imap
設定php7.0-fpm若有需要變更任何php7.0-fpm的任何設定,可以修改下列設定檔案
sudovim/etc/php/7.0/fpm/pool.d/www.conf
安裝php7
224
像是您如果想要把異動傾聽php的執行擁有者變更為 kejyun,您可以做以下的
設定
user=kejyun
group=kejyun
listen.owner=kejyun
listen.group=kejyun
listen.mode=0660
設定Nginxphp檔案處理方式
設定虛擬主機Virtualhost設定檔
sudovim/etc/nginx/sites-available/kejyun.dev
設定php檔案處理方式
安裝php7
225
server{
#...
#設定php檔案處理方式
location~\.php${
fastcgi_split_path_info^(.+\.php)(/.+)$;
fastcgi_passunix:/var/run/php/php7.0-fpm.sock;
fastcgi_indexindex.php;
includefastcgi_params;
fastcgi_paramSCRIPT_FILENAME$document_root$fastcgi_scr
ipt_name;
fastcgi_intercept_errorsoff;
fastcgi_buffer_size16k;
fastcgi_buffers416k;
fastcgi_connect_timeout300;
fastcgi_send_timeout300;
fastcgi_read_timeout300;
}
#...
}
重新啟動php如果有異動任何php7.0-fpm的任何設定的話,必須要將php7.0-fpm服務重新啟
動,才能夠讀取到新的設定
sudoservicephp7.0-fpmrestart
這樣我們就完成php7的設定了!
參考資料
InstallPHP7onUbuntu14.04|EnricoZimuelkasparsd/php-7-debian:InstallPHP7onDebian/Ubuntu
安裝php7
226
Installingphp7-fpmwithphpredisextensiononUbuntu14.04
安裝php7
227
安裝composer
下載composer
curl-sShttps://getcomposer.org/installer|php
將composer執行檔移動到系統環境變數路徑
移動過去系統環境變數路徑中,這樣所有系統的使用者才都可以執行
sudomvcomposer.phar/usr/bin/composer
安裝所有Laravel專案套件
在我們將程式使用git推送到主機時,vendor下所有的套件都不會被推送到主機,
所以程式碼上去主機後,需要自己作安裝套件的動作
cd/home/kejyun/laravel52
composerinstall
這樣我們就完成了composer的安裝跟安裝Laravel專案套件了
參考資料
ComposerDownload
安裝composer
228
安裝MySQL5.7
設定MySQL5.7套件資源庫
目前(2016/03)MySQL5.7沒有在Ubuntu的預設套件庫中,所以若要使用
MySQL5.7的話,則必須要設定加入此套件庫,這樣我們才能在Ubuntu安裝
MySQL5.7
wgethttp://dev.mysql.com/get/mysql-apt-config_0.6.0-1_all.deb
sudodpkg-imysql-apt-config_0.6.0-1_all.deb
sudodpkg-reconfiguremysql-apt-config
設定使用MySQL當作預設安裝版本
安裝MySQL5.7
229
設定完成點Apply完成設定
更新套件資源庫
加入新的套件資源庫後,必須進行系統套件清單更新,才能夠讀取到新的套件設定
安裝MySQL5.7
230
sudoapt-getupdate
安裝MySQL
sudoapt-getinstallmysql-server-5.7
這樣就完成MySQL5.7的安裝了!
參考資料
MySQL::AQuickGuidetoUsingtheMySQLAPTRepositoryMySQL::DownloadMySQLAPTRepository
安裝MySQL5.7
231
安裝Memcached
安裝
直接安裝Ubuntu的Memcached套件
sudoapt-getinstallmemcached
sudoapt-getinstalllibmemcached-devlibmemcached11
設定php7Memcached套件
php7讀取Memcached的套件還是在開發版,沒辦法直接使用apt-get去完成安
裝,所以我們要自行下載開發版套件去進行編譯安裝
安裝php7套件編譯軟體
php7.0-dev是為了執行php7的phpize而安裝的
sudoapt-getinstallpkg-config
sudoapt-getinstallphp7.0-dev
下載phpMemcached套件
gitclonehttps://github.com/php-memcached-dev/php-memcached
切換開發版套件路徑
cdphp-memcached
gitcheckout-bphp7origin/php7
安裝Memcached
232
編譯php7Memcached套件
phpize
./configure
make
maketest
sudomakeinstall
設定載入php7memcached套件
sudovim/etc/php/7.0/cli/conf.d/20-memcached.ini
sudovim/etc/php/7.0/fpm/conf.d/20-memcached.ini
在 20-memcached.ini檔案內設定
extension=memcached.so
設定檔分別要在 cli與 fpm兩個地方
cli指的是commandline的php執行擋
fpm指的是nginx解析php的解譯器
記得兩邊都要設定
重新啟動php7.0-fpm重新啟動之後,在網頁部分可以用 phpinfo();確認看看有沒有看到
Memcached套件
sudoservicephp7.0-fpmrestart
查詢安裝結果
查詢phpinfoMemcached安裝結果
安裝Memcached
233
查詢phpcommandlineMemcached安裝結果
安裝Memcached
234
$php-i|grepmemcached
/etc/php/7.0/cli/conf.d/20-memcached.ini,
memcached
memcachedsupport=>enabled
libmemcachedversion=>1.0.18
memcached.compression_factor=>1.3=>1.3
memcached.compression_threshold=>2000=>2000
memcached.compression_type=>fastlz=>fastlz
memcached.default_binary_protocol=>0=>0
memcached.default_connect_timeout=>0=>0
memcached.default_consistent_hash=>0=>0
memcached.serializer=>php=>php
memcached.sess_binary_protocol=>1=>1
memcached.sess_connect_timeout=>0=>0
memcached.sess_consistent_hash=>1=>1
memcached.sess_lock_expire=>0=>0
memcached.sess_lock_max_wait=>notset=>notset
memcached.sess_lock_retries=>5=>5
memcached.sess_lock_wait=>notset=>notset
memcached.sess_lock_wait_max=>2000=>2000
memcached.sess_lock_wait_min=>1000=>1000
memcached.sess_locking=>1=>1
memcached.sess_number_of_replicas=>0=>0
memcached.sess_persistent=>0=>0
memcached.sess_prefix=>memc.sess.=>memc.sess.
memcached.sess_randomize_replica_read=>0=>0
memcached.sess_remove_failed_servers=>0=>0
memcached.sess_sasl_password=>novalue=>novalue
memcached.sess_sasl_username=>novalue=>novalue
memcached.sess_server_failure_limit=>0=>0
memcached.store_retry_count=>2=>2
Registeredsavehandlers=>filesusermemcached
這樣我們就完成Memcached及在php7.0Memcached套件的安裝了
參考資料
安裝Memcached
235
php-memcached-dev/php-memcached:memcachedextensionbasedonlibmemcachedlibrarykasparsd/php-7-debian:InstallPHP7onDebian/UbuntuServerPilot|HowtoInstallthePHPMemcacheExtensionInstallingPHP-7withMemcached-ServersforHackers
安裝Memcached
236
安裝Redis
設定Redis套件資源庫
sudoadd-apt-repositoryppa:chris-lea/redis-server
sudoapt-getupdate
安裝
sudoapt-getinstallredis-server
設定RedisRedis設定檔放在 /etc/redis/redis.conf
sudovim/etc/redis/redis.conf
設定設定檔
requirepass你的Redis連線密碼
#bind127.0.0.1不要限定只有本機才能連線
maxmemory-policynoeviction
appendonlyyes
appendfilenameredis-staging-ao.aof
重新啟動Redis
sudoserviceredis-serverrestart
這樣我們就完成Redis的安裝了!!
安裝Redis
237
參考資料
HowToConfigureaRedisClusteronUbuntu14.04|DigitalOcean
安裝Redis
238
安裝Let'sEncrypt為了推廣SSL憑證,Let'sEncrypt提供了免費的SSL憑證,可以讓你們的主機也
有SSL加密的保障
但是Let'sEncrypt提供的憑證有效期限每次只有 90天的效期,若過期之後需要
重新更新憑證方可繼續使用
下載
wgethttps://dl.eff.org/certbot-auto
chmoda+xcertbot-auto
安裝SSL憑證
在安裝SSL憑證時需要將您的Nginx主機服務關閉,才能進行憑證驗證
HowwouldyouliketoauthenticatewiththeACMECA?
----------------------------------------------------------------
---------------
1:Placefilesinwebrootdirectory(webroot)
2:Spinupatemporarywebserver(standalone)
----------------------------------------------------------------
---------------
Selecttheappropriatenumber[1-2]then[enter](press'c'toc
ancel):
用nginx選擇 standalone,若是apache則選擇用 webroot
輸入驗證SSL的網址
安裝Let'sEncrypt
239
Pleaseenterinyourdomainname(s)(commaand/orspaceseparate
d)(Enter'c'
tocancel):kejyun.dev
憑證檔案
安裝完的憑證會依照你申請的domain當作資料夾名稱放到
/etc/letsencrypt/live/目錄下
如果我同時申請了 kejyun.dev與 www.kejyun.dev,那麼憑證檔案就會分別
放在 /etc/letsencrypt/live/kejyun.dev/及/etc/letsencrypt/live/www.kejyun.dev/目錄下
憑證檔案分別會有4個
檔案名稱 說明
cert.pem 申請網域的憑證
chain.pem Let'sEncrypt的憑證
fullchain.pem cert.pem及chain.pem合併檔案
privkey.pem 申請網域的憑證密鑰
設定nginx使用SSL憑證
安裝Let'sEncrypt
240
server{
#設定監聽的port為443
listen443ssl;
#設定憑證檔案
ssl_certificate/etc/letsencrypt/live/kejyun.dev/fullchain.p
em;
ssl_certificate_key/etc/letsencrypt/live/kejyun.dev/privkey
.pem;
ssl_protocolsTLSv1TLSv1.1TLSv1.2;
ssl_prefer_server_cipherson;
ssl_ciphers'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH
';
}
設定nginx80port自動轉址導向SSL443port
server{
listen80;
server_namekejyun.dev;
return301https://$host$request_uri;
}
重新啟動Nginx
sudoservicenginxrestart
設定完成後就可以看到自己的網站有做SSL加密了!
自動更新Let'sEncrypt憑證
自動更新憑證
安裝Let'sEncrypt
241
./path/to/certbotrenew--pre-hook"servicenginxstop"--post-h
ook"servicenginxstart"
在更新憑證的時候,還是需要把webserver停掉,所以可以在 --pre-hook更新前關掉nginx,在 --post-hook更新後啟動nginx
加入Cronjob排程執行憑證更新
編輯crontab
sudocrontab-e
設定每個禮拜一的凌晨2:30進行一次憑證的檢查及更新
302**1./path/to/certbotrenew--pre-hook"servicenginxsto
p"--post-hook"servicenginxstart">>/var/log/letsencrypt-ren
ewal.log
這樣我們就有了一個半永久的SSL憑證了!!
參考資料
Certbot-NginxonUbuntu14.04Certbotdocumentation-Renewingcertificatesletsencrypt/letsencryptHowToSecureNginxwithLet'sEncryptonUbuntu14.04|DigitalOcean
安裝Let'sEncrypt
242
其他常見問題
這裏會介紹一些在開發Laravel5遇到的常見問題
CalltoundefinedmethodgetCachedCompilePath()
其他常見問題
243
CalltoundefinedmethodgetCachedCompilePath()我在使用Laravel5.0.x時,使用 composerupdate去更新目前的套件時,跳出
了這樣的訊息:
(PS:也有人在執行 phpartisanclear-compiled出現這樣的狀況)
PHPFatalerror:CalltoundefinedmethodIlluminate\Foundation\Application::getCachedCompilePath()
這個是因為Laravel5在執行時會把整個Framework編譯到
storage/framework/compiled.php,若這個檔案已產生,Laravel5在更新套
件時執行一些相關Laravel的功能時,會預設執行 compiled.php檔案中的類別
函式,而更新的檔案中有 getCachedCompilePath()這個方法,所以呼叫時
Laravel會在舊的 compiled.php找不到這個方法
解決方式
直接把 storage/framework/compiled.php刪除即可,Laravel5會自動重新產
生這個 compiled.php檔案!
參考資料
RuntimeExceptiononfreshinstall
CalltoundefinedmethodgetCachedCompilePath()
244
變更專案目錄名稱導致View無法讀取
在想要變更原先Laravel5的專案目錄時,Laravel5會告訴你你沒辦法讀取到view的目錄,看了錯誤的訊息發現這個view的目錄是在原先舊專案的目錄名稱下
錯誤訊息長得像這樣:
InvalidArgumentExceptioninFileViewFinder.phpline137:
View[welcome]notfound.
這個原因是Laravel5會將設定檔作快取存下來到
bootstrap/cache/config.php目錄下面,而這個快取的檔案是原本舊專案的設
定,所以我們必須要清除這個快取,才可以讓Laravel5可以讀取到新的設定,這
時候我們可以在artisan下這些指令,清除原先專案的快取設定:
$phpartisanconfig:clear
$phpartisanview:clear
執行清除這些快取資料後,我們就可以正常的的獨到新專案目錄下的view資料
摟!
變更專案目錄名稱導致View無法讀取
245
Laravel5.1目錄結構異動
從Laravel5.0升級到Laravel5.1時,app的目錄結構有做一些小異動,異動如下
app/Command=>app/Jobsapp/Handlers=>app/Listeners
根據官方說法,這樣的命名比較能夠識別出該目錄程式的作用是什麼
命令(Command)=>工作(Jobs)處理器(Handlers)=>事件傾聽器(Listeners)
在從5.0升級至5.1時,記得將這幾個目錄做重新命名喔~
參考資料
DirectoryChanges-LaracastReleaseNotes-laravel.tw
Laravel5.1目錄結構異動
246
官方學習資源
FrameworkLaravel-ThePHPFrameworkForWebArtisansLumen-PHPMicro-FrameworkByLaravel
最新消息
LaravelNewsLaravelWeekly
文件(Document)Welcome!-LaravelPHPFrameworkLaravelwiki
APILaravel5APIDocumentation
套件清單
Packalyst::PackagesforLaravelPackagist-taglaravelLaravelCollectiveArsenal::Cartalyst
作者
TaylorOtwell|Twitter
官方
248
官方
249
社群
FacebookPHP台灣
Laravel台灣
PHP也有DayLaravel(@laravelphp)|TwitterLaravel-社群-Google+Laravel-RedditLaravel-QuoraLaravel-FacebookLaravel-LinkedIn
共享文件
歡迎來到Laravel台灣-laraveltw.hackpad.com
活動
Laravel台灣-KKTIX
論壇
SitePointPHPScotch-Laravelhttp://www.codetutorial.io/tag/laravel/Forum-Laravel.IOLaravelForum-LaracsstsTheLaravelPodcastLaravelSlacklaravel/laravel-Gitter
社群
250
BlogMattStauffer.coCodeHeapsVluzrmosGrahamCampbell
社群
251
會議議程
會議
LaraconUSLaraconEUArtisanConf
會議影片
LaraconEU2014LaraconUS2014LaraconEU2013LaraconUS2013
會議議程
252
工作
LaraJobsLaravelConsultantsandFreelancers+LaravelGurusWithLaravel-ThejobboardforLaravelprojectsanddevelopers
工作
253
文件
LaravelALaravel5BoilerplateProjectLaravelBookWebdevelopment,design,andothernerdytopics!|DayleReesLaravel-簡潔、優雅的PHP開發框架(PHPWebFramework)。-Laravel中文網
Laravel中國社區
SimpleLaravel♥ScotchAwesomeLaravelCursodeLaravel5enespañoldesdecero|styde.netLaravelCollective
PHPAwesomePHPPHP-ToolsSitePointPHP–LearnPHP,MySQL,SOAP&more
設計模式
PSR繁體中文
安裝
HowtoSetupLaravel4-Tuts+CodeTutorial
指令函式
LaravelCheatSheetLaravel-Tricks.com
文件
254
文件
255
文章
部落格
MattStauffer.co
文件
AwesomeLaravel
除錯
DebuggingQueriesinLaravel
IoC神奇的服务容器
事件
StepbyStepGuidetoInstallingSocket.ioandBroadcastingEventswithLaravel5.1
文章
256
套件
清單
Packalyst::PackagesforLaravelPackagist-taglaravelLaravelCollectiveArsenal::CartalystBuiltwithLaravel
時間
Carbon-AsimplePHPAPIextensionforDateTime.
編輯器
LaravelMarkdown
檔案
LaravelFlysystem
除錯
ThemissingtailcommandforLaravel5LaravelExceptionsLaravelDebugbar
條碼
SimpleQrCode
套件
257
社群
LaravelGitHub
CMSWardrobeBootstrapCMSTypiCMSPHPHubCachetPaperwork
OpenSourcelaravel-tricks
套件
258
佈署程式
LaravelForgeForgeRecipes
Envoyer-ZeroDowntimePHPDeploymentLaravelhostinginthecloud
服務工具
259
教學影片
English
初學
Laravel5FundamentalsWhat'sNewinLaravel5.1LaravelBasicsCourseLaravelFromScratch
進階
TheBestLaravelandPHPScreencastsPHPAcademyAnthonyVipondmuukrlsLyndaLaravelTuts+CodeCourses-Laravel
繁體中文
Laravel台灣-Youtube
教學影片
260
教學網站
英文
LaravelRecipesLaravel-Tricks.comWebdevelopment,design,andothernerdytopics!|DayleReesLaravelBookPHP:TheRightWay
中文
Laravel初體驗-實作Blog系統|LaravelDojoPHP:TheRightWay繁體中文
教學網站
261
SublimeProWorkflowinLaravelandSublimeText-Tuts+CodeTutorial
編輯開發
262
主機
HostingOpenShiftAmazonWebServices(AWS)LinodeHerokuVultrPagodaBoxDigitalOcean
教學
RunLaravel5.1onOpenShiftInstallingaLaravelapponHerokuLaravel5-PagodaBoxDocumentation
主機
263
案例
VDemocracy.tw-群眾募資‧群眾集資網站CrowdfundingPlatforminTaiwanBuiltwithlaravel.com
成功案例
264
top related