Laravel框架一:原理機制篇

http://www.cnblogs.com/XiongMaoMengNan/p/6644892.html

Laravel作爲在國內國外都頗爲流行的PHP框架,風格優雅,其擁有自己的一些特點。

 

. 請求週期

  Laravel 採用了單一入口模式,應用的所有請求入口都是 public/index.php 文件。

  1. 註冊類文件自動加載器:Laravel通過composer進行依賴管理,並在bootstrap/autoload.php中註冊了Composer Auto Loader (PSR-4),應用中類的命名空間將被映射到類文件實際路徑,不再需要開發者手動導入各種類文件,而由自動加載器自行導入。因此,Laravel允許你在應用中定義的類可以自由放置在Composer Auto Loader能自動加載的任何目錄下,但大多數時候還是建議放置在app目錄下或app的某個子目錄下
  2. 創建服務容器:從 bootstrap/app.php 文件中取得 Laravel 應用實例 $app (服務容器)
  3. 創建 HTTP / Console 內核:傳入的請求會被髮送給 HTTP 內核或者 console 內核進行處理,HTTP 內核繼承自 Illuminate\Foundation\Http\Kernel 類。它定義了一個 bootstrappers 數組,數組中的類在請求真正執行前進行前置執行,這些引導程序配置了錯誤處理,日誌記錄,檢測應用程序環境,以及其他在請求被處理前需要完成的工作;HTTP 內核同時定義了一個 HTTP 中間件列表,所有的請求必須在處理前通過這些中間件處理 HTTP session 的讀寫,判斷應用是否在維護模式, 驗證 CSRF token 等等
  4. 載入服務提供者至容器:在內核引導啓動的過程中最重要的動作之一就是載入服務提供者到你的應用,服務提供者負責引導啓動框架的全部各種組件,例如數據庫、隊列、驗證器以及路由組件。因爲這些組件引導和配置了框架的各種功能,所以服務提供者是整個 Laravel 啓動過程中最爲重要的部分,所有的服務提供者都配置在 config/app.php 文件中的 providers 數組中。首先,所有提供者的 register 方法會被調用;一旦所有提供者註冊完成,接下來,boot 方法將會被調用
  5. 分發請求:一旦應用完成引導和所有服務提供者都註冊完成,Request 將會移交給路由進行分發。路由將分發請求給一個路由或控制器,同時運行路由指定的中間件

     

二. 服務容器和服務提供者

  服務容器是 Laravel 管理類依賴和運行依賴注入的有力工具,在類中可通過 $this->app 來訪問容器,在類之外通過 $app 來訪問容器;服務提供者是 Laravel 應用程序引導啓動的中心,關係到服務提供者自身、事件監聽器、路由以及中間件的啓動運行。應用程序中註冊的路由通過RouteServiceProvider實例來加載;事件監聽器在EventServiceProvider類中進行註冊;中間件又稱路由中間件,在app/Http/Kernel.php類文件中註冊,調用時與路由進行綁定。在新創建的應用中,AppServiceProvider 文件中方法實現都是空的,這個提供者是你添加應用專屬的引導和服務的最佳位置,當然,對於大型應用你可能希望創建幾個服務提供者,每個都具有粒度更精細的引導。服務提供者在 config/app.php 配置文件中的providers數組中進行註冊

複製代碼
<?php

namespace App\Providers;

use Riak\Connection;
use Illuminate\Support\ServiceProvider;

class RiakServiceProvider extends ServiceProvider
{
    /**
     * 在容器中註冊綁定
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton(Connection::class, function ($app) {
            return new Connection(config('riak'));
        });
    }
}
複製代碼

  

三. 依賴注入

  Laravel 實現依賴注入方式有兩種:自動注入和主動註冊。自動注入通過參數類型提示由服務容器自動注入實現;主動註冊則需開發人員通過綁定機制來實現,即綁定服務提供者或類(參考: http://d.laravel-china.org/docs/5.4/container )。

  1. 綁定服務提供者或類:這種方式對依賴注入的實現可以非常靈活多樣
    複製代碼
    use Illuminate\Support\Facades\Storage;
    use App\Http\Controllers\PhotoController;
    use App\Http\Controllers\VideoController;
    use Illuminate\Contracts\Filesystem\Filesystem;
    
    $this->app->when(PhotoController::class)
              ->needs(Filesystem::class)
              ->give(function () {
                  return Storage::disk('local');
              });
    
    $this->app->when(VideoController::class)
              ->needs(Filesystem::class)
              ->give(function () {
                  return Storage::disk('s3');
              });
    複製代碼
  2. 參數類型聲明:通過對類的構造器參數類型、類的方法參數類型、閉包的參數類型給出提示來實現
    複製代碼
    <?php
    
    namespace App\Http\Controllers;
    
    use App\Users\Repository as UserRepository;
    
    class UserController extends Controller
    {
        /**
         * user repository 實例。
         */
        protected $users;
    
