Slim容器分析
5年前,我還沒什么編程經(jīng)驗,第一次接觸java的spring框架,了解容器容器的概念,立刻被它巧妙的設(shè)計所驚呆,沒錯,就是驚呆...沒想到程序居然可以這么寫!!
不是從上至下的命令式編程,不是分而治之的結(jié)構(gòu)式編程,也不是我當(dāng)時水平所認(rèn)知的自底向上,相互作用的對象式編程,而是可復(fù)用,可替換的組件化編程。
后來一直做PHP Web應(yīng)用開發(fā),也沒機會用Spring做一些應(yīng)用,一直在想PHP什么時候也能有使用容器的框架就好了。
一次偶然機會,在一個技術(shù)qq群,有人推薦一個叫Slim的框架,我隨手打開github,看看這個框架源碼,又驚呆了,Slim里有容器,而且驚嘆現(xiàn)在的PHP框架怎么越來越像Java Web的框架,有容器,有組件,全OOP。
Slim的源代碼地址Github
附Slim資料鏈接:
以日志組件為例,來看看PHP是怎么配置組件,怎么講組件注入容器,怎么實例化組件,以及何時實例化組件和調(diào)用組件的方法?
入口文件
Slim/public/index.php
所有請求都是發(fā)送給入口文件,然后由入口文件分發(fā)請求到相應(yīng)的服務(wù),入口文件很簡單,我截取了和主題相關(guān)的部分。
<?php
// 包含應(yīng)用配置文件
$settings = require __DIR__ . '/../src/settings.php';
// 初始化應(yīng)用
$app = new \Slim\App($settings);
// 注入應(yīng)用所依賴組件
// Set up dependencies
require __DIR__ . '/../src/dependencies.php';
配置組件
Slim/src/settings.php
其中就包含了logger組件的配置信息:
日志組件的名字:slim-app
日志組件的log記錄保存的位置:_DIR_ . '/../logs/app.log',
<?php
return [
'settings' => [
'displayErrorDetails' => true, // set to false in production
'addContentLengthHeader' => false, // Allow the web server to send the content-length header
// Renderer settings
'renderer' => [
'template_path' => __DIR__ . '/../templates/',
],
// Monolog settings
'logger' => [
'name' => 'slim-app',
'path' => __DIR__ . '/../logs/app.log',
],
],
];
注入組件 - 依賴注入
應(yīng)用初始化之后,開始向容器注入應(yīng)用所依賴的組件。
在Slim/src/dependencies.php里面定義了應(yīng)用所依賴的組件,比如模板組件、日志組件、數(shù)據(jù)庫組件等等。
我們就取其中l(wèi)ogger組件來分析分析
<?php
// monolog
$container['logger'] = function ($container) {
$settings = $container->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], Monolog\Logger::DEBUG));
return $logger;
};
這里將一個回調(diào)函數(shù)賦值給一個容器實例的“l(fā)ogger”屬性,
研究一下這個回調(diào)函數(shù):
回調(diào)函數(shù)的參數(shù)是一個容器實例,回調(diào)函數(shù)體通過這個容器實例獲取logger組件的配置信息,根據(jù)配置信息實例化組件,最后返回這個組件實例。
將這樣一個實例化組件的回調(diào)函數(shù)交給容器,就實現(xiàn)logger組件的注入——這種注入,通過回調(diào)函數(shù)注入依賴是依賴注入的一種實現(xiàn)方法。
這也是控制反轉(zhuǎn)的一種實現(xiàn),把原本由應(yīng)用程序?qū)嵗M件,交給了低層容器去做。
實例化組件
那么把實例化得控制權(quán)交給容器,那么容器什么時候?qū)嵗M件呢?
答案是,在第一次調(diào)用組件的時候。
Slim/src/routes.php
<?php
app->get('/[{name}]', function ($request, $response, $args) {
// Sample log message
$this->logger->info("Slim-Skeleton '/' route");
// Render index view
return $this->renderer->render($response, 'index.phtml', $args);
});
在執(zhí)行下面語句時,如果logger組件沒有實例化,就實例logger組件,將實例保存在容器中,并且返回logger組件實例;如果容器中已經(jīng)有l(wèi)ogger組件的實例,就返回該實例——單例模式。
<?php
$this->logger->info("Slim-Skeleton '/' route");
$this指向容器,這里使用了php的魔術(shù)方法__get()去獲取容器的內(nèi)的屬性。
Slim/vendor/slim/slim/Slim/Container.php
<?php
/********************************************************************************
* Magic methods for convenience
*******************************************************************************/
public function __get($name)
{
return $this->get($name);
}
最后調(diào)用下面方法放回logger組件的實例。
<?php
public function offsetGet($id)
{
if (!isset($this->keys[$id])) {
throw new \InvalidArgumentException(sprintf('Identifier "%s" is not defined.', $id));
}
if (
isset($this->raw[$id])
|| !is_object($this->values[$id])
|| isset($this->protected[$this->values[$id]])
|| !method_exists($this->values[$id], '__invoke')
) {
return $this->values[$id];
}
if (isset($this->factories[$this->values[$id]])) {
return $this->values[$id]($this);
}
$raw = $this->values[$id];
$val = $this->values[$id] = $raw($this);
$this->raw[$id] = $raw;
$this->frozen[$id] = true;
return $val;
}
其中最關(guān)鍵是這一句
<?php
$val = $this->values[$id] = $raw($this);
$raw是前面提到的logger的回調(diào)函數(shù),通過$raw($this)去調(diào)用回調(diào)函數(shù),返回logger組件的實例。
緊接著做了兩件事:
一是賦值給$this->values[$id],作為一個單例保存在容器中,之后再次調(diào)用logger組件時,直接返回這個單例。
二是將logger組件實例賦值給$val,作為整個方法的返回值,返回到logger組件的調(diào)用處,也就是回到了之前調(diào)用logger組件的info()方法處,見下面代碼,這樣就能寫日志到app.log文件里了,
Slim/src/routes.php
<?php
$this->logger->info("Slim-Skeleton '/' route");
現(xiàn)代制造工業(yè)模式
容器組件化編程,讓我想起現(xiàn)代制造工業(yè)模式,比如汽車制造業(yè)。
最初汽車制造商所有汽車零件都是自己生產(chǎn)組裝。
現(xiàn)代汽車廠商已經(jīng)將汽車零件外包給第三方工廠。
汽車制造商只需要與第三方工廠簽到合同,提供標(biāo)準(zhǔn)。
第三方工廠自行安排具體的零件生產(chǎn)工作。
汽車制造商需要汽車時,就從第三方工廠取貨,組裝汽車。
在這里汽車制造商就是就是容器,汽車就是應(yīng)用程序,汽車零件就是組件。
看來不僅面向?qū)ο缶幊淌菍ΜF(xiàn)實的抽象,軟件設(shè)計思想也是來源現(xiàn)實世界的抽象
最后總結(jié)下來,Slim容器有2個特點:
- 使用回調(diào)函數(shù)實現(xiàn)依賴注入,達(dá)到控制反轉(zhuǎn)的目的。
- 在使用組件時,才實例化組件,并單例化。