原文鏈接
Laravel指南
配置
Laravel的主配置文件將經(jīng)常用到的文件集中到了根目錄下的.env
目錄下,這樣更高效更安全。其內(nèi)容如下:
# 這里配置
APP_ENV=local
APP_DEBUG=true
APP_KEY=YboBwsQ0ymhwABoeRgtlPE6ScqSzeWZG
# 這里配置數(shù)據(jù)庫(kù)
DB_HOST=localhost
DB_DATABASE=test
DB_USERNAME=root
DB_PASSWORD=mysql
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
MAIL_DRIVER=smtp
MAIL_HOST=mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
EXAMPLE_PUBLIC_KEY=abc\ndef # 要在配置里面換行,目前只有這種方式了,在讀取的時(shí)候這樣子讀取: str_replace("\\n", "\n", env('MSGCENTER_PUBLIC_KEY'))
還可以在該文件里配置其它的變量,然后在其它地方使用env(name, default)
即可訪問。例如,讀取數(shù)據(jù)庫(kù)可以用config('database.redis.default.timeout', -1)
來。
全局配置文件.env
僅僅是一些常量的配置,而真正具體到哪個(gè)模塊的配置則是在config
目錄之下.同樣,也可以動(dòng)態(tài)改變配置:Config::set('database.redis.default.timeout')
另外,可以通過幫助函數(shù)來獲取當(dāng)前的應(yīng)用環(huán)境:
# 獲取的是.env里面APP_ENV的值
$environment = App::environment();
App::environment('local') // true/false
app()->environment()
控制器
laravel可以直接通過命令創(chuàng)建一個(gè)控制器:
php artisan make:controller HomeController
,然后就會(huì)有這么一個(gè)控制器文件了:app/Http/Controllers/HomeController.php
數(shù)據(jù)校驗(yàn)Validation
# 通過Validator進(jìn)行校驗(yàn),第一個(gè)參數(shù)是一個(gè)key-value的數(shù)組
$validation = Validator::make($request->all(), [
'ip' => 'required|ip' # 校驗(yàn)key=ip的值是否真的是ip
'arr.*.field' => 'required|' # 驗(yàn)證數(shù)組內(nèi)部的字段,5.1不支持
])
# 常用框架自帶的認(rèn)證類型
active_url # 該url一定能訪問
array # 僅允許為數(shù)組
between:min,max # 介于最小值和最大值之間,兩邊都是閉區(qū)間,如果是數(shù)字,一定要先聲明當(dāng)前字段為integer
boolean # 必須是true,false,1,0,"1","0"
date # 必須是時(shí)間類型
exists:table,column # 判斷字段的值是否存在于某張表的某一列里面
exists:table,column1,column2,value # 判斷字段的值是否存在于某張表的某一列里面,并且另一列的值為多少
exists:table,column1,column2,!value # 判斷字段的值是否存在于某張表的某一列里面,并且另一列的值不為多少
exists:table,column1,column2,{$field}# 判斷字段的值是否存在于某張表的某一列里面,并且另一列的值和前面的某個(gè)字段提供的值一樣
in:value1,value2,...# 字段值必須是這些值中的一個(gè),枚舉值
not_in:value1,value2,... # 字段值不為這其中的任何一個(gè)
integer # 必須是整數(shù)
ip # 必須是IP字符串
json # 必須是JSON字符串
max:value # 規(guī)定最大值
min:value # 規(guī)定最小值
numeric # 是數(shù)字
required # 必填
required_with:字段名 # 當(dāng)某個(gè)字段存在的時(shí)候當(dāng)前字段必填
required_if:anotherfield,value # 當(dāng)某個(gè)字段的某個(gè)值為多少的時(shí)候,當(dāng)前字段為必填
string # 必須是字符串
url # 必須是合法的url
regex # 必須符合這個(gè)正則表達(dá)式,例如regex:/^[a-z]{1}[a-z0-9-]+$/,需要注意的是,如果正則表達(dá)式中用了|符號(hào),必須用數(shù)組的方式來寫正則表達(dá)式,否則會(huì)報(bào)錯(cuò),例如['required', 'regex:/[0-9]([0-9]|-(?!-))+/']
# 自定義錯(cuò)誤提示的消息,可以通過傳遞進(jìn)去,不過也可以直接在語言包文件resources/lang/xx/validation.php文件的的custom數(shù)組中進(jìn)行設(shè)置
# 驗(yàn)證數(shù)組里面的字段用這樣的方式
'person.email' => 'email|unique:users'
'person.first_name' => 'required_with:person.*.last_name'
# 將表單的驗(yàn)證提取出來作為單獨(dú)的表單請(qǐng)求驗(yàn)證Form Request Validation
# 使用php artisan make:request BlogPostRequest創(chuàng)建一個(gè)表單請(qǐng)求驗(yàn)證類,會(huì)在app/Http/Requests里面生成相應(yīng)的類,之后表單驗(yàn)證邏輯就只需要在這里寫上就行了,例如
<?php
namespace App\Http\Requests;
use Route;
use Illuminate\Support\Facades\Auth;
class BlogPost extends Request{
// 這個(gè)方法驗(yàn)證用戶是否有權(quán)限訪問當(dāng)前的控制器
public function authorize() {
$id = Route::current()->getParameter('post'); // 如果是resource的東西,要獲取id,在這里是這樣子獲取,不能直接用id,而是相對(duì)應(yīng)的資源名
switch($this->method()){ # 我這里,姑且卸載一起
case 'POST':{
return Auth::user()->can('create', Project::class);
}
case 'PUT':{
return Auth::user()->can('update', Project::find($id));
}
}
}
/**
* 這里則是返回驗(yàn)證規(guī)則
*/
public function rules(){
switch($this->method()){
case 'POST': {
return [
'name' => 'required|string|max:100',
];
}
case 'PUT':{
return [
'name' => 'required|string|max:100',
];
}
}
}
// 自定義返回格式
public function response(array $errors){
return redirect()->back()->withInput()->withErrors($errors);
}
}
Restful資源控制器
資源控制器可以讓你快捷的創(chuàng)建 RESTful 控制器。通過命令php artisan make:controller PhotoController
創(chuàng)建一個(gè)資源控制器,這樣會(huì)在控制器PhotoController.php
里面包含預(yù)定義的一些Restful的方法
Route::resource('photo', 'PhotoController');
# 嵌套資源控制器
# 例如
Route::resource('photos.comments', 'PhotoCommentController');
# 這樣可以直接通過這樣的URL進(jìn)行訪問photos/{photos}/comments/{comments}
# 控制器只需要這樣子定義即可
public function show($photoId, $commentId)
資源控制器對(duì)應(yīng)的路由
Verb | URI | Action | Route Name |
---|---|---|---|
GET | /photos | index | photos.index |
GET | /photos/create | create | photos.create |
POST | /photos | store | photos.store |
GET | /photos/{photo} | show | photos.show |
GET | /photos/{photo}/edit | edit | photos.edit |
PUT/PATCH | /photos/{photo} | update | photos.update |
DELETE | /photos/{photo} | destroy | photos.destroy |
路由url
路由緩存:laravel里面使用route:cache Artisan
,可以加速控制器的路由表,而且性能提升非常顯著。
# 路由分組,第一個(gè)屬性則是下面所有路由共有的屬性
Route::group(['namespace' => 'Cron', 'middleware' => ['foo', 'bar']], function()
{
Route::get('/', function()
{
// App\Http\Controllers\Cron
});
Route::get('user/profile', function()
{
// Has Foo And Bar Middleware
});
});
# 通過url向控制器傳遞參數(shù)
這樣定義url
Route::resource('wei/{who}', 'WeixinController');
然后在控制器里這樣定義
public function index($who)
# 嵌套資源控制器
# 例如
Route::resource('photos.comments', 'PhotoCommentController');
# 這樣可以直接通過這樣的URL進(jìn)行訪問photos/{photos}/comments/{comments}
# 控制器只需要這樣子定義即可
public function show($photoId, $commentId)
# 如果要獲取嵌套資源的url,可以這樣子:
route('post.comment.store', ['id'=> 12]) # 這樣子就獲取到id為12的post的comment的創(chuàng)建接口地址
視圖/靜態(tài)資源
提供文件下載
return response()->download($pathToFile); # 直接提供文件下載
return response()->download($pathToFile, $name, $headers); # 設(shè)置文件名和響應(yīng)頭
return response()->download($pathToFile)->deleteFileAfterSend(true); # 設(shè)置為下載后刪除
模板Template
標(biāo)簽
# 轉(zhuǎn)義
{!! $name !!}
# if else
@if()
@else
@endif
# 需要注意的是,if else是不能寫在一行的如果非要寫在同一行,建議使用這樣的方法
{!! isset($a) && $a['a'] == 'a' ? 'disabled': '' !!}
分頁(yè)
Larval的分頁(yè)主要靠Eloquent來實(shí)現(xiàn),如果要獲取所有的,那么直接把參數(shù)寫成PHP_INT_MAX
就行了嘛
$users = User::where('age', 20)->paginate(20); // 表示每頁(yè)為20條,不用去獲取頁(yè)面是第幾頁(yè),laravel會(huì)自動(dòng)在url后面添加page參數(shù),并且paginate能自動(dòng)獲取,最后的結(jié)果,用json格式顯示就是
{
'total': 50,
'per_page': 20,
'current_page': 1,
'last_page': 3,
'next_page_url': '...',
'prev_page_url': null,
'from': 1,
'to': 15,
'data': [{}, {}]
}
# 如果是在數(shù)據(jù)庫(kù)關(guān)系中進(jìn)行分頁(yè)可以直接在Model里面鞋
public function ...(){
return $this->posts()->paginate(20);
}
# 獲取all的分頁(yè)數(shù)據(jù),不用::all(),而是
User::paginate(20) # 直接用paginate
數(shù)據(jù)庫(kù)Model
Laravel提供了migration和seeding為數(shù)據(jù)庫(kù)的遷移和填充提供了方便,可以讓團(tuán)隊(duì)在修改數(shù)據(jù)庫(kù)的同時(shí),保持彼此的進(jìn)度,將建表語句及填充操作寫在laravel框架文件里面并,使用migration來控制數(shù)據(jù)庫(kù)版本,再配合Artisan命令,比單獨(dú)管理數(shù)據(jù)庫(kù)要方便得多。
配置文件
config/database.php
里面進(jìn)行數(shù)據(jù)庫(kù)引擎的選擇,數(shù)據(jù)庫(kù)能通過prefix
變量統(tǒng)一進(jìn)行前綴的配置
建表操作
生成一個(gè)model: php artisan make:model user -m
,這樣會(huì)在app
目錄下新建一個(gè)和user表對(duì)應(yīng)的model文件
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
//
}
加上-m
參數(shù)是為了直接在database/migrations
目錄下生成其遷移文件,對(duì)數(shù)據(jù)庫(kù)表結(jié)構(gòu)的修改都在此文件里面,命名類似2016_07_04_051936_create_users_table
,對(duì)數(shù)據(jù)表的定義也在這個(gè)地方,默認(rèn)會(huì)按照復(fù)數(shù)來定義表名:
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateApplicationsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('applications', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
DB::statement('ALTER TABLE `'.DB::getTablePrefix().'applications` comment "這里寫表的備注"');
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::drop('applications');
}
}
當(dāng)數(shù)據(jù)表定義完成過后,執(zhí)行php artisan migrate
即可在真的數(shù)據(jù)庫(kù)建表了
php artisan migrate // 建表操作,運(yùn)行未提交的遷移
php artisan migrate:rollback // 回滾最后一次的遷移
php artisan migrate:reset // 回滾所有遷移
php artisan migrate:refresh // 回滾所有遷移并重新運(yùn)行所有遷移
如果要修改原有model,不能直接在原來的migrate文件上面改動(dòng),而是應(yīng)該新建修改migration,例如,執(zhí)行php artisan make:migration add_abc_to_user_table
這樣會(huì)新建一個(gè)遷移文件,修改語句寫在up函數(shù)里面:
public function up()
{
Schema::table('users', function (Blueprint $table) {
$table->string('mobile', 20)->nullable()->after('user_face')->comment('電話號(hào)碼')->change(); // 將mobile字段修改為nullable并且放在user_face字段后面,主要就是在后面加上change()方法
$table->renameColumn('from', 'to'); // 重命名字段
$table->dropColumn('votes'); // 刪除字段
$table->dropColumn(['votes', 'from']);// 刪除多個(gè)字段
$table->string('email')->unique(); // 創(chuàng)建索引字段
$table->unique('email'); // 創(chuàng)建唯一索引
$table->unique('email', 'nickname'); // 聯(lián)合唯一索引
$table->index(['email', 'name']); // 創(chuàng)建復(fù)合索引
$table->dropPrimary('users_id_primary'); // 移除主鍵
$table->dropUnique('users_email_unique'); // 移除唯一索引
$table->dropIndex('geo_state_index'); // 移除基本索引
});
}
表/Model的定義
class User extends Model{
public $timestamps = false; // 設(shè)置該表不需要使用時(shí)間戳,updated_at和created_at字段
protected $primaryKey = 'typeid' // 不以id為主鍵的時(shí)候需要單獨(dú)設(shè)置
protected $primaryKey = null; // 沒有主鍵的情況
protected $incrementing = false; // 不使用自增主鍵
protected $connection = 'second'; // 設(shè)置為非默認(rèn)的那個(gè)數(shù)據(jù)庫(kù)連接
protected $fillable = ['id', 'name']; // 設(shè)置可直接通過->訪問或者直接提交保存的字段
protected $table = 'my_flights'; // 自定義表明,默認(rèn)的表明會(huì)以model的復(fù)數(shù)形式,需要注意的是,英語單詞復(fù)數(shù)的變化有所不同,如果取錯(cuò)了表明活著以中文拼音作為表明,有時(shí)候就需要明確表的名稱了
}
字段的定義
# 字段定義
$table->increments('id') # 默認(rèn)都有的自增的主鍵
$table->string('name', 45)->comment('名字') # 字符串類型,添加注釋,長(zhǎng)度可指明也可不指名
$table->boolean('type') # 相當(dāng)于tinyint(1)
$table->softDeletes() # 軟刪除,名為deleted_at類型為timestamp的軟刪除字段
$table->bigInteger('') # bigint(20),加不加sign都是20
$table->integer() # int(10)
$table->integer()->uninsign() # int(11)
$table->integer()->unsigned() # int(10)
$table->mediumInteger('') # int(9)
$table->mediumInteger('')->unsign() # int(9)
$table->mediumInteger('')->unsigned()
# 相當(dāng)于int(8)
$table->smallInteger('') # smallint(6)
$table->smallInteger('')->unsign() # smallint(6)
$table->smallInteger('')->unsigned() # smallint(5)
$table->tinyInteger('') # tinyint(4)
$table->tinyInteger('')->unsign() # tinyint(4)
$table->tinyInteger('')->unsigned() # tinyint(1)
$table->float('') # 相當(dāng)于DOUBLE
$table->text('') # text()
$table->dateTime('created_at') # DATETIME類型
# 字段屬性
->nullable() # 允許null
->unsigned() # 無符號(hào),如果是integer就是int(10)
->unsign() # 無符號(hào),如果是integer就是int(11)
->default('') # 默認(rèn)值
# 索引定義
$table->index('user_id')
# 主鍵定義
$table->primary('id') # 默認(rèn)不用寫這個(gè)
$table->primary(array('id', 'name')) # 多個(gè)主鍵的情況
# 外鍵定義
$table->integer('user_id')->unsigned(); # 先要有一個(gè)字段,而且必須是unsigned的integer
$table->foreign('user_id')->references('id')->on('users'); # 關(guān)聯(lián)到users表的id字段
定義表之間的關(guān)系
直接在ORM里面進(jìn)行表關(guān)系的定義,可以方便查詢操作。
一對(duì)多hasMany
public function posts(){
return $this->hasMany('App\Post');
}
# 可以這樣使用
Users::find(1)->posts
# 指定外鍵
$this->hasMany('App\Post', 'foreign_key', 'local_key')
一對(duì)一hasOne
public function father(){
return $this->hasOne('App\Father');
}
$this->hasOne('App\Father', 'id', 'father'); # 表示father的id對(duì)應(yīng)本表的father
相對(duì)關(guān)聯(lián)belongsTo(多對(duì)一)
public function user(){
return $this->belongsTo('App\User')
}
Posts::find(1)->user # 可以找到作者
多對(duì)多關(guān)系belongsToMany
如果有三張表,users,roles,role_user其中,role_user表示users和roles之間的多對(duì)多關(guān)系。如果要通過user直接查出來其roles,那么可以這樣子
class User extends Model {
public funciton roles()
{
return $this->belongsToMany('App\Role', 'user_roles', 'user_id', 'foo_id'); # 其中user_roles是自定義的關(guān)聯(lián)表表名,user_id是關(guān)聯(lián)表里面的user_id,foo_id是關(guān)聯(lián)表里面的role_id
}
}
$roles = User::find(1)->roles; # 這樣可以直接查出來,如果想查出來roles也需要在roles里面進(jìn)行定義
多態(tài)關(guān)聯(lián)
一個(gè)模型同時(shí)與多種模型相關(guān)聯(lián),可以一對(duì)多(morphMany)、一對(duì)一(morphOne)、多對(duì)多(mar)
例如: 三個(gè)實(shí)例,文章、評(píng)論、點(diǎn)贊,其中點(diǎn)贊可以針對(duì)文章和評(píng)論,點(diǎn)贊表里面有兩個(gè)特殊的字段target_id
、target_type
,其中target_type
表示對(duì)應(yīng)的表的Model,target_id
表示對(duì)應(yīng)的表的主鍵值
# 點(diǎn)贊Model
class Like extends Model {
public function target() {
return $this->morphTo(); // 如果主鍵不叫id,那么可以指定morphTo(null, null, 'target_uuid')最后這個(gè)參數(shù)是字段名喲
}
}
// 文章Model
class Post extends Model {
public function likes(){ # 獲取文章所有的點(diǎn)贊
return $this->morphMany('App\Like', 'target');
}
}
// 評(píng)論Model
class Comment extends Model {
public function likes() { # 獲取評(píng)論所有的點(diǎn)贊
return $this->morphMany('App\Like', 'target');
}
}
$comment->likes;
$comment->likes;
$this->morphedByMany('App\Models\Posts', 'target', 'table_name'); // 一種多對(duì)多關(guān)聯(lián)的morphedby
數(shù)據(jù)庫(kù)填充
Laravel使用數(shù)據(jù)填充類來填充數(shù)據(jù),在app/database/seeds/DatabaseSeeder.php
中定義??梢栽谄渲凶远x一個(gè)填充類,但最
好以形式命名,如(默認(rèn)填充類為DatabaseSeeder,只需要在該文件新建類即可,不是新建文件):
class DatabaseSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run()
{
$this->call(UsersTableSeeder::class);
}
}
class UsersTableSeeder extends Seeder
{
/**
* Run the user seeds.
*/
public function run()
{
DB::table('users')->delete();
App\User::create([
'email' => 'admin@haofly.net',
'name' => '系統(tǒng)管理員',
]);
}
}
然后在Composer的命令行里執(zhí)行填充命令
php artisan db:seed
php artisan migrate:refresh --seed //回滾數(shù)據(jù)庫(kù)并重新運(yùn)行所有的填充
ORM操作
# 獲取查詢SQL
DB::connection('default')->enableQueryLog() # 如果不指定連接可以直接DB::enableQueryLog()
... # ORM操作
dd(DB::connection('statistics')->getQueryLog()) # 打印sql
# 查詢
User::all() # 取出所有記錄
User::all(array('id', 'name')) # 取出某幾個(gè)字段
User::find(1) # 根據(jù)主鍵取出一條數(shù)據(jù)
User::findOrFail(1) # 根據(jù)主鍵取出一條數(shù)據(jù)或者拋出異常
User::where([
['id', 1],
['name', 'haofly']
) # where語句能夠傳遞一個(gè)數(shù)組
User::where() # 如果不加->get()或者其他的是不會(huì)真正查詢數(shù)據(jù)庫(kù)的,所以可以用這種方式來拼接,例如$a_where=User::where();$result =$a_where->where()->get();
User::whereIn('name', ['hao', 'fly']) # in查詢
User::whereNull('name') # is null
User::whereNotNull('name') # is not null
User::whereBetween('score', [1, 100]) # where between
User::whereNotBetween('score', [1, 100]) # where not between
User::whereDate('created_at', '2017-05-17')
User::whereMonth('created_at', '5')
User::whereDay('created_at', '17')
User::whereYear('created_at', '2017')
User::whereColumn('first_field', 'second_field') # 判斷兩個(gè)字段是否相等
User::where(...)->orWhere() # or where
User::where()->firstOrFail() # 查找第一個(gè),找不到就拋異常
User::where('user_id', 1)->get()# 返回一個(gè)Collection對(duì)象
User::where(...)->first() # 只取出第一個(gè)model對(duì)象
User::find(1)->logs->where(...) # 關(guān)系中的結(jié)果也能用where等字句
User::->where('updated_at', '>=', date('Y-m-d H:i').':00')->where('updated_at', '<=', date('Y-m-d H:i').':59') # 按分鐘數(shù)查詢
User::find(1)->sum('money') # 求和SUM
User::where(...)->get()->pluck('name') # 只取某個(gè)字段的值,而不是每條記錄取那一個(gè)字段,這是平鋪的,這里的pluck針對(duì)的是一個(gè)Collection,注意,這里只能針對(duì)Collection,千萬不要直接針對(duì)一個(gè)Model,這樣只會(huì)取出那張表的第一條數(shù)據(jù)的那一列
User::select('name')->where() # 也是只取出某個(gè)字段,但是這里不是平鋪的
User::where(...)->pluck('name') # 這是取出單獨(dú)的一個(gè)行的一個(gè)列,不再需要first
User::withTrashed()->where() # 包括軟刪除了的一起查詢
User::onlyTrashed()->where() # 僅查找軟刪除了的
User::find(1)->posts # 取出一對(duì)多關(guān)聯(lián),返回值為Collection
User::find(1)->posts() # 取出一對(duì)多關(guān)聯(lián),返回值為hasMany
User::find(1)->posts->count() # 判斷關(guān)聯(lián)屬性是否存在stackoverflow上面用的這種方法
User::all()->orderBy('name', 'desc') # 按降序排序
User::all()->latest() # 按created_at排序
User::all()->oldest() # 按created_at排序
User::all()->inRandomOrder()->first() # 隨機(jī)順序
# 訪問器,如果在Model里面有定義這樣的方法
public function getNameAttribute(){
return $this->firstname.$this->lastname;
}
那么在外部可以直接$user->name進(jìn)行訪問
# 新增
Model::firstOrCreate() # firstOrCreate的第二個(gè)參數(shù)是5.3才開始的
Model::firstOrNew() # 與上面一句不同的是不會(huì)立馬添加到數(shù)據(jù)庫(kù)里,可以通過$object->new來判斷是否是新添加的,如果該方法不存在那就用$object->exists判斷是否已經(jīng)存在于數(shù)據(jù)庫(kù)中,這個(gè)方法是沒有第二個(gè)參數(shù)的
Model::updateOrCreate(array(), array())
$User::find(1)->phones()->create([]) # 存在著關(guān)聯(lián)的model可以直接新建,而且可以不指定那個(gè)字段,比如這里創(chuàng)建phone的時(shí)候不用指定user_id
$author->posts()->save($post); # 添加hasone或者h(yuǎn)asmany,不過這是針對(duì)新建的
$author->posts()->associate($post); # 這是直接將外鍵設(shè)置為已經(jīng)存在的一個(gè)posts
$author->posts()->saveMany([$post1, $post2]) # 添加hasmany
$post->author()->save(Author::find(1)) # 設(shè)置外鍵
$author->posts()->detach([1,2,3])
$author->posts()->attach([1,2,3=>['expires'=>$expires]])
# 修改
$user->fill(['name' => 'wang'])->save() # fill必須save過后才會(huì)更新到數(shù)據(jù)庫(kù)
$user->update(['name' => 'wang']) # update會(huì)立即更新數(shù)據(jù)庫(kù)
$user->increment('age', 5) # 如果是數(shù)字類型,可以直接相加,不帶5就表示之內(nèi)加1
$user->decrement('age', 5) # 或者減
# 刪除
$user->delete() # 刪除,如果設(shè)置了軟刪除則是軟刪除
$user->forceDelete() # 無論是否設(shè)置軟刪除都物理刪除
# 事務(wù),注意數(shù)據(jù)庫(kù)的連接問題
DB::beginTransaction();
DB::connection('another')->begintransaction();
DB::rollback(); # 5.1之前用的都是rollBack
DB::commit();
查詢緩存
With(預(yù)加載)
with在laravel的ORM中被稱為預(yù)加載,作用與關(guān)聯(lián)查詢上
# 例如要查詢所有文章的作者的名字,可以這樣子做
$posts = App\Post::all();
foreach($posts as $post) {
var_dump($post->user->name);
}
# 但是,這樣做的話,每一篇文章都會(huì)查詢一次用戶,而如果這些文章的用戶都是一個(gè)人,那豈不是要查詢n次了。這時(shí)候預(yù)加載就有用了。
$posts = App\Post::with('user')->get();
foreach ( $books as $book) {
var_dump($post->user->name);
}
# 這樣子做,所有的數(shù)據(jù)在foreach前就都讀取出來了,后面循環(huán)的時(shí)候并沒有查詢數(shù)據(jù)庫(kù),總共只需要查詢2次數(shù)據(jù)庫(kù)。
# with還可以一次多加幾張關(guān)聯(lián)表
App\Post::wth('user', 'author')->get();
# 嵌套使用
App\Post::with('user.phone')->get(); # 取出用戶并且取出其電話
# 也可以不用全部取出來
$users = User::with(['posts' => function ($query) {
$query->where('title', '=', 'test');
}])->get();
Cache
緩存的是結(jié)果
ORM對(duì)象方法
# hasMany對(duì)象的查詢
$posts = User::find(1)->posts() # 返回hasMany對(duì)象,并未真正查詢數(shù)據(jù)庫(kù)
$posts = User::find(1)->posts # 返回Collection對(duì)象,數(shù)據(jù)庫(kù)的查詢結(jié)果集
$posts->get() # 返回Collection對(duì)象,數(shù)據(jù)庫(kù)的查詢結(jié)果集
Collection對(duì)象
$obj->count() # 計(jì)數(shù)
$obj->first() # 取出第一個(gè)對(duì)象
$obj->last() # 取出最后一個(gè)對(duì)象
$obj->isEmpty() # 是否為空
認(rèn)證相關(guān)
授權(quán)Policy
Policy主要用于對(duì)用戶的某個(gè)動(dòng)作添加權(quán)限控制,這里的Policy
并不是對(duì)Controller
的權(quán)限控制.
權(quán)限的注冊(cè)在app/Providers/AuthServiceProvider.php
里面,權(quán)限的注冊(cè)有兩種:
# 一種是直接在boot方法里面進(jìn)行定義
class AuthServiceProvider extends ServiceProvider{
public function boot(GateContract $gate) {
$this->registerPolicies($gate);
$gate->define('update-post', function($user, $post) {
return $user->id === $post->user_id; # 這樣就添加了一個(gè)名為update-post的權(quán)限
} )
$gate->define('update-post', 'Class@method'); # 也可以這樣指定回調(diào)函數(shù)
$gate->before(function ($user, $ability) { # before方法可以凌駕于所有的權(quán)限判斷之上,如果它說可以就可以
if ($user->isSuperAdmin())
return true;
});
$gate->after(function() {})
}
}
# 第二種是創(chuàng)建Policy類,可以用命令php artisan make:policy PostPolicy進(jìn)行創(chuàng)建,會(huì)在Policies里面生成對(duì)應(yīng)的權(quán)限類,當(dāng)然,權(quán)限類創(chuàng)建完了后同樣也需要將該類注冊(cè)到AuthServiceProvider里面去,只需要在其$policies屬性中定義就好了,例如
protected $policies = [
Post::class => PostPolicy::class, # 將權(quán)限類綁定到某個(gè)Model
];
# 權(quán)限類的定義:
class PostPolicy{
public function before($user, $ability){ // 類似的before方法
if ($user->isSuperAdmin()) {return true}
}
public function update(User $user, Post $post){
return true;
}
}
權(quán)限的使用
# 控制器中使用
use Gate;
if (Gate::denies('update-post', $post)) {abort(403, 'Unauthorized action')}
Gate::forUser($user)->allows('update-post', $post) {}
Gate::define('delete-comment', function($user, $post, $comment){}) # 傳遞多個(gè)參數(shù)
Gate::allows('delete-comment', [$post, $comment]) # 也可這樣傳遞多個(gè)參數(shù)
$user->cannot('update-post', $post)
$user->can('update-post', $post)
$user->can('update', $post) # 無論你有好多個(gè)Policy,因?yàn)闄?quán)限類是根據(jù)Model創(chuàng)建的,系統(tǒng)會(huì)自動(dòng)定位到PostPolicy的update中去判斷
$user->can('create', Post::class) # 自動(dòng)定位到某個(gè)model
@can('update-post', $post) # 在模版中使用,如果是create可以這樣@can('create', \App\Post::class)
<html>
@endcan
@can('update-post', $post)
<html1>
@else
<html2>
@endcan
@can('create', \App\Post::class) # Post的創(chuàng)建,針對(duì)PostPolicy
@can('create', [\App\Comment::class, $post]) # Comment的創(chuàng)建,針對(duì)CommentPolicy,并且應(yīng)該這樣子定義:public function create(User $user, $commentClassName, Project $project)
任務(wù)隊(duì)列Job
通過php artisan make:job CronJob
新建隊(duì)列任務(wù),會(huì)在app/Jobs
下新建一個(gè)任務(wù).
# 隊(duì)列里能夠直接在構(gòu)造函數(shù)進(jìn)行注入,例如
public function __construct(ResourceService $resourceService){
$this->resourceService = $resourceService;
}
# 任意地方使用隊(duì)列
dispatch(new App\Jobs\PerformTask);
# 指定隊(duì)列名稱
$jog = (new App\Jobs\..)->onQueue('name');
dispatch($jog);
# 指定延遲時(shí)間
$job = (new App\Jobs\..)->delay(60);
# 任務(wù)出錯(cuò)執(zhí)行
public function failed()
{
echo '失敗了';
}
隊(duì)列消費(fèi)
-
queue:work
: 最推薦使用這種方式,它比queue:listen
占用的資源少得多,不需要每次啟動(dòng)框架。但是代碼如果更新就需要用queue:restart
來重啟
需要注意的是
- 不要在
Jobs
的構(gòu)造函數(shù)里面使用數(shù)據(jù)庫(kù)操作,最多在那里面定義一些傳遞過來的常量,否則隊(duì)列會(huì)出錯(cuò)或者無響應(yīng) - job如果有異常,是不能被catch的,job只會(huì)重新嘗試執(zhí)行該任務(wù),并且默認(rèn)會(huì)不斷嘗試,可以在監(jiān)聽的時(shí)候指定最大嘗試次數(shù)
--tries=3
- 不要將太大的對(duì)象放到隊(duì)列里面去,否則會(huì)超占內(nèi)存,有的對(duì)象本身就有幾兆大小
- 一個(gè)很大的坑是在5.4及以前,由于
queue:work
沒有timeout參數(shù),所以當(dāng)它超過了隊(duì)列配置中的expire
時(shí)間后,會(huì)自動(dòng)重試,但是不會(huì)銷毀以前的進(jìn)程,默認(rèn)是60s,所以如果有耗時(shí)任務(wù)超過60s,那么隊(duì)列很有可能在剛好1分鐘的時(shí)候自動(dòng)新建一條一模一樣的任務(wù),這就導(dǎo)致數(shù)據(jù)重復(fù)的情況。
事件
就是實(shí)現(xiàn)了簡(jiǎn)單的觀察者模式,允許訂閱和監(jiān)聽?wèi)?yīng)用中的事件。用法基本上和隊(duì)列一致,并且如果用上隊(duì)列,那么代碼執(zhí)行上也和隊(duì)列一致了。
事件的注冊(cè)
事件的定義
事件監(jiān)聽器
事件的觸發(fā)
服務(wù)容器
Laravel核心有個(gè)非常非常高級(jí)的功能,那就是服務(wù)容器,用于管理類的依賴,可實(shí)現(xiàn)自動(dòng)的依賴注入。比如,經(jīng)常會(huì)在laravel的控制器的構(gòu)造函數(shù)中看到這樣的代碼:
function function __construct(Mailer $mailer){
$this->mailer = $mailer
}
但是我們卻從來不用自己寫代碼去實(shí)例化Mailer,其實(shí)是由Laravel的服務(wù)容器自動(dòng)去提供類的實(shí)例化了。
# 注冊(cè)進(jìn)容器
$this->app->bind('Mailer', function($app){
return new Mailer('一些構(gòu)造參數(shù)')
});
$this->app->singleton('Mailer', function($app){ # 直接返回的是單例
return new Mailer('一些構(gòu)造參數(shù)')
})
$this->app->instance('Mailer', $mailer) # 如果已經(jīng)有一個(gè)實(shí)例化了的對(duì)象,那么可以通過這種方式將它綁定到服務(wù)容器中去
# 從容器解析出來
$mailer = $this->app->make('Mailer') # 返回一個(gè)實(shí)例
$this->app['Mailer'] # 這樣也可以
public function __construct(Mailer $mailer) # 在控制器、事件監(jiān)聽器、隊(duì)列任務(wù)、過濾器中進(jìn)行注冊(cè)
事件Event
應(yīng)用場(chǎng)景: 1.緩存機(jī)制的松散耦合,比如在獲取一個(gè)資源時(shí)先看是否有緩存,有則直接讀緩存,沒有則走后短數(shù)據(jù)庫(kù),此時(shí),通常做法是在原代碼里面直接用if...else...
進(jìn)行判斷,但有了緩存后,我們可以用事件來進(jìn)行觸發(fā)
重要對(duì)象
Request
$request->route() # 通過request獲取Route對(duì)象
Route
$route->parameters() # 獲取路由上的參數(shù),即不是GET和POST之外的,定義在路由上面的參數(shù)
幫助函數(shù)
# intersect 獲取request的字段來更新字段
$record->update($request->intersect([
'title',
'label',
'year',
'type'
]));
str_contains('Hello foo bar.', 'foo'); # 判斷給定字符串是否包含指定內(nèi)容
str_random(25); # 產(chǎn)生給定長(zhǎng)度的隨機(jī)字符串
錯(cuò)誤和日志
logger
用于直接輸出DEBUG
級(jí)別的日志,更好的是使用use Illuminate\Support\Facades\Log;
,如果storage/laravel.log
下面找不到日志,那么可能是重定向到apache
或者nginx
下面去了
# 日志的用法
Log::useFiles(storage_path().'/logs/laravel.log') # 如果發(fā)現(xiàn)無論什么都不輸入到日志里面去,一是檢查日志文件的權(quán)限,而是添加這個(gè),直接指名日志文件
Log::emergency('緊急情況');
Log::alert('警惕');
Log::critical('嚴(yán)重');
Log::error('錯(cuò)誤');
Log::warning('警告');
Log::notice('注意');
Log::info('This is some useful information.');
Log::debug();
Artisan Console
-
php artisna config:cache
: 把所有的配置文件組合成一個(gè)單一的文件,讓框架能夠更快地去加載。 - 使用命令的方式執(zhí)行腳本,這時(shí)候如果要打印一些日志信息,可以直接用預(yù)定義的方法,還能顯示特定的顏色:
$this->info('') # 綠色
$this->line('') # 黑色
$this->comment('') # 黃色
$this->question('') # 綠色背景
$this->error('') # 紅色背景
測(cè)試
PHP的phpunit提供了很好的測(cè)試方式,Laravel對(duì)其進(jìn)行了封裝,使得易用性更高更方便。
# 訪問頁(yè)面
$this->visit('/')->click('About')->seePageIs('/about-us') # 直接點(diǎn)擊按鈕并察看頁(yè)面
$this->seePageIs('/next') # 驗(yàn)證當(dāng)前url的后綴是不是這個(gè)
$this->visit('/')->see('Laravel 5')->dontSee('Rails') # 查看頁(yè)面是否存在某個(gè)字符串或者不存在
# 用戶登錄
$user = User::find(1)
$this->be($user) # 直接在測(cè)試用例添加這個(gè)即可
Auth::check() # 用戶是否登錄,如果已經(jīng)登錄返回true
# 表單填寫
$this->type($text, $elementName) # 輸入文本
$this->select($value, $elementName) # 選擇一個(gè)單選框或者下拉式菜單的區(qū)域
$this->check($elementName) # 勾選復(fù)選框
$this->attach($pathtofile, $elementName) # 添加一個(gè)文件
$this->press($buttonTextOrElementName) # 按下按鈕
# 如果是復(fù)雜的表單,特別是包含了數(shù)組的表單,可以這樣子
<input name="multi[]" type="checkbox" value="1">
<input name="multi[]" type="checkbox" value="2">
這種的,就不能直接使用上面的方法了,只能怪上面的方法不夠智能呀,解決方法是直接提交一個(gè)數(shù)組
$this->submitForm('提交按鈕', [
'name' => 'name',
'multi' => [1, 2]
]);
# 測(cè)試
$this->seeInDatabase('users', ['email' => 'hehe@example.com']) # 斷言數(shù)據(jù)庫(kù)中存在
# 模型工廠Model Factories,database/factories/ModelFactory.php,可以不用插入數(shù)據(jù)庫(kù),就能直接得到一個(gè)完整的Model對(duì)象,define指定一個(gè)模型,然后把字段拿出來填上想要生成的數(shù)據(jù),例如
$factory->define(App\User::class, function (Faker\Generator $faker) {
return [
'name' => $faker->name,
'password' => bcrypt(str_random(10)),
'remember_token' => str_random(10),
]
});
// 使用的時(shí)候,直接這樣,50表示生成50個(gè)模型對(duì)象
factory(App\User::class, 50)->create()->each(function($u) {
$u->posts()->save(factory(App\Post::class)->make());
});
# 直接對(duì)控制器進(jìn)行測(cè)試可以這樣做
public function setUp(){
$this->xxxController = new xxxController()
}
public function testIndex{
$re = $this->xxxController->index(new Request([]));
var_dump($re->content);
var_dump($re->isSuccessful());
}
在實(shí)際的測(cè)試過程中,我有這樣的幾點(diǎn)體會(huì):
- 測(cè)試類本身就不應(yīng)該繼承的,因?yàn)閱卧獪y(cè)試本身就應(yīng)該獨(dú)立開來
- 直接對(duì)控制器測(cè)試是一種簡(jiǎn)單直接有效的測(cè)試方法,而無需再單獨(dú)給service或者model層進(jìn)行測(cè)試