        /**
         * 控制器構造方法。
         *
         * @param  UserRepository  $users
         * @return void
         */
        public function __construct(UserRepository $users)
        {
            $this->users = $users;
        }
    
        /**
         * 儲存一個新用戶。
         *
         * @param  Request  $request
         * @return Response
         */
        public function store(Request $request)
        {
            $name = $request->input('name');
    
            //
        }
    }
    複製代碼
  3. 路由參數依賴:下邊的示例使用 Illuminate\Http\Request 類型提示的同時還獲取到路由參數id
    複製代碼
    你的路由可能是這樣定義的:
    Route::put('user/{id}', 'UserController@update');
    
    而控制器對路由參數id的依賴卻可能是這樣實現的:
    <?php
    
    namespace App\Http\Controllers;
    
    use Illuminate\Http\Request;
    
    class UserController extends Controller
    {
        /**
         * 更新指定的用戶。
         *
         * @param  Request  $request
         * @param  string  $id
         * @return Response
         */
        public function update(Request $request, $id)
        {
            //
        }
    }
    複製代碼

     

四. Artisan Console 

  Laravel利用PHP的CLI構建了強大的Console工具artisan,artisan幾乎能夠創建任何你想要的模板類以及管理配置你的應用,在開發和運維管理中扮演着極其重要的角色,artisan是Laravel開發不可或缺的工具。在Laravel根目錄下運行:PHP artisan list可查看所有命令列表。用好artisan能極大地簡化開發工作,並減少錯誤發生的可能;另外,還可以編寫自己的命令。下面列舉部分比較常用的命令:

  • 啓用維護模式:php artisan down --message='Upgrading Database' --retry=60
  • 關閉維護模式:php artisan up
  • 生成路由緩存:php artisan route:cache
  • 清除路由緩存:php artisan route:clear
  • 數據庫遷移 Migrations:php artisan make:migration create_users_table --create=users
  • 創建資源控制器:php artisan make:controller PhotoController --resource --model=Photo
  • 創建模型及遷移:php artisan make:model User -m

 

五. 表單驗證機制

  表單驗證在web開發中是不可或缺的,其重要性也不言而喻,也算是每個web框架的標配部件了。Laravel表單驗證擁有標準且龐大的規則集,通過規則調用來完成數據驗證,多個規則組合調用須以“|”符號連接,一旦驗證失敗將自動回退並可自動綁定視圖。

  下例中,附加bail規則至title屬性,在第一次驗證required失敗後將立即停止驗證;“.”語法符號在Laravel中通常表示嵌套包含關係,這個在其他語言或框架語法中也比較常見

$this->validate($request, [
    'title' => 'bail|required|unique:posts|max:255',
    'author.name' => 'required',
    'author.description' => 'required',
]);

Laravel驗證規則參考 http://d.laravel-china.org/docs/5.4/validation#可用的驗證規則 ;另外,在Laravel開發中還可採用如下擴展規則:

  1. 自定義FormRequest (須繼承自 Illuminate\Foundation\Http\FormRequest )
  2. Validator::make()手動創建validator實例
  3. 創建validator實例驗證後鉤子
  4. 按條件增加規則
  5. 數組驗證
  6. 自定義驗證規則

 

