Laravel 創建自己的 Facade

博客原文:Laravel 創建自己的 Facade

前言

laravel 提供了一個靈活的模式,那就是 facade 。框架內部的 DB、Auth、File 等功能也有相關的 facade 實現。那麼,該如何寫自己的 facade 呢?

Facade 是什麼?

首先,facade 並不是 laravel 獨有的東西,它就是設計模式中的外觀模式(Facade)。
當然,這裏就不長篇大論去討論外觀模式的定義了。這篇文章寫的很不錯 : 設計模式(九)外觀模式Facade(結構型)
那麼,laravel 的 facade 做了什麼?
同樣的, laravel 實現了外觀模式的開關功能,並且使用魔術方法 __callstatic 實現了靜態方式調用、動態創建對象的功能。參考 (官方文檔)

當然你可能覺得這些概念很抽象,都什麼玩意。那麼其實簡單的講,laravel 的 facade 就是將某些功能封裝成工具類,而且能以靜態方式調用工具類的方法。

建立自己的 facade

首先、以 laravel 5.1 框架,我之前寫過的 Geoip facade 爲例,說一下怎麼去建立自己的 facade。

下載 geoip 擴展

geoip 是一個可以更具 IP 獲取國家、地域、城市信息的 PHP 擴展,基於 maxmind 數據庫。 github 在此

首先,爲 laravel 添加 geoip 擴展。
打開 composer.json,添加 “geoip2/geoip2”: “~2.0” 到 require。
項目根目錄運行 composer update ( 需要安裝 composer )更新一下,geoip 的依賴和軟件包就被下載到 vendor 文件夾中了。

然後下載 geoip 依賴的數據庫,免費庫的地址 : GeoLite2

我下載了 GeoLite2 Country 和 GeoLite2 City 庫,放到了 storage/geoipdb 中。

建立 facade

在 app 目錄下新建 Facades 文件夾,裏面新建 Facades/GeoIP/GeoIP.php 和 Facades/GeoIP/Facade/GeoIP.php (建議每個功能新建一個文件夾區分,比如我這裏給 GeoIP 新建一個文件夾,關於GeoIP 的東西全放到這裏)
注意,Facades/GeoIP 下的 GeoIP.php 是你要對 geoip 擴展進行封裝的類, Facades/GeoIP/Facade 下的 GeoIP.php 是你的 facade,用來給 laravel 解析使用,這兩個文件可以不同名。
image.png

Facades/GeoIP/Facade/GeoIP.php 如下

<?php

namespace App\Facades\GeoIP\Facade;

use Illuminate\Support\Facades\Facade;

class GeoIP extends Facade
{
    protected static function getFacadeAccessor()
    {
        return 'geoip';
    }
}

注意你的 facade 現在只有一個方法,返回了一個字符串 ‘geoip’ , 這個字符串是一個標號,用來給 laravel 的服務提供者解析使用的。

Facades/GeoIP/GeoIP.php 如下(吐槽:寫的有點隨意)

<?php

namespace App\Facades\GeoIP;

use GeoIp2\Database\Reader;

class GeoIP
{
    /**
     * GeoIP country db path (base on storage_path).
     *
     * @var GeoIP
     */
    private $_country_db = 'geoipdb/GeoLite2-Country.mmdb';

    /**
     * GeoIP city db path (base on storage_path).
     *
     * @var GeoIP
     */
    private $_city_db = 'geoipdb/GeoLite2-City.mmdb';

    /**
     * Instance for GeoIP .
     *
     * @var GeoIP
     */
    private $_instance;

    /**
     * Init instance.
     *
     */
    public function init($mode)
    {
        switch ($mode) {
          case 'getCountry':
            $path = $this->_country_db;
            break;
          case 'getCity':
            $path = $this->_city_db;
            break;
          default:
            break;
        }

        $this->_instance = new Reader(storage_path($path));
    }

