前言
laravel 提供了一個(gè)靈活的模式屁倔,那就是 facade 〔嫒ぃ框架內(nèi)部的 DB泞边、Auth、File 等功能也有相關(guān)的 facade 實(shí)現(xiàn)疗杉。那么繁堡,該如何寫自己的 facade 呢?
Facade 是什么乡数?
首先椭蹄,facade 并不是 laravel 獨(dú)有的東西,它就是設(shè)計(jì)模式中的外觀模式(Facade)净赴。
當(dāng)然绳矩,這里就不長(zhǎng)篇大論去討論外觀模式的定義了。這篇文章寫的很不錯(cuò) : 設(shè)計(jì)模式(九)外觀模式Facade(結(jié)構(gòu)型)玖翅。
那么翼馆,laravel 的 facade 做了什么?
同樣的金度, laravel 實(shí)現(xiàn)了外觀模式的開關(guān)功能应媚,并且使用魔術(shù)方法 __callstatic 實(shí)現(xiàn)了靜態(tài)方式調(diào)用、動(dòng)態(tài)創(chuàng)建對(duì)象的功能猜极。參考 (官方文檔)
當(dāng)然你可能覺得這些概念很抽象中姜,都什么玩意。那么其實(shí)簡(jiǎn)單的講跟伏,laravel 的 facade 就是將某些功能封裝成工具類丢胚,而且能以靜態(tài)方式調(diào)用工具類的方法。
建立自己的 facade
首先受扳、以 laravel 5.1 框架携龟,我之前寫過的 Geoip facade 為例,說一下怎么去建立自己的 facade勘高。
下載 geoip 擴(kuò)展
geoip 是一個(gè)可以更具 IP 獲取國(guó)家峡蟋、地域坟桅、城市信息的 PHP 擴(kuò)展,基于 maxmind 數(shù)據(jù)庫(kù)蕊蝗。 github 在此仅乓。
首先,為 laravel 添加 geoip 擴(kuò)展匿又。
打開 composer.json,添加 "geoip2/geoip2": "~2.0" 到 require建蹄。
項(xiàng)目根目錄運(yùn)行 composer update ( 需要安裝 composer )更新一下碌更,geoip 的依賴和軟件包就被下載到 vendor 文件夾中了。
然后下載 geoip 依賴的數(shù)據(jù)庫(kù)洞慎,免費(fèi)庫(kù)的地址 : GeoLite2
我下載了 GeoLite2 Country 和 GeoLite2 City 庫(kù)痛单,放到了 storage/geoipdb 中。
建立 facade
在 app 目錄下新建 Facades 文件夾劲腿,里面新建 Facades/GeoIP/GeoIP.php 和 Facades/GeoIP/Facade/GeoIP.php (建議每個(gè)功能新建一個(gè)文件夾區(qū)分旭绒,比如我這里給 GeoIP 新建一個(gè)文件夾,關(guān)于GeoIP 的東西全放到這里)
注意焦人,F(xiàn)acades/GeoIP 下的 GeoIP.php 是你要對(duì) geoip 擴(kuò)展進(jìn)行封裝的類挥吵, Facades/GeoIP/Facade 下的 GeoIP.php 是你的 facade,用來給 laravel 解析使用花椭,這兩個(gè)文件可以不同名忽匈。
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 現(xiàn)在只有一個(gè)方法,返回了一個(gè)字符串 'geoip' , 這個(gè)字符串是一個(gè)標(biāo)號(hào)矿辽,用來給 laravel 的服務(wù)提供者解析使用的丹允。
Facades/GeoIP/GeoIP.php 如下(吐槽:寫的有點(diǎn)隨意)
<?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);
// 國(guó)家信息
$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;
// 經(jīng)緯度
$data['latitude'] = $record->location->latitude;
$data['longitude'] = $record->location->longitude;
return $data;
}
}
OK袋倔,現(xiàn)在 geoip 的常用功能已經(jīng)封裝到方法中了雕蔽。
注冊(cè)服務(wù)
完成了 facade 的創(chuàng)建和功能封裝,下面就要使用它了宾娜。自己創(chuàng)建的 facade 要在 laravel 使用是要進(jìn)行注冊(cè)的批狐,以便 laraval 在啟動(dòng)時(shí)能自動(dòng)注入依賴(請(qǐng)看 laravel 的依賴注入簡(jiǎn)介 : laravel 依賴注入 學(xué)院君)
編寫服務(wù)提供者
在 app/Providers 下新建 FacadesServiceProvider.php
可以手動(dòng)建,也可以用 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
{
/**
* 在容器中注冊(cè)綁定。
*
* @return void
*/
public function register()
{
$this->app->singleton('geoip', function ($app) {
return new GeoIP($app);
});
}
}
上面代碼可知嘱根,服務(wù)提供者注冊(cè)時(shí)會(huì)注冊(cè)一個(gè)單例髓废,標(biāo)號(hào)為 'geoip',也就是我們自己的 facade 返回的那個(gè)该抒,然后回調(diào)函數(shù)會(huì)返回一個(gè)對(duì)象慌洪,也就是我們封裝 geoip 功能的那個(gè)類的實(shí)例顶燕,不明白的同學(xué)可以看看 laravel 的服務(wù)提供者和服務(wù)容器相關(guān)知識(shí)哦。(注意要 use 將 facade 和封裝類的命名空間引用一下哦)
注冊(cè)服務(wù)提供者
laravel 5.1 以上版本的話冈爹, config/app.php 中找到 providers 和 aliases 涌攻,將你的服務(wù)提供者和 facade 別名配置一下 :
providers 加入 :
App\Providers\FacadeServiceProvider::class,
aliases 加入(不用每次都寫很長(zhǎng)的命名空間前綴) :
'GeoIP' => App\Facades\GeoIP\Facade\GeoIP::class,
對(duì)于 lumen 5.2 以上,需要在 bootstrap/app.php 中添加
$app->register(App\Providers\FacadesServiceProvider::class);
注:放到AppServiceProvider類的register方法里更合理一些
$this->app->register(App\Providers\FacadesServiceProvider::class);
注冊(cè)完畢后频伤,每次使用 facade::function 的時(shí)候恳谎,laravel 會(huì)自動(dòng)解析 facade, 然后創(chuàng)建一個(gè)對(duì)象給用戶使用憋肖,因痛,而無(wú)需用戶自己去 new 一個(gè)對(duì)象出來。
使用
現(xiàn)在岸更,在任何一個(gè)控制器鸵膏,或者路由的回調(diào)函數(shù)中,使用
$res = GeoIP::getCountry('75.101.195.215');
var_dump($res);
你會(huì)發(fā)現(xiàn)怎炊,facade 已經(jīng)可以好好工作了谭企,enjoy!
參考文章
【1】設(shè)計(jì)模式(九)外觀模式Facade(結(jié)構(gòu)型)
【2】Laravel 服務(wù)容器實(shí)例教程 —— 深入理解控制反轉(zhuǎn)(IoC)和依賴注入(DI)
【3】Laravel 服務(wù)提供者實(shí)例教程 —— 創(chuàng)建 Service Provider 測(cè)試實(shí)例