六. 事件機制

  Laravel事件機制是一種很好的應用解耦方式,因爲一個事件可以擁有多個互不依賴的監聽器。事件類 (Event) 類通常保存在 app/Events 目錄下,而它們的監聽類 (Listener) 類被保存在 app/Listeners 目錄下,使用 Artisan 命令來生成事件和監聽器時他們會被自動創建。

  1. 註冊事件和監聽器:EventServiceProvider的 listen 屬性數組用於事件(鍵)到對應的監聽器(值)的註冊,然後運行 php artisan event:generate將自動生成EventServiceProvider中所註冊的事件(類)模板和監聽器模板,然後在此基礎之上進行修改來實現完整事件和監聽器定義;另外,你也可以在 EventServiceProvider 類的 boot 方法中通過註冊閉包事件來實現
  2. 定義事件(類):事件(類)就是一個包含與事件相關信息數據的容器,不包含其它邏輯
    複製代碼
     1 <?php
     2 
     3 namespace App\Events;
     4 
     5 use App\Order;
     6 use Illuminate\Queue\SerializesModels;
     7 
     8 class OrderShipped
     9 {
    10     use SerializesModels;
    11 
    12     public $order;
    13 
    14     /**
    15      * 創建一個事件實例。
    16      *
    17      * @param  Order  $order
    18      * @return void
    19      */
    20     public function __construct(Order $order)
    21     {
    22         $this->order = $order;
    23     }
    24 }
    複製代碼
  3. 定義監聽器:事件監聽器在 handle 方法中接受了事件實例作爲參數
    複製代碼
     1 <?php
     2 
     3 namespace App\Listeners;
     4 
     5 use App\Events\OrderShipped;
     6 
     7 class SendShipmentNotification
     8 {
     9     /**
    10      * 創建事件監聽器。
    11      *
    12      * @return void
    13      */
    14     public function __construct()
    15     {
    16         //
    17     }
    18 
    19     /**
    20      * 處理事件
    21      *
    22      * @param  OrderShipped  $event
    23      * @return void
    24      */
    25     public function handle(OrderShipped $event)
    26     {
    27         // 使用 $event->order 來訪問 order ...
    28     }
    29 }
    複製代碼
  4. 停止事件傳播:在監聽器的 handle 方法中返回 false 來停止事件傳播到其他的監聽器
  5. 觸發事件:調用 event 輔助函數可觸發事件,事件將被分發到它所有已經註冊的監聽器上
    複製代碼
     1 <?php
     2 
     3 namespace App\Http\Controllers;
     4 
     5 use App\Order;
     6 use App\Events\OrderShipped;
     7 use App\Http\Controllers\Controller;
     8 
     9 class OrderController extends Controller
    10 {
    11     /**
    12      * 將傳遞過來的訂單發貨。
    13      *
    14      * @param  int  $orderId
    15      * @return Response
    16      */
    17     public function ship($orderId)
    18     {
    19         $order = Order::findOrFail($orderId);
    20 
    21         // 訂單的發貨邏輯...
    22 
    23         event(new OrderShipped($order));
    24     }
    25 }
    複製代碼
  6. 隊列化事件監聽器:如果監聽器中需要實現一些耗時的任務,比如發送郵件或者進行 HTTP 請求,那把它放到隊列中處理是非常有用的。在使用隊列化監聽器,須在服務器或者本地環境中配置隊列並開啓一個隊列監聽器,還要增加 ShouldQueue 接口到你的監聽器類;如果你想要自定義隊列的連接和名稱,你可以在監聽器類中定義 $connection 和 $queue 屬性;如果隊列監聽器任務執行次數超過在工作隊列中定義的最大嘗試次數,監聽器的 failed 方法將會被自動調用
    複製代碼
     1 <?php
     2 
     3 namespace App\Listeners;
     4 
     5 use App\Events\OrderShipped;
     6 use Illuminate\Contracts\Queue\ShouldQueue;
     7 
     8 class SendShipmentNotification implements ShouldQueue
     9 {
    10     /**
    11      * 隊列化任務使用的連接名稱。
    12      *
    13      * @var string|null
    14      */
    15     public $connection = 'sqs';
    16 
    17     /**
    18      * 隊列化任務使用的隊列名稱。
    19      *
    20      * @var string|null
    21      */
    22     public $queue = 'listeners';
    23 
    24     public function failed(OrderShipped $event, $exception)
    25     {
    26         //
    27     }       
    28 }
    複製代碼
  7. 事件訂閱者:事件訂閱者允許在單個類中定義多個事件處理器,還應該定義一個 subscribe 方法,這個方法接受一個事件分發器的實例,通過調用事件分發器的 listen 方法來註冊事件監聽器,然後在 EventServiceProvider 類的 $subscribe 屬性中註冊訂閱者

    複製代碼
     1 <?php
     2 
     3 namespace App\Listeners;
     4 
     5 class UserEventSubscriber
     6 {
     7     /**
     8      * 處理用戶登錄事件。
     9      */
    10     public function onUserLogin($event) {}
    11 
    12     /**
    13      * 處理用戶註銷事件。
    14      */
    15     public function onUserLogout($event) {}
    16 
    17     /**
    18      * 爲訂閱者註冊監聽器。
    19      *
    20      * @param  Illuminate\Events\Dispatcher  $events
    21      */
    22     public function subscribe($events)
    23     {
    24         $events->listen(
    25             'Illuminate\Auth\Events\Login',
    26             'App\Listeners\UserEventSubscriber@onUserLogin'
    27         );
    28 
    29         $events->listen(
    30             'Illuminate\Auth\Events\Logout',
    31             'App\Listeners\UserEventSubscriber@onUserLogout'
    32         );
    33     }
    34 
    35 }
    複製代碼

 