    /**
     * Get Country infomations.
     *
     * @param  String  $ip
     * @return Array
     */
    public function getCountry($ip)
    {
      $this->init(__FUNCTION__);

      $record = $this->_instance->country($ip);

      // 國家信息
      $data['iso_code'] = $record->country->isoCode;
      $data['country_name'] = $record->country->name;
      $data['country_name_zh_cn'] = $record->country->names['zh-CN'];

      return $data;
    }
 
 /**
     * Get City infomations.
     *
     * @param  String  $ip
     * @return Array
     */
    public function getCity($ip)
    {
      $this->init(__FUNCTION__);

      $record = $this->_instance->city($ip);

      $data['iso_code'] = $record->country->isoCode;
      $data['country_name'] = $record->country->name;
      $data['country_name_zh_cn'] = $record->country->names['zh-CN'];

      // 省、州信息
      $data['sub_division_name'] = $record->mostSpecificSubdivision->name;
      $data['sub_division_name_zh_cn'] = $record->mostSpecificSubdivision->names['zh-CN'];
      $data['sub_division_code'] = $record->mostSpecificSubdivision->isoCode;

      // 城市信息
      $data['city_name'] = $record->city->name;
      $data['postal_code'] = $record->postal->code;

      // 經緯度
      $data['latitude'] = $record->location->latitude;
      $data['longitude'] = $record->location->longitude;

      return $data;
    }
}

OK,現在 geoip 的常用功能已經封裝到方法中了。

註冊服務

完成了 facade 的創建和功能封裝,下面就要使用它了。自己創建的 facade 要在 laravel 使用是要進行註冊的,以便 laraval 在啓動時能自動注入依賴(請看 laravel 的依賴注入簡介 : laravel 依賴注入 學院君)

編寫服務提供者

在 app/Providers 下新建 FacadesServiceProvider.php
可以手動建,也可以用 artisan 命令來生成,隨你喜歡。
app/Providers/FacadesServiceProvider.php 代碼如下:

<?php

namespace App\Providers;

use App\Service\ApiService;
use Illuminate\Support\ServiceProvider;

// include the class facade binded
use App\Facades\GeoIP\GeoIP;

class FacadesServiceProvider extends ServiceProvider
{
    /**
     * 在容器中註冊綁定。
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('geoip', function ($app) {
            return new GeoIP($app);
        });
    }
}

上面代碼可知,服務提供者註冊時會註冊一個單例,標號爲 ‘geoip’,也就是我們自己的 facade 返回的那個,然後回調函數會返回一個對象,也就是我們封裝 geoip 功能的那個類的實例,不明白的同學可以看看 laravel 的服務提供者和服務容器相關知識哦。(注意要 use 將 facade 和封裝類的命名空間引用一下哦)

註冊服務提供者

laravel 5.1 以上版本的話, config/app.php 中找到 providers 和 aliases ,將你的服務提供者和 facade 別名配置一下 :

providers 加入 :

App\Providers\FacadeServiceProvider::class,
aliases 加入(不用每次都寫很長的命名空間前綴) :

'GeoIP' => App\Facades\GeoIP\Facade\GeoIP::class,
對於 lumen 5.2 以上,需要在 bootstrap/app.php 中添加
$app->register(App\Providers\FacadesServiceProvider::class);
注:放到AppServiceProvider類的register方法裏更合理一些
$this->app->register(App\Providers\FacadesServiceProvider::class);
註冊完畢後,每次使用 facade::function 的時候,laravel 會自動解析 facade, 然後創建一個對象給用戶使用,,而無需用戶自己去 new 一個對象出來。

使用
現在,在任何一個控制器,或者路由的回調函數中,使用

$res = GeoIP::getCountry('75.101.195.215');
var_dump($res);

你會發現,facade 已經可以好好工作了,enjoy!

參考文章

【1】設計模式(九)外觀模式Facade(結構型)
【2】Laravel 服務容器實例教程 —— 深入理解控制反轉(IoC)和依賴注入(DI)
【3】Laravel 服務提供者實例教程 —— 創建 Service Provider 測試實例

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