可以將別名視為特殊的常量變量,他的作用在于避免將一些文件路徑、URL以硬編碼的方式 寫入代碼中,或者多處出現一長串的文件路徑、URL。
## 預定義的別名[](http://www.digpage.com/aliases.html#id1 "Permalink to this headline")
Yii中,別名以?@?開頭,以區別于正常的文件路徑和URL。Yii中預定義了許多常用的 別名。別名的定義一般放在應用的最開始的階段進行,比如引導階段、初始化階段等。 這樣可以保證后續代碼可以使用這些定義好的別名。
### 配置文件中的別名[](http://www.digpage.com/aliases.html#id2 "Permalink to this headline")
別名一般放在?digpage.com\common\config\bootstrap.php?, 或者digpage.com\frontend\config\bootstrap.php?等?bootstrap.php?文件中定義。比如:
~~~
<?php
Yii::setAlias('common', dirname(__DIR__));
Yii::setAlias('frontend', dirname(dirname(__DIR__)) . '/frontend');
Yii::setAlias('backend', dirname(dirname(__DIR__)) . '/backend');
Yii::setAlias('console', dirname(dirname(__DIR__)) . '/console');
~~~
在此定義的別名通過入口腳本引入Yii應用中,具體可以看看?[_入口文件index.php_](http://www.digpage.com/app_struct.html#entry-script)?部分的內容。上面的bootstrap.php?文件定義了?@common?,?@frontend?,?@backend?和?@console?4個別名。 開發者也可以自己在?bootstrap.php?中加入自己的別名定義,這是最常運用的定義別名的方式。
### Yii預定義的別名[](http://www.digpage.com/aliases.html#yii "Permalink to this headline")
相比較于通過?bootstrap.php?的方式定義別名,有的別名就不那么直觀了,可能沒法一下子看到。 而且,這些別名相對比較固定,與Yii框架和應用自身息息相關。這類別名直接寫到Yii的代碼中去了。 對于這類Yii框架預定義的別名,一般不去修改他。改他的收益遠小于造成的副作用,這種得不償失的虧本買賣, 高智商人群是不會去干的。
這些預定義的別名,主要分布在?yii\BaseYii?和?yii\base\Application?等類中。
在?yii\BaseYii?中:
~~~
// 定義了 @yii 別名
public static $aliases = ['@yii' => __DIR__];
~~~
yii\BaseYii::$aliases?用于保存整個Yii應用的所有的別名。 這里默認地把?yii\BaseYii.php?所在的目錄作為?@yii?別名。
另外,對于?yii\base\Application?在其構造函數?__construct()?中,會調用以下代碼:
~~~
public function preInit(&$config)
{
... ...
// basePath必須在配置文件中給出,否則會拋出棄常
if (isset($config['basePath'])) {
// 這里會設置 @app
$this->setBasePath($config['basePath']);
unset($config['basePath']);
} else {
throw new InvalidConfigException(
'The "basePath" configuration for the Application is required.');
}
// @vendor 如果配置文件中設置了 vendorPath 使用配置的值,否則使用默認的
if (isset($config['vendorPath'])) {
$this->setVendorPath($config['vendorPath']);
unset($config['vendorPath']);
} else {
$this->getVendorPath();
}
// @runtime 如果配置文件中設置了 runtimePath ,就使用配置的值,否則使用默認的
if (isset($config['runtimePath'])) {
$this->setRuntimePath($config['runtimePath']);
unset($config['runtimePath']);
} else {
$this->getRuntimePath();
}
... ...
}
~~~
上面的代碼中,預定義了5個別名:?@app?,?@vendor?@bower?@npm?,?@runtime?。 上面的代碼中,basePath?不是別名,但必須由開發者自己在配置文件中設定,表示應用的根目錄。 對于frontend而言,就是目錄?path/to/digpage.com/frontend?。 在定義?basePath?時,Yii順便定義了?@app?,代碼在yii\base\Application::setBasePath()?中:
~~~
public function setBasePath($path)
{
parent::setBasePath($path);
Yii::setAlias('@app', $this->getBasePath());
}
~~~
可以看出,?@app?與?basePath?是一致的。
在?yii\base\Application?的初始化過程中,與設置?basePath?類似, 在配置?vendorPath?runtimePath時,Yii會調用?setVendorPath()?setRuntimePath()?。 如果未在配置文件中對這兩個配置項作出設置,Yii會調用?getVendorPath()?和?getRuntimePath()?, 這兩個函數最終也會調用相應的set函數對這些別名進行定義。
@vendor?,?@bower?,?@npm?和?@runtime?這4個別名就由這兩個set函數定義:
~~~
public function getVendorPath()
{
// 在未設置vendorPath時,使用默認值
if ($this->_vendorPath === null) {
$this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
}
return $this->_vendorPath;
}
// 這里定義了3個別名
public function setVendorPath($path)
{
$this->_vendorPath = Yii::getAlias($path);
Yii::setAlias('@vendor', $this->_vendorPath);
Yii::setAlias('@bower', $this->_vendorPath . DIRECTORY_SEPARATOR . 'bower');
Yii::setAlias('@npm', $this->_vendorPath . DIRECTORY_SEPARATOR . 'npm');
}
public function getRuntimePath()
{
// 在未設置runtimePath時,使用默認值
if ($this->_runtimePath === null) {
$this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
}
return $this->_runtimePath;
}
// 這里定義了 @runtime 別名
public function setRuntimePath($path)
{
$this->_runtimePath = Yii::getAlias($path);
Yii::setAlias('@runtime', $this->_runtimePath);
}
~~~
對于上面的代碼,默認情況下,會有:
* @app?,必須由開發者在配置文件中提供,一般為配置文件的?dirname(__DIR__)?。 即digpage.com/frontend?之類的目錄。
* @vendor?,一般定義為?@app/vendor?,高級模板中則定義為?@app/../vendor
* @bower?,定義為?@vendor/bower
* @npm?,定義為?@vendor/npm
* @runtime?,定義為?@app/runtime
但是,這里有一個比較特殊的,就是?@vendor?。 對于使用Yii基礎模版創建的應用而言,會使用上面提到的?@app/vendor?。 但是,對于使用高級模版創建的應用,你會發現,vendor目錄并不在 frontend 或 backend 目錄下, 而是跟他們是兄弟目錄。這是因為對于整個工程而言,這個vendor的內容是 frontend 和 backend等共用的。 放在 frontend 或 backend 都不合適,干脆就地提拔吧。 這一點我們在前面?[_Yii應用的目錄結構和入口腳本_](http://www.digpage.com/app_struct.html#app-struct)?已經講過了。
因此,實際上高級應用模版的?@vendor?應該是?@app/../vendor?,上面的代碼顯然不適用。 對此,貼心的Yii也已經考慮到了。在使用高級模板創建應用時,?digpage.com/common/config/main.php?配置文件會重新設定?vendorPath
~~~
'vendorPath' => dirname(dirname(__DIR__)) . '/vendor'
~~~
這樣就避免了使用代碼中默認的值。
對于Web應用,?yii\base\Web\Application?中又定義了?@webroot?和?@web?2個別名:
~~~
protected function bootstrap()
{
$request = $this->getRequest();
Yii::setAlias('@webroot', dirname($request->getScriptFile()));
Yii::setAlias('@web', $request->getBaseUrl());
parent::bootstrap();
}
~~~
這里?@webroot?就是入口腳本?index.php?所在的目錄。 而?@web?則是URL別名,表示當前應用的根URL地址 。
最后一個藏有別名的地方,在于Yii的擴展(extensions)。 當使用Composer安裝擴展后,會向@vendor/yiisoft/extensions.php?寫入信息,其中就包含相應的別名。 只不過這些別名通常都是二級別名。然后,在?yii\base\Application::bootstrap()?中,將這些擴展的別名進行注冊。
首先看看一個典型的?extensions.php
~~~
<?php
$vendorDir = dirname(__DIR__);
return array (
'yiisoft/yii2-swiftmailer' =>
array (
'name' => 'yiisoft/yii2-swiftmailer',
'version' => '9999999-dev',
'alias' =>
array (
'@yii/swiftmailer' => $vendorDir . '/yiisoft/yii2-swiftmailer',
),
),
... ...
'yiisoft/yii2-gii' =>
array (
'name' => 'yiisoft/yii2-gii',
'version' => '9999999-dev',
'alias' =>
array (
'@yii/gii' => $vendorDir . '/yiisoft/yii2-gii',
),
),
);
~~~
注意上面這段代碼中的?alias?,這個鍵對應的就是一個別名及其所代表的實際路徑。 至于具體對這個?extensions.php?的內容進行處理并注冊成別名的工作, 是由?yii\base\Application::bootstrap()?完成:
~~~
protected function bootstrap()
{
// 將 extensions.php 的內容讀取進 $this->extensions 備用
if ($this->extensions === null) {
$file = Yii::getAlias('@vendor/yiisoft/extensions.php');
$this->extensions = is_file($file) ? include($file) : [];
}
// 遍歷 $this->extensions 并注冊別名
foreach ($this->extensions as $extension) {
if (!empty($extension['alias'])) {
foreach ($extension['alias'] as $name => $path) {
Yii::setAlias($name, $path);
}
}
... ...
}
}
~~~
經過上面這些代碼,我們的各種插件也有了自己的別名,如上面的?@yii\swiftmailer?,?@yii\gii等,常見的還有?@yii\bootstrap?等。
### 所有預定義的別名[](http://www.digpage.com/aliases.html#id3 "Permalink to this headline")
小結一下,默認預定義別名一共有12個,其中路徑別名11個,URL別名只有?@web?1個:
* @yii?表示Yii框架所在的目錄,也是?yii\BaseYii?類文件所在的位置;
* @app?表示正在運行的應用的根目錄,一般是?digpage.com/frontend?;
* @vendor?表示Composer第三方庫所在目錄,一般是?@app/vendor?或?@app/../vendor?;
* @bower?表示Bower第三方庫所在目錄,一般是?@vendor/bower?;
* @npm?表示NPM第三方庫所在目錄,一般是?@vendor/npm?;
* @runtime?表示正在運行的應用的運行時用于存放運行時文件的目錄,一般是?@app/runtime?;
* @webroot?表示正在運行的應用的入口文件?index.php?所在的目錄,一般是?@app/web;
* @web?URL別名,表示當前應用的根URL,主要用于前端;
* @common?表示通用文件夾;
* @frontend?表示前臺應用所在的文件夾;
* @backend?表示后臺應用所在的文件夾;
* @console?表示命令行應用所在的文件夾;
* 其他使用Composer安裝的Yii擴展注冊的二級別名。
這樣,在整個Yii應用中,只要使用上述別名,就可方便、且統一地表示特定的路徑或URL。
## 定義與解析別名[](http://www.digpage.com/aliases.html#id4 "Permalink to this headline")
Yii使用?Yii::$aliases[]?來保存別名, 定義別名就是將別名及其代表的實際路徑或URL寫入這個數組, 而解析別名就是將別名的信息從數組讀取出去并組合。
### 別名的定義過程[](http://www.digpage.com/aliases.html#id5 "Permalink to this headline")
除了像上面的代碼那樣定義一個別名之外,還有其他的用法:
~~~
// 使用一個路徑定義一個路徑別名
Yii::setAlias('@foo', 'path/to/foo');
// 使用一個URL定義一個URL別名
Yii::setAlias('@bar', 'http://www.example.com');
// 使用一個別名定義另一個別名
Yii::setAlias('@fooqux', '@foo/qux');
// 定義一個“二級”別名
Yii::setAlias('@foo/bar', 'path/to/foo/bar');
~~~
從上面的代碼中可以了解到,?Yii::setAlias()?是定義別名的關鍵。 實際上,該方法的代碼在BaseYii::setAlias()?中:
~~~
public static function setAlias($alias, $path)
{
// 如果擬定義的別名并非以@打頭,則在前面加上@
if (strncmp($alias, '@', 1)) {
$alias = '@' . $alias;
}
// 找到別名的第一段,即@ 到第一個 / 之間的內容,如@foo/bar/qux的@foo
$pos = strpos($alias, '/');
$root = $pos === false ? $alias : substr($alias, 0, $pos);
if ($path !== null) {
// 去除路徑末尾的 \ / 。如果路徑本身就是一個別名,直接解析出來
$path = strncmp($path, '@', 1) ? rtrim($path, '\\/')
: static::getAlias($path);
// 檢查是否有 $aliases[$root],
// 看看是否已經定義好了根別名。如果沒有,則以$root為鍵,保存這個別名
if (!isset(static::$aliases[$root])) {
if ($pos === false) {
static::$aliases[$root] = $path;
} else {
static::$aliases[$root] = [$alias => $path];
}
// 如果 $aliases[$root] 已經存在,則替換成新的路徑,或增加新的路徑
} elseif (is_string(static::$aliases[$root])) {
if ($pos === false) {
static::$aliases[$root] = $path;
} else {
static::$aliases[$root] = [
$alias => $path,
$root => static::$aliases[$root],
];
}
} else {
static::$aliases[$root][$alias] = $path;
krsort(static::$aliases[$root]);
}
// 當傳入的 $path 為 null 時,表示要刪除這個別名。
} elseif (isset(static::$aliases[$root])) {
if (is_array(static::$aliases[$root])) {
unset(static::$aliases[$root][$alias]);
} elseif ($pos === false) {
unset(static::$aliases[$root]);
}
}
}
~~~
對于別名的定義過程:
別名規范化
如果要定義的別名?$alias?并非以?@?打頭,自動為這個別名加上?@?前綴。 總之,只要是別名,必然以?@?打頭。下面的兩個語句,都定義了相同的別名?@foo
~~~
Yii::setAlias('foo', 'path/to/foo');
Yii::setAlias('@foo', 'path/to/foo');
~~~
獲取根別名
$alias?的根別名,就是?@?加上第一個?/?之間地內容,以?$root?表示。 這里可以看出,別名是分層次的。下面3個語句的根別名都是?@foo
~~~
Yii::setAlias('@foo', 'path/to/some/where');
Yii::setAlias('@foo/bar', 'path/to/some/where');
Yii::setAlias('@foo/bar/qux', 'path/to/some/where');
~~~
新定義別名還是刪除別名
如果傳入的?$path?不是?null?,說明是正常的別名定義。 對于正常的別名定義,就是往BaseYii::$aliases[]?里寫入信息。 而如果?$path?為?null?,說明是要刪除別名:
~~~
// 定義別名@foo
Yii::setAlias('@foo', 'path/to/some/where');
// 刪除別名@foo
Yii::setAlias('@foo', null);
~~~
解析?$path
對于新定義別名,既然?$path?不為?null?,那么先進行解析: 如果?$path?以?@?打頭,說明這也是一個別名,則調用?Yii::getAlias()?, 并將解析后的結果作為新的?$path?; 如果?$path?不以?@?打頭,說明是一個正常的path 或 URL, 那么去除?$path?末尾的?/?和?\?。
別名的寫入
對于全新的別名,也即其根別名是新的,?BaseYii::aliases[$root]?不存在。 那么全新別名的寫入分兩種情況: 如果全新別名本身就是根別名,那么直接?BaseYii::aliases[$alias]?=?$path?; 而如果全新的別名并非是一個根別名,即形如?@foo/bar?帶有二級、三級等路徑的,BaseYii::aliases[$root]?=?[$alias?=>?$path]?。比如:
~~~
// BaseYii::aliases['@foo'] = ['@foo/bar' => 'path/to/foo/bar']
Yii::setAlias('@foo/bar', 'path/to/foo/bar');
// BaseYii::aliases['@qux'] = 'path/to/qux'
Yii::setAlias('@qux', 'path/to/qux');
~~~
而對于根別名已經存在的別名,在寫入時,就要考慮覆蓋、新增的問題了:
~~~
// 初始 BaseYii::aliases['@foo'] = 'path/to/foo'
Yii::setAlias('@foo', 'path/to/foo');
// 直接覆蓋 BaseYii::aliases['@foo'] = 'path/to/foo2'
Yii::setAlias('@foo', 'path/to/foo2');
/**
* 新增
* BaseYii::aliases['@foo'] = [
* '@foo/bar' => 'path/to/foo/bar',
* '@foo' => 'path/to/foo2',
* ];
*/
Yii::setAlias('@foo/bar', 'path/to/foo/bar');
// 初始 BaseYii::aliases['@bar'] = ['@bar/qux' => 'path/to/bar/qux'];
Yii::setAlias('@bar/qux', 'path/to/bar/qux');
// 直接覆蓋 BaseYii::aliases['@bar'] = ['@bar/qux' => 'path/to/bar/qux2'];
Yii::setAlias('@bar/qux', 'path/to/bar/qux2');
/**
* 新增
* BaseYii::aliases['@bar'] = [
* '@bar/foo' => 'path/to/bar/foo',
* '@bar/qux' => 'path/to/bar/qux2',
* ];
*/
Yii::setAlias('@bar/foo', 'path/to/bar/foo');
~~~
注意如果根別名對應的是一個數組,在新增、覆蓋后, Yii會調用PHP的?krsort()?把數組按照鍵值重新逆向排序。 這可以有效確保長的別名會放在短的類以別名前面, 比如,?@foo/bar/qux?和@foo/bar?同樣被放在根別名?@foo?之下, 但長的那個,會被放在前面。
別名的刪除
傳入的?$path?為?null?表示要刪除別名。 Yii使用PHP的?unset()?注銷?BaseYii::$aliases[]?數組中的對應元素, 達到刪除別名的目的。注意刪除別名后,不需要調用?krsort()?對數組進行處理。
### 別名的解析過程[](http://www.digpage.com/aliases.html#id6 "Permalink to this headline")
與定義過程使用?Yii::setAlias()?相對應,別名的解析過程使用?Yii::getAlias()?, 實際代碼在BaseYii::getAlias()?中:
~~~
public static function getAlias($alias, $throwException = true)
{
// 一切不以@打頭的別名都是無效的
if (strncmp($alias, '@', 1)) {
return $alias;
}
// 先確定根別名 $root
$pos = strpos($alias, '/');
$root = $pos === false ? $alias : substr($alias, 0, $pos);
// 從根別名開始找起,如果根別名沒找到,一切免談
if (isset(static::$aliases[$root])) {
if (is_string(static::$aliases[$root])) {
return $pos === false ? static::$aliases[$root] :
static::$aliases[$root] . substr($alias, $pos);
} else {
// 由于寫入前使用了 krsort() 所以,較長的別名會被先遍歷到。
foreach (static::$aliases[$root] as $name => $path) {
if (strpos($alias . '/', $name . '/') === 0) {
return $path . substr($alias, strlen($name));
}
}
}
}
if ($throwException) {
throw new InvalidParamException("Invalid path alias: $alias");
} else {
return false;
}
}
~~~
別名的解析過程相對簡單:
* 先按根別名找到可能保存別名的分支。
* 遍歷這個分支下的所有樹葉。由于之前葉子(別名)是按鍵值逆排序的,所以優先匹配長別名。
* 將找到的最長匹配別名替換成其所對應的值,再接上?@alias?的后半截,成為新的別名。
別名的解析過程可以這么看:
~~~
// 無效的別名,別名必須以@打頭,別名不能放在中間
// 但是語句不會出錯,會認為這是一個路徑,一字不變的路徑: path/to/@foo/bar
Yii::getAlias('path/to/@foo/bar');
// 定義 @foo @foo/bar @foo/bar/qux 3個別名
Yii::setAlias('@foo', 'path/to/foo');
Yii::setAlias('@foo/bar', 'path/2/bar');
Yii::setAlias('@foo/bar/qux', 'path/to/qux');
// 找不到 @foobar根別名,拋出異常
Yii::getAlias('@foobar/index.php');
// 匹配@foo,相當于 path/to/foo/qux/index.php
Yii::getAlias('@foo/qux/index.php');
// 匹配@foo/bar/qux,相當于 path/to/qux/2/index.php
Yii::getAlias('@foo/bar/qux/2/index.php');
// 匹配@foo/bar,相當于 path/to/bar/2/2/index.php
Yii::getAlias('@foo/bar/2/index.php');
~~~
## 小結[](http://www.digpage.com/aliases.html#id7 "Permalink to this headline")
回顧上面的內容,我們有這么幾個要點:
* 別名需在使用前定義,因此通常來講,定義別名應當在放在應用的初始化階段。
* 別名必然以?@?打頭。
* 別名的定義可以使用之前已經定義過的別名。
* 別名在儲存時,至多只分成兩級,第一級的鍵是根別名。 第二級別名的鍵是完整的別名,而不是去除根別名后剩下的所謂的“二級”別名。
* Yii通過分層的樹結構來保存別名最主要是為高效檢索作準備。
* 很多地方可以直接使用別名,而不用調用?Yii::getAlias()?轉換成真實的路徑或URL。
* 別名解析時,優先匹配較長的別名。
* Yii預定義了許多常用的別名供編程時使用。
* 使用別名時,要將別名放在最前面,不能放在中間。
如果覺得《深入理解Yii2.0》對您有所幫助,也請[幫助《深入理解Yii2.0》](http://www.digpage.com/donate.html#donate)。 謝謝!
- 更新記錄
- 導讀
- Yii是什么
- Yii2.0的亮點
- 背景知識
- 如何閱讀本書
- Yii基礎
- 屬性(Property)
- 事件(Event)
- 行為(Behavior)
- Yii約定
- Yii應用的目錄結構和入口腳本
- 別名(Alias)
- Yii的類自動加載機制
- 環境和配置文件
- 配置項(Configuration)
- Yii模式
- MVC
- 依賴注入和依賴注入容器
- 服務定位器(Service Locator)
- 請求與響應(TBD)
- 路由(Route)
- Url管理
- 請求(Reqeust)
- Web應用Request
- Yii與數據庫(TBD)
- 數據類型
- 事務(Transaction)
- AcitveReocrd事件和關聯操作
- 樂觀鎖與悲觀鎖
- 《深入理解Yii2.0》視頻教程
- 第一講:基礎配置
- 第二講:用戶登錄
- 第三講:文章及評論的模型
- 附錄
- 附錄1:Yii2.0 對比 Yii1.1 的重大改進
- 附錄2:Yii的安裝
- 熱心讀者