七. Eloquent 模型

  Eloquent ORM 以ActiveRecord形式來和數據庫進行交互,擁有全部的數據表操作定義,單個模型實例對應數據表中的一行

1 $flights = App\Flight::where('active', 1)
2                ->orderBy('name', 'desc')
3                ->take(10)
4                ->get(); 

  config/database.php中包含了模型的相關配置項。Eloquent 模型約定:

  1. 數據表名:模型以單數形式命名(CamelCase),對應的數據表爲蛇形複數名(snake_cases),模型的$table屬性也可用來指定自定義的數據表名稱
  2. 主鍵:模型默認以id爲主鍵且假定id是一個遞增的整數值,也可以通過 
incrementing = false時間戳:模型會默認在你的數據庫表有 created_at 和 updated_at 字段,設置 dateFormat 屬性用於在模型中設置自己的時間戳格式數據庫連接:模型默認會使用應用程序中配置的數據庫連接,如果你想爲模型指定不同的連接,可以使用 $connection 屬性自定義批量賦值:當用戶通過 HTTP 請求傳入了非預期的參數,並藉助這些參數 create 方法更改了數據庫中你並不打算要更改的字段,這時就會出現批量賦值(Mass-Assignment)漏洞,所以你需要先在模型上定義一個  guarded(黑名單,禁止批量賦值字段名數組)
1 // 用屬性取回航班,當結果不存在時創建它...
2 $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']);
3 
4 // 用屬性取回航班,當結果不存在時實例化一個新實例...
5 $flight = App\Flight::firstOrNew(['name' => 'Flight 10']);
模型軟刪除:如果模型有一個非空值 deleted_at,代表模型已經被軟刪除了。要在模型上啓動軟刪除,則必須在模型上使用Illuminate\Database\Eloquent\SoftDeletes trait 並添加 deleted_at 字段到你的模型 $dates 屬性上和數據表中,通過調用trashed方法可查詢模型是否被軟刪除
複製代碼
 1 <?php
 2 
 3 namespace App;
 4 
 5 use Illuminate\Database\Eloquent\Model;
 6 use Illuminate\Database\Eloquent\SoftDeletes;
 7 
 8 class Flight extends Model
 9 {
10     use SoftDeletes;
11 
12     /**
13      * 需要被轉換成日期的屬性。
14      *
15      * @var array
16      */
17     protected $dates = ['deleted_at'];
18 }
複製代碼
查詢作用域:Laravel允許對模型設定全局作用域和本地作用域(包括動態範圍),全局作用域允許我們爲模型的所有查詢添加條件約束(定義一個實現 Illuminate\Database\Eloquent\Scope 接口的類),而本地作用域允許我們在模型中定義通用的約束集合(模型方法前加上一個 scope 前綴)。作用域總是返回查詢構建器
複製代碼
 1 全局作用域定義 2 <?php
 3 
 4 namespace App\Scopes;
 5 
 6 use Illuminate\Database\Eloquent\Scope;
 7 use Illuminate\Database\Eloquent\Model;
 8 use Illuminate\Database\Eloquent\Builder;
 9 
10 class AgeScope implements Scope
11 {
12     /**
13      * 應用作用域
14      *
15      * @param  \Illuminate\Database\Eloquent\Builder  $builder
16      * @param  \Illuminate\Database\Eloquent\Model  $model
17      * @return void
18      */
19     public function apply(Builder $builder, Model $model)
20     {
21         return $builder->where('age', '>', 200);
22     }
23 }
24 
25 本地作用域26 <?php
27 
28 namespace App;
29 
30 use Illuminate\Database\Eloquent\Model;
31 
32 class User extends Model
33 {
34     /**
35      * 限制查詢只包括受歡迎的用戶。
36      *
37      * @return \Illuminate\Database\Eloquent\Builder
38      */
39     public function scopePopular($query)
40     {
41         return $query->where('votes', '>', 100);
42     }
43 
44     /**
45      * 限制查詢只包括活躍的用戶。
46      *
47      * @return \Illuminate\Database\Eloquent\Builder
48      */
49     public function scopeActive($query)
50     {
51         return $query->where('active', 1);
52     }
53 }
54 
55 動態範圍56 <?php
57 
58 namespace App;
59 
60 use Illuminate\Database\Eloquent\Model;
61 
62 class User extends Model
63 {
64     /**
65      * 限制查詢只包括指定類型的用戶。
66      *
67      * @return \Illuminate\Database\Eloquent\Builder
68      */
69     public function scopeOfType($query, $type)
70     {
71         return $query->where('type', $type);
72     }
73 }
複製代碼
隱藏和顯示屬性:模型  
  1. visible 屬性用於顯示屬性和關聯的輸出,另外makeVisible()還可用來臨時修改可見性。當你要對關聯進行隱藏時,需使用關聯的方法名稱,而不是它的動態屬性名稱
    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 在數組中可見的屬性。
    11      *
    12      * @var array
    13      */
    14     protected $visible = ['first_name', 'last_name'];
    15 }
    16 ?>
    17 
    18 //makeVisible()用來臨時修改可見性
    19 return $user->makeVisible('attribute')->toArray();
    複製代碼
  2. 訪問器和修改器:訪問器(getFooAttribute)和修改器(setFooAttribute)可以讓你修改 Eloquent 模型中的屬性或者設置它們的值,比如你想要使用 Laravel 加密器來加密一個被保存在數據庫中的值,當你從 Eloquent 模型訪問該屬性時該值將被自動解密。訪問器和修改器要遵循cameCase命名規範,修改器會設置值到 Eloquent 模型內部的 $attributes 屬性上

    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 獲取用戶的名字。
    11      *
    12      * @param  string  $value
    13      * @return string
    14      */
    15     public function getFirstNameAttribute($value)
    16     {
    17         return ucfirst($value);
    18     }
    19 
    20      /**
    21      * 設定用戶的名字。
    22      *
    23      * @param  string  $value
    24      * @return void
    25      */
    26     public function setFirstNameAttribute($value)
    27     {
    28         $this->attributes['first_name'] = strtolower($value);
    29     }
    30 }
    複製代碼

    而對於訪問器與修改器的調用將是模型對象自動進行的

    1 $user = App\User::find(1);
    2 $user->first_name = 'Sally';//將自動調用相應的修改器
    3 $firstName = $user->first_name;//將自動調用相應的訪問器 
  3. 追加屬性:在轉換模型到數組或JSON時,你希望添加一個在數據庫中沒有對應字段的屬性,首先你需要爲這個值定義一個 訪問器,然後添加該屬性到改模型的 appends 屬性中
    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class User extends Model
     8 {
     9      /**
    10      * 訪問器被附加到模型數組的形式。
    11      *
    12      * @var array
    13      */
    14     protected $appends = ['is_admin'];
    15 
    16     /**
    17      * 爲用戶獲取管理者的標記。
    18      *
    19      * @return bool
    20      */
    21     public function getIsAdminAttribute()
    22     {
    23         return $this->attributes['admin'] == 'yes';
    24     }
    25 }
    複製代碼
  4. 屬性類型轉換:$casts 屬性數組在模型中提供了將屬性轉換爲常見的數據類型的方法,且鍵是那些需要被轉換的屬性名稱,值則是代表字段要轉換的類型。支持的轉換的類型有:integer、real、float、double、string、boolean、object、array、collection、date、datetime、timestamp
    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 應該被轉換成原生類型的屬性。
    11      *
    12      * @var array
    13      */
    14     protected $casts = [
    15         'is_admin' => 'boolean',//is_admin 屬性以整數(0 或 1)被保存在我們的數據庫中,把它轉換爲布爾值
    16     ];
    17 }
    複製代碼
  5. 序列化: Laravel模型及關聯可遞歸序列化成數組或JSON

    複製代碼
     1 //單個模型實例序列化成數組
     2 $user = App\User::with('roles')->first();
     3 return $user->toArray();
     4 //集合序列化成數組
     5 $users = App\User::all();
     6 return $users->toArray();
     7 
     8 //單個模型實例序列化成JSON
     9 $user = App\User::find(1);
    10 return $user->toJson();
    11 //直接進行string轉換會將模型或集合序列化成JSON
    12 $user = App\User::find(1);
    13 return (string) $user;
    14 //因此你可以直接從應用程序的路由或者控制器中返回 Eloquent 對象
    15 Route::get('users', function () {
    16     return App\User::all();
    17 });
    複製代碼
  6. 關聯(方法)與動態屬性:在 Eloquent 模型中,關聯被定義成方法(methods),也可以作爲強大的查詢語句構造器
    1 $user->posts()->where('active', 1)->get();

    Eloquent 模型支持多種類型的關聯:一對一、一對多、多對多、遠層一對多、多態關聯、多態多對多關聯

    舉個例子,一個 User 模型會關聯一個 Phone 模型,一對一關聯(hasOne)
    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class User extends Model
     8 {
     9     /**
    10      * 獲取與用戶關聯的電話號碼
    11      */
    12     public function phone()
    13     {
    14         return $this->hasOne('App\Phone');
    15     }
    16 }
    複製代碼

    動態屬性允許你訪問關聯方法,使用 Eloquent 的動態屬性來獲取關聯記錄,如同他們是定義在模型中的屬性

    1 $phone = User::find(1)->phone;

    Eloquent 會假設對應關聯的外鍵名稱是基於模型名稱的。在這個例子裏,它會自動假設 Phone 模型擁有 user_id 外鍵。如果你想要重寫這個約定,則可以傳入第二個參數到 hasOne 方法裏

    1 return $this->hasOne('App\Phone', 'foreign_key');

    如果你想讓關聯使用 id 以外的值,則可以傳遞第三個參數至 hasOne 方法來指定你自定義的鍵

    1 return $this->hasOne('App\Phone', 'foreign_key', 'local_key');

    如果我們要在 Phone 模型上定義一個反向關聯,此關聯能夠讓我們訪問擁有此電話的 User 模型。我們可以定義與 hasOne 關聯相對應的 belongsTo 方法

    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use Illuminate\Database\Eloquent\Model;
     6 
     7 class Phone extends Model
     8 {
     9     /**
    10      * 獲取擁有該電話的用戶模型。
    11      */
    12     public function user()
    13     {
    14         return $this->belongsTo('App\User');
    15     }
    16
    複製代碼
  7. 模型事件: Laravel爲模型定義的事件包括creating, created, updating, updated, saving, saved, deleting, deleted, restoring, restored。 模型上定義一個 $events 屬性

    複製代碼
     1 <?php
     2 
     3 namespace App;
     4 
     5 use App\Events\UserSaved;
     6 use App\Events\UserDeleted;
     7 use Illuminate\Notifications\Notifiable;
     8 use Illuminate\Foundation\Auth\User as Authenticatable;
     9 
    10 class User extends Authenticatable
    11 {
    12     use Notifiable;
    13 
    14     /**
    15      * 模型的時間映射。
    16      *
    17      * @var array
    18      */
    19     protected $events = [
    20         'saved' => UserSaved::class,
    21         'deleted' => UserDeleted::class,
    22     ];
    23 }
    複製代碼

     如果你在一個給定的模型中監聽許多事件,也可使用觀察者將所有監聽器變成一個類,類的一個方法就是一個事件監聽器

    複製代碼
     1 定義觀察者 2 <?php
     3 
     4 namespace App\Observers;
     5 
     6 use App\User;
     7 
     8 class UserObserver
     9 {
    10     /**
    11      * 監聽用戶創建的事件。
    12      *
    13      * @param  User  $user
    14      * @return void
    15      */
    16     public function created(User $user)
    17     {
    18         //
    19     }
    20 
    21     /**
    22      * 監聽用戶刪除事件。
    23      *
    24      * @param  User  $user
    25      * @return void
    26      */
    27     public function deleting(User $user)
    28     {
    29         //
    30     }
    31 }
    32 
    33 註冊觀察者34 <?php
    35 
    36 namespace App\Providers;
    37 
    38 use App\User;
    39 use App\Observers\UserObserver;
    40 use Illuminate\Support\ServiceProvider;
    41 
    42 class AppServiceProvider extends ServiceProvider
    43 {
    44     /**
    45      * 運行所有應用.
    46      *
    47      * @return void
    48      */
    49     public function boot()
    50     {
    51         User::observe(UserObserver::class);
    52     }
    53 
    54     /**
    55      * 註冊服務提供.
    56      *
    57      * @return void
    58      */
    59     public function register()
    60     {
    61         //
    62     }
    63 }
    複製代碼

 

八. Laravel的Restful風格

  一般認爲Restful風格的資源定義不包含操作,但是在Laravel中操作(動詞)也可作爲一種資源來定義。下圖是對Laravel中資源控制器操作原理的描述,可以看到,create、edit就直接出現在了URI中,它們是一種合法的資源。對於create和edit這兩種資源的訪問都採用GET方法來實現,第一眼看到頓感奇怪,後來嘗試通過artisan console生成資源控制器,並注意到其對create、edit給出註釋“ Show the form for ”字樣,方知它們只是用來展現表單而非提交表單的。

 

九. 擴展開發

  我們知道,Laravel本身是基於Composer管理的一個包,遵循Composer的相關規範,可以通過Composer來添加所依賴的其他Composer包,因此在做應用的擴展開發時,可以開發Composer包然後引入項目中即可;另外也可開發基於Laravel的專屬擴展包。下面所講的就是Laravel的專屬擴展開發,最好的方式是使用 contracts ,而不是 facades,因爲你開發的包並不能訪問所有 Laravel 提供的測試輔助函數,模擬 contracts 要比模擬 facade 簡單很多。

  • 服務提供者:服務提供者是你的擴展包與 Laravel 連接的重點,須定義自己的服務提供者並繼承自 Illuminate\Support\ServiceProvider 基類
  • 路由:若要爲你的擴展包定義路由,只需在包的服務提供者的 boot 方法中傳遞 routes 文件路徑到 loadRoutesFrom 方法即可
    複製代碼
    1 /**
    2  * 在註冊後進行服務的啓動。
    3  *
    4  * @return void
    5  */
    6 public function boot()
    7 {
    8     $this->loadRoutesFrom(__DIR__.'/path/to/routes.php');
    9 }
    複製代碼
  • 配置文件:你可以選擇性地將擴展包的配置文件發佈(publishes)到應用程序本身的config目錄上或者合併(mergeConfigFrom)到應用程序裏的副本配置文件中,但不應在配置文件中定義閉包函數,當執行 config:cache Artisan命令時,它們將不能正確地序列化
    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * 用戶使用 vendor:publish 命令可將擴展包的文件將會被複制到指定的位置上。
     5  *
     6  * @return void
     7  */
     8 public function boot()
     9 {
    10     $this->publishes([
    11         __DIR__.'/path/to/config/courier.php' => config_path('courier.php'),
    12     ]);
    13 }
    14 
    15 $value = config('courier.option');//只要你的配置文件被髮布,就可以如其它配置文件一樣被訪問
    16 
    17 /**
    18  * 或者選擇性在容器中註冊綁定。
    19  *
    20  * 此方法僅合併配置數組的第一級。如果您的用戶部分定義了多維配置數組,則不會合並缺失的選項
    21  *
    22  * @return void
    23  */
    24 public function register()
    25 {
    26     $this->mergeConfigFrom(
    27         __DIR__.'/path/to/config/courier.php', 'courier'
    28     );
    29 }
    複製代碼
  • 數據庫遷移:如果你的擴展包包含數據庫遷移,需要使用 loadMigrationsFrom 方法告知 Laravel 如何去加載它們。在運行 php artisan migrate 命令時,它們就會自動被執行,不需要把它們導出到應用程序的 database/migrations 目錄
    複製代碼
    1 /**
    2  * 在註冊後進行服務的啓動。
    3  *
    4  * @return void
    5  */
    6 public function boot()
    7 {
    8     $this->loadMigrationsFrom(__DIR__.'/path/to/migrations');
    9 }
    複製代碼
  • 語言包:如果你的擴展包裏面包含了本地化,則可以使用 loadTranslationsFrom 方法來告知 Laravel 該如何加載它們。下例假設你的包名稱爲courier
    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->loadTranslationsFrom(__DIR__.'/path/to/translations', 'courier');
     9 
    10     //如果不想發佈語言包至應用程序的 resources/lang/vendor 目錄,請註銷對$this->publishes()調用。運行 Laravel 的 vendor:publish Artisan 命令可將擴展包的語言包複製到指定的位置上
    11     $this->publishes([
    12         __DIR__.'/path/to/translations' => resource_path('lang/vendor/courier'),
    13     ]);
    14 }
    15 
    16 echo trans('courier::messages.welcome');//擴展包翻譯參照使用了雙分號 package::file.line 語法
    複製代碼

     

  • 視圖:若要在 Laravel 中註冊擴展包 視圖,則必須告訴 Laravel 你的視圖位置,loadViewsFrom 方法允許傳遞視圖模板路徑與擴展包名稱兩個參數。需要特別指出的是,當你使用 loadViewsFrom 方法時,Laravel 實際上爲你的視圖註冊了兩個位置:一個是應用程序的 resources/views/vendor 目錄,另一個是你所指定的目錄。Laravel會先檢查 resources/views/vendor 目錄是否存在待加載視圖,如果不存在,纔會從指定的目錄去加載,這個方法可以讓用戶很方便的自定義或重寫擴展包視圖。

    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->loadViewsFrom(__DIR__.'/path/to/views', 'courier');
     9 
    10     //若要發佈擴展包的視圖至 resources/views/vendor 目錄,則必須使用服務提供者的 publishes 方法。運行 Laravel 的 vendor:publish Artisan 命令時,擴展包的視圖將會被複制到指定的位置上 
    11     $this->publishes([
    12         __DIR__.'/path/to/views' => resource_path('views/vendor/courier'),
    13     ]); 
    14 }
    15 
    16 //擴展包視圖參照使用了雙分號 package::view 語法
    17 Route::get('admin', function () {
    18     return view('courier::admin');
    19 });
    複製代碼
  • 命令:使用 commands 方法給擴展包註冊 Artisan 命令,命令的定義要遵循Laravel Artisan 命令規範

    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     if ($this->app->runningInConsole()) {
     9         $this->commands([
    10             FooCommand::class,
    11             BarCommand::class,
    12         ]);
    13     }
    14 }
    複製代碼
  • 公用 Assets:你可以發佈像 JavaScript、CSS 和圖片這些資源文件到應用程序的 public 目錄上。當用戶執行 vendor:publish 命令時,您的 Assets 將被複制到指定的發佈位置。由於每次更新包時通常都需要覆蓋資源,因此您可以使用 --force 標誌:php artisan vendor:publish --tag=public --force

    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->publishes([
     9         __DIR__.'/path/to/assets' => public_path('vendor/courier'),
    10     ], 'public');
    11 }
    複製代碼

     

  • 發佈羣組文件:你可能想讓用戶不用發佈擴展包的所有資源文件,只需要單獨發佈擴展包的配置文件即可,通過在調用 publishes 方法時使用標籤來實現

    複製代碼
     1 /**
     2  * 在註冊後進行服務的啓動。
     3  *
     4  * @return void
     5  */
     6 public function boot()
     7 {
     8     $this->publishes([
     9         __DIR__.'/../config/package.php' => config_path('package.php')
    10     ], 'config');
    11 
    12     $this->publishes([
    13         __DIR__.'/../database/migrations/' => database_path('migrations')
    14     ], 'migrations');
    15 }
    複製代碼

    對於上例運行命令 php artisan vendor:publish --tag=config 時將忽略掉migrations部分

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章