緣起 因為一個月的短暫停留,我在給朋友搞事情,所以Yii系列的文章耽擱了很長時間,現在又重拾當時的知識,給大伙好好擼下這一系列的博客 提起Yii,雖然是國外的開發者搞的,但是它的作者是華人,這才是讓我們引以為豪的,如果以後有機會薛強回國大力發展PHP事業,我肯定回去他麾下搞事情,為PHP在國內的發展 ...
緣起
因為一個月的短暫停留,我在給朋友搞事情,所以Yii系列的文章耽擱了很長時間,現在又重拾當時的知識,給大伙好好擼下這一系列的博客
提起Yii,雖然是國外的開發者搞的,但是它的作者是華人,這才是讓我們引以為豪的,如果以後有機會薛強回國大力發展PHP事業,我肯定回去他麾下搞事情,為PHP在國內的發展貢獻自己的一份力,雖然現在沒有這個能力,這不薛強沒回來嘛,回來的話那時的我肯定可以的~哈哈哈~
領英上強哥的背景如下:
職務:MicroStrategy 公司擔任構架師
地址:美國首都華盛頓 Metro Area
教育:
- 杜克大學·電腦科學·博士
- 浙江大學·電腦科學·碩士
好了,話步前言,上節我們已經將Yii2.0完整的安裝到我們的機器中啦,在瀏覽器中輸入下麵的地址,你就可以訪問你的伺服器應用啦~
http://伺服器IP/app/yii/web/index.php
出現下麵的命令那就對啦~
整體結構
assets文件夾:assets的作用是方便模塊化,插件化的,一般來說出於安全原因不允許通過url訪問protected下麵的文件 ,但是我們又希望將module單獨出來,所以需要使用發佈,即將一個目錄下的文件複製一份到assets下麵方便通過url訪問。
commands文件夾:控制台腳本存放的地方,自動運行腳本
config文件夾:配置文件存放的文件夾
controller文件夾:MVC中C文件存放的文件夾
mail文件夾:郵件發送目錄,具體幹啥的我還在摸索中哈~
models文件夾:MVC中M文件存放的文件夾
runtime:日誌文件
tests:測試腳本文件夾
vendor:第三方組件存放,composer下載的組件存放的文件夾,自動幫你autoload
views:MVC中V存放的文件夾
web:web主應用入口腳本存放的位置
以上是整個文件夾的佈局,裡面的各個文件有啥用,我會在後續的【應用Yii2.0搭建後臺應用框架】中詳細介紹
我這一系列的文章均來自於Yii中文網中對Yii2.0的權威指南,感謝國內開發者對社區的貢獻,感謝翻譯的人,讓我們免去了百度翻譯之苦。
如果大家不太喜歡我的描述,可以去看一下官方對Yii2.0的解釋,很詳細,但很官方,不會調戲你^_^
官方文檔【中文版】:http://www.yiichina.com/doc/guide/2.0
Yii 應用參照模型-視圖-控制器 (MVC)設計模式來組織。不懂MVC?這麼說吧,不管是前端應用還是後端應用,首當其衝的設計模式就是MVC。所以瞭解它相當有必要!
M模型代表數據、業務邏輯和規則;V視圖展示模型的輸出;C控制器接受出入並將其轉換為模型和視圖命令。
這就是Yii的整個框架結構設計,我們的MVC就是其中的控制器,視圖和模型,他們的各自作用上面也講了下,一般的後端應用,M表示從資料庫、第三方鏈接、本地文件中獲取的數據進行處理,整理,在交給到V端,V端的作用一般是在頁面中反饋給用戶的頁面,如果是以數據的形式返回給用戶,那這個V層就不用做過多的渲染。C層的話主要是連接兩者的作用,C層獲取到用戶的請求,傳給M層,M層處理好數據,反饋給C層,C層再將數據給到V層,V層展示給用戶。MVC模型的便捷之處就是邏輯清晰,每個模塊負責自己的事,有條有理,非常便於初學者理解,是一個入門的模型。
除此之外,Yii還包含其他邏輯處理塊,比方說上面圖中的入口腳本【調用應用一開始必被調用的腳本文件】,應用主體【Yii::$app全局可訪問對象】,應用組件【全局通用的一些工具集】,模塊【業務邏輯單元,每個業務邏輯一個模塊,會讓代碼很清晰】,過濾器【規範行為的對象,在控制器執行之前或之後調用,定義一類特殊的行為】,前端資源和小部件我們先不講,因為是涉及到前端的一些組件內容,後面我會單獨開闢一個系列來講前端知識,我出這一系列的目的主要是針對後臺應用~
入口腳本
心細的朋友可能早就發現了,為啥我們在上面的訪問鏈接中後面有個index.php,對,就是它,它就是入口腳本,每次web請求都必須經過它!
http://172.16.122.58/app/yii/web/index.php
一般他都是在web這個目錄下麵的,這個是web應用的入口腳本。
還有個入口腳本是啥呢,控制台腳本,下麵的那個叫yii的php腳本,啥作用呢,你們想想啊,電商後臺中,如果有很多人要調整庫存,是不是調整一次就給改一次呀,肯定不會呀,庫存操作如果調用資料庫太頻繁了,資料庫肯定扛不住的,我們的做法就是先放到類似於Redis的緩存中,等到一定量的時候,或者有個1秒鐘的時候我們給同步一次資料庫,同步的方式就是調用控制台腳本啦,配合Linux的crontab,完美解決資料庫調用過於頻繁的問題。控制台腳本後面我們會介紹,一般業務線中用的還挺多的。
入口腳本主要完成以下工作:
- 定義全局常量;
- 註冊 Composer 自動載入器;
- 包含 Yii 類文件;
- 載入應用配置;
- 創建一個應用實例並配置;
- 調用 yii\base\Application::run() 來處理請求。
<?php // comment out the following two lines when deployed to production defined('YII_DEBUG') or define('YII_DEBUG', true); defined('YII_ENV') or define('YII_ENV', 'dev'); require(__DIR__ . '/../vendor/autoload.php'); require(__DIR__ . '/../vendor/yiisoft/yii2/Yii.php'); $config = require(__DIR__ . '/../config/web.php'); (new yii\web\Application($config))->run();
入口腳本是定義全局常量的最好地方,話雖如此,不建議在這裡定義啥全局變數!Yii 支持以下三個常量:
YII_DEBUG
:標識應用是否運行在調試模式。當在調試模式下,應用會保留更多日誌信息,如果拋出異常,會顯示詳細的錯誤調用堆棧。因此,調試模式主要適合在開發階段使用,YII_DEBUG
預設值為 false。
YII_ENV
:標識應用運行的環境。YII_ENV
預設值為 'prod'
,表示應用運行線上上產品環境。
YII_ENABLE_ERROR_HANDLER
:標識是否啟用 Yii 提供的錯誤處理,預設為 true。
autoload.php,讀過我之前文章的朋友肯定對他有印象,PSR-4,自動載入器哈,這個是註冊composer自動載入器的。
Yii.php,包含Yii類的文件路徑。
倒數第二行是載入應用配置。最後一行是運行一個應用,這裡面的$config【web.php】這個文件會在後面詳細解答。
應用主體配置
應用主體在入口腳本中創建並能通過表達式 \Yii::$app
全局範圍內訪問。訪問的變數定義在哪兒呢,由於應用主體配置比較複雜,就是剛剛提到的$config,config文件夾中的web.php文件。後面比較複雜的配置都可以放到單個文件中,這是個技巧,即減少了配置文件的代碼行數,也將整個框架清晰很多。
這個裡面定義了很多屬性,我們來分別看下吧。
<?php $params = require(__DIR__ . '/params.php'); $config = [
params這個參數里的所有變數就被定義在params.php這個文件裡面。
下麵是我的項目中配置的一些文件在上方定義
<?php $params = require(__DIR__ . '/params.php'); $rules = require(__DIR__ . '/rules.php'); $aliases = require(__DIR__ . '/aliases.php'); $cacheConfig = require(__DIR__ . '/cache.php');
這裡面主要是params參數,rules路由規則,aliases別名規則,cacheConfig緩存配置,你可能會懷疑,為何db這麼關鍵的沒有配置上來,db是區分環境的,在index.php中會區分stable環境、pro環境還是測試環境做區分。
我們還是回到我們的Yii源碼,配置文件上方配置了params所在的文件,
$config = [ 'id' => 'basic', 'basePath' => dirname(__DIR__), 'bootstrap' => ['log'], 'components' => [ 'request' => [ // !!! insert a secret key in the following (if it is empty) - this is required by cookie validation 'cookieValidationKey' => 'A9BMCrvbxuCEnE39rVpOUECgcBJTnzUH', ], 'cache' => [ 'class' => 'yii\caching\FileCache', ], 'user' => [ 'identityClass' => 'app\models\User', 'enableAutoLogin' => true, ], 'errorHandler' => [ 'errorAction' => 'site/error', ], 'mailer' => [ 'class' => 'yii\swiftmailer\Mailer', // send all mails to a file by default. You have to set // 'useFileTransport' to false and configure a transport // for the mailer to send real emails. 'useFileTransport' => true, ], 'log' => [ 'traceLevel' => YII_DEBUG ? 3 : 0, 'targets' => [ [ 'class' => 'yii\log\FileTarget', 'levels' => ['error', 'warning'], ], ], ], 'db' => require(__DIR__ . '/db.php'), /* 'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ ], ], */ ], 'params' => $params, ];
這邊有幾個屬性,id、basePath、bootstrap和components。其他還有幾個比較重要的屬性:aliases、language、modules。
id
yii\base\Application::id 屬性用來區分其他應用的唯一標識ID。一般配置為程式名稱。必要屬性之一。
basePath
yii\base\Application::basePath 指定該應用的根目錄。系統預定義 @app
代表這個路徑。 你如果需要require目錄內的文件,可以使用這個方式找到對應文件。另外一個必要屬性,這個是必須得配置的。
bootstrap
這個屬性很實用,它允許你用數組指定啟動階段yii\base\Application::bootstrap()需要運行的組件。一般後端應用中配置個
'bootstrap' => ['log'],
即可。
components
這是最重要的屬性,它允許你註冊多個在其他地方使用的應用組件。比方說session、log、db和cache啊,都在這裡面配置的,具體的下一節應用組件中會講到。
aliases
該屬性允許你用一個數組定義多個別名。數組的key為別名名稱,值為對應的路徑。
[ 'aliases' => [ '@name1' => 'path/to/path1', '@name2' => 'path/to/path2', ], ]
我的配置裡面都是設置的extension擴展類的別名,extension裡面放置一些基礎調用類,比方說CURL、微信支付等等的。
[ '@ext' => dirname(__DIR__) . '/extensions', ]
language
該屬性指定應用展示給終端用戶的語言,預設為 en
標識英文。如果需要之前其他語言可以配置該屬性。
'language' => 'zh-CN',
modules
該屬性指定應用所包含的模塊。還記得上面說的業務的分割嗎,是的,就是在modules裡面進行劃分的,對應的目錄就是根目錄下的modules目錄,也是需要配置噠~
[ 'modules' => [ // "booking" 模塊以及對應的類 'booking' => 'app\modules\booking\BookingModule', // "comment" 模塊以及對應的配置數組 'comment' => [ 'class' => 'app\modules\comment\CommentModule', 'db' => 'db', ], ], ]
我的模塊的劃分
'modules' => [ 'admin' => [ 'class' => 'app\modules\admin\Admin', ], 'api' => [ 'class' => 'app\modules\api\Api', ], 'user' => [ 'class' => 'app\modules\user\User', ], 'bus' => [ 'class' => 'app\modules\bus\Bus', ], 'goods' => [ 'class' => 'app\modules\goods\Goods', ],... ]
這個配置對你代碼的結構清晰性有很大的提升,後端應用必配屬性。
defaultRoute
該屬性指定未配置的請求的響應 路由規則,路由規則可能包含模塊ID,控制器ID,動作ID。 例如help
, post/create
, admin/post/create
,如果動作ID沒有指定,會使用yii\base\Controller::defaultAction中指定的預設值。
對於 yii\web\Application 網頁應用,預設值為 'site'
對應 SiteController
控制器,並使用預設的動作。
對於 yii\console\Application 控制台應用, 預設值為 'help'
對應 yii\console\controllers\HelpController::actionIndex()。
說完了基礎的主體配置,其他的主體配置可以去官方文檔中查看喲,那裡面很詳細,不過我還是針對我使用過的和大伙聊聊。
我們來看下components組件配置有哪些關鍵的配置。
應用組件
應用主體配置完畢,我們來看看其中components有哪些關鍵配置吧
在同一個應用中,每個應用組件都有一個獨一無二的 ID 用來區分其他應用組件,你可以通過如下表達式訪問應用組件
\Yii::$app->componentID
官網給出的components示例如下
[ 'components' => [ // 使用類名註冊 "cache" 組件 'cache' => 'yii\caching\ApcCache', // 使用配置數組註冊 "db" 組件 'db' => [ 'class' => 'yii\db\Connection', 'dsn' => 'mysql:host=localhost;dbname=demo', 'username' => 'root', 'password' => '', ], // 使用函數註冊"search" 組件 'search' => function () { return new app\components\SolrService; }, ], ]
這裡定義了三個配置屬性:cache、db、search,除此之外,比較重要的components屬性還有如下幾個:urlManager、user、session、errorHandler和log。
cache
代表代表各種緩存存儲器,例如記憶體,文件,資料庫。具體會在【Yii系列】操作緩存一章中講解
db
代表一個可以執行資料庫操作的資料庫連接。具體會在【Yii系列】連接資料庫一章中講解
search
搜索組件入口配置,以後有機會和大家嘮嘮solr相關,切割關鍵字,切割,切割~
urlManager
支持URL地址解析和創建。具體會在【Yii系列】請求處理一章中講解
user
代表認證登錄用戶信息,僅在yii\web\Application 網頁應用中可用。如果有後臺用戶許可權認證管理,可去官網瞭解明細。
session
代表會話信息,僅在yii\web\Application 網頁應用中可用,我們一般都是通過這個來控制用戶屬性的,具體會在【Yii系列】請求處理一章詳細講解
errorHandler
處理 PHP 錯誤和異常
log
全局日誌配置入口,具體會在【Yii系列】請求處理一章講解
請謹慎註冊太多應用組件,應用組件就像全局變數,使用太多可能加大測試和維護的難度。 一般情況下可以在需要時再創建本地組件。
大家可以著重關註幾個配置:db、urlManager、log和cache,這些都是在日常工作中用的比較多的,必配的配置。
控制器
講完了上面的配置文件,我們再來看看組成Yii最最關鍵的部分,也是大家工作過程中打交道最多的部分啦,請打起你們十二分的精神,看好每一個字元喲~
首先我們來看下MVC中的C,下麵我們就成他為Controller,因為他是入口,就和入口腳本一樣,用戶發來請求,首先到的就是控制器(只是在後臺應用中先到,如果是包含前端資源,用戶首先接觸到的肯定是前端的代碼哈)。
Controller的主要責任是接受用戶請求,分發處理用戶請求,拿到結果數據,遞交給V層展示給用戶。
繼承yii\base\Controller類的對象
Controller由操作【action】組成,它是執行終端用戶請求的最基礎的單元,一個控制器可有一個或多個操作。
如下示例顯示包含兩個操作view
和 create
的控制器post
:
namespace app\controllers; use Yii; use app\models\Post; use yii\web\Controller; use yii\web\NotFoundHttpException; class PostController extends Controller { public function actionView($id) { $model = Post::findOne($id); if ($model === null) { throw new NotFoundHttpException; } return $this->render('view', [ 'model' => $model, ]); } public function actionCreate() { $model = new Post; if ($model->load(Yii::$app->request->post()) && $model->save()) { return $this->redirect(['view', 'id' => $model->id]); } else { return $this->render('create', [ 'model' => $model, ]); } } }
用戶訪問的時候就是直接被只想到某個action裡面的,有這個action方法了,那麼我們如何訪問它呢,這邊就涉及到一個被稱為路由的東西。
終端用戶通過所謂的【路由】尋找到操作
路由使用如下格式:
ControllerID/ActionID
模塊下的控制器,使用如下格式:
ModuleID/ControllerID/ActionID
如果用戶的請求地址為 http://hostname/index.php?r=site/index
, 會執行site
控制器的index
操作。是的,有過web基礎的朋友可能對這個比較熟悉,?後面的是參數,而這個r參數就代表了訪問路徑。
那麼如何去創建一個控制器呢?
在yii\web\Application網頁應用中,控制器應繼承yii\web\Controller 或它的子類。
在yii\console\Application控制台應用中,控制器繼承yii\console\Controller 或它的子類。
namespace app\controllers; use yii\web\Controller; class SiteController extends Controller { }
site就為這個Controller的控制器id。控制器ID通常是和資源有關的名詞。
這裡面有個事項需要註意下,Yii框架下都是通過駝峰命名法去查找對應的ID的,比方說這邊的SiteController,在URL調用中只需要保留site,後面的Controller不需要放到url裡面,控制器類必須能被自動載入。
控制器Id可包含子目錄首碼,例如 admin/article
代表 yii\base\Application::controllerNamespace控制器命名空間下 admin
子目錄中 article
控制器。
下麵為一些示例,假設yii\base\Application::controllerNamespace控制器命名空間為 app\controllers
:
article
對應app\controllers\ArticleController
;post-comment
對應app\controllers\PostCommentController
;admin/post-comment
對應app\controllers\admin\PostCommentController
;adminPanels/post-comment
對應app\controllers\adminPanels\PostCommentController
.
每個應用有一個由yii\base\Application::defaultRoute屬性指定的預設控制器; 就是我們上面講的那個
[ 'defaultRoute' => 'main' ]
當請求沒有指定路由,該屬性值作為路由使用。
對於yii\web\Application網頁應用,它的值為 'site';
對於 yii\console\Application控制台應用,它的值為 help。
所以URL為 http://hostname/index.php
表示由 site
控制器來處理。
創建action
創建action可簡單地在控制器類中定義所謂的操作方法來完成,操作方法必須是以action
開頭的公有方法。和controller,命名的時候是使用駝峰命名法,訪問的時候全小寫,如果中間有多個大寫字母,用'-'間隔即可。
namespace app\controllers; use yii\web\Controller; class SiteController extends Controller { public function actionIndex() { return $this->render('index'); } public function actionHelloWorld() { return 'Hello World'; } }
獨立操作我們不做深究,感興趣的朋友可以去研究下yii自帶的驗證碼模塊源碼。
action的參數值從請求中獲取,
對於yii\web\Application網頁應用, 每個操作參數的值從$_GET
中獲得,參數名作為鍵;一般不會使用$_GET直接去參數,這樣不安全。一般使用app的Request去取出用戶請求的body和head。
$request = Yii::$app->request;
對於yii\console\Application控制台應用, 操作參數對應命令行參數。
如下例,操作view
(內聯操作) 申明瞭兩個參數 $id
和 $version
。
namespace app\controllers; use yii\web\Controller; class PostController extends Controller { public function actionView($id, $version = null) { // ... } }
操作參數會被不同的參數填入,如下所示:
http://hostname/index.php?r=post/view&id=123
:$id
會填入'123'
,$version
仍為 null 空因為沒有version
請求參數;http://hostname/index.php?r=post/view&id=123&version=2
: $id和
$version分別填入
'123'和
'2'`;http://hostname/index.php?r=post/view
: 會拋出yii\web\BadRequestHttpException 異常 因為請求沒有提供參數給必須賦值參數$id
;http://hostname/index.php?r=post/view&id[]=123
: 會拋出yii\web\BadRequestHttpException 異常 因為$id
參數收到數字值['123']
而不是字元串.
關於Controller的生命周期及其他這邊沒有提到的,可以去官網看詳細的解釋。
模型
講完了入口的Controller,現在我們來說說MVC中最最最最重要的數據模型層,這一層負責數據的整合處理,包括業務邏輯控制,從資料庫拿出數據,打包數據,緩存操作,返回給Controller等一系列操作,可謂是我們日常業務邏輯工作中每天大部分時間都在擼的代碼了。
模型可通過繼承 yii\base\Model 或它的子類定義,基類yii\base\Model支持許多實用的特性:
Model
類也是更多高級模型如Active Record 活動記錄的基類,不得不說,對於資料庫操作不熟練的朋友可以嘗試一下Active Record,Yii的這個特性讓你操作資料庫像操作對象一樣簡單。
定義屬性,說白了也就是定義model裡面的變數。
namespace app\models; use yii\base\Model; class ContactForm extends Model { public $name; public $email; public $subject; public $body; }
之前做過java,eclipse自動生成get,set方法,感覺好方便,這邊,一般情況下不需要生成get,set方法,除非你的資料庫操作是通過Active Record進行的,你才需要去覆蓋魔術方法如__get()
, __set()
使屬性像普通對象屬性被訪問。
對於model裡面屬性的訪問,這要感謝yii\base\Model支持 ArrayAccess 數組訪問 和 ArrayIterator 數組迭代器。可以使用如下兩種方式對屬性進行訪問,是public屬性喲。
$model = new \app\models\ContactForm; // "name" 是ContactForm模型的屬性 $model->name = 'example'; echo $model->name;
$model = new \app\models\ContactForm; // 像訪問數組單元項一樣訪問屬性 $model['name'] = 'example'; echo $model['name']; // 迭代器遍歷模型 foreach ($model as $name => $value) { echo "$name: $value\n"; }
屬性標簽,一般應用在輸入錯誤的提示處,屬性標簽是視圖一部分,但是在模型中申明標簽通常非常方便,並可行程非常簡潔可重用代碼。我們這邊不做深究,一般在後臺應用中很少涉及。感興趣的朋友可以去官方文當詳查。
屬性的規範很重要,常規的操作是不允許兩個model之間互相調用各自的屬性的,如非必要,一般model中比較常規的屬性放到全局變數中去控制,後臺應用中,可供給model操作的數據均是來源於用戶的,我們需要做的更多的是對用戶輸入的控制。你可能在日常的工作中對這些用戶輸入的控制直接就使用了,楓爺在這邊給大家一個小小的警醒,千萬不能這麼乾,有幾步還是需要做的,為了安全性。其一,xss攻擊的防範;其二,用戶輸入的驗證。
xss的攻擊,Yii提供了相應的方式應對,如果這個參數不對資料庫進行操作,一般情況下無需驗證,如果需要對資料庫進行驗證,需要進行xss驗證,具體的實例代碼百度一下,一大堆,這裡就不做講解啦。
對於用戶輸入的驗證,最好的做法是在Controller裡面對用戶的行為進行控制,也就是重寫behaviors這個方法,然後對每個action進行驗證。
'validation' => [ 'class' => 'ext\controller\behavior\Validation', 'verifyPath' => 'app\modules\api\modules\v1\models\validation\\', 'indexName' => 'body', 'enabled' => true ],
其實,是否需要登錄驗證,是否需要緩存都可以在behaviors裡面進行配置。
這樣區分開來不但能夠避免在model裡面寫過多校驗代碼,也可以使你的model層更加清晰簡潔。
其實這個應該是在Controller那邊說的,既然這邊提到數據校驗,就一筆帶過啦~具體的如何操作,我們會在下麵一節【過濾器】中詳解。
對於數據的獲取,資料庫還是緩存,我們在接下來的章節中繼續講解,這邊就不細究了,進入下一個環節,View。
對於Model,也就這麼多,其他的和寫一般的function一樣,寫業務邏輯就可以啦,需要註意的都說了哈~以後有補充在評論欄。
視圖
後端介面的應用中,這個模塊很少被使用到,如果大家對這個模塊感興趣,可以去官網詳查,我這邊的方法很簡單暴力,直接return給用戶數據即可。
不要忘了,前端需要什麼樣的數據格式,我們這邊需要在最後return的時候encode一下哈。最常用的莫過於json格式啦。
public function formatJson($data = []) { Yii::$app->response->format = \yii\web\Response::FORMAT_JSON; return $data; }
模塊
你可能沒有在Yii2.0的源代碼中找到關於模塊的代碼,那是因為Yii最大的優勢是開發後臺系統,並沒有想過需要把業務邏輯切割的那麼細,我們在日常開發後臺介面的時候,會有很多獨立的系統,比方說用戶模塊,訂單模塊,支付模塊等等等等。那模塊的優勢就顯示出來了,每個系統一個模塊,清清楚楚。
如何創建模塊呢?下麵來一步步的搞清楚。
模塊是獨立的軟體單元,由模型, 視圖, 控制器和其他支持組件組成,是不是感覺就是個小型的MVC模型,沒錯,每個模塊都是一個獨立的單元。
使用模塊必須在應用主體中配置它,終端用戶可以訪問在應用主體中已安裝的模塊的控制器。
如下例子顯示一個模型的目錄結構:
app/ Module.php 模塊類文件 controllers/ 包含控制器類文件 DefaultController.php default 控制器類文件 models/ 包含模型類文件 views/ 包含控制器視圖文件和佈局文件
Module.php是基礎模塊類,繼承yii\base\Module的模塊類,該類文件直接放在模塊的yii\base\Module::basePath【模塊對應的主目錄】下。必須能被自動載入。
在應用主體中配置模塊,如下示例
$config = [ 'id' => 'app.name', 'basePath' => dirname(__DIR__), 'bootstrap' => ['log'], 'language' => 'zh-CN', 'components' => [ ... ], 'params' => $params, 'modules' => [ 'user' => [ 'class' => 'app\modules\user\User', ], 'goods' => [ 'class' => 'app\modules\goods\Goods', ], 'store' => [ 'class' => 'app\modules\store\Store', ], 'supply' => [ 'class' => 'app\modules\supply\Supply', ], 'order' => [ 'class' => 'app\modules\order\Order', ], 'stock' => [ 'class' => 'app\modules\stock\Stock', ], 'pay' => [ 'class' => 'app\modules\pay\Pay', ], 'message' => [ 'class' => 'app\modules\message\Message', ], ], 'aliases' => $aliases, 'defaultRoute' => 'admin' ];
主體應用中配置的模塊就指向剛剛建立的模塊基礎類。
每個模塊可以有自己獨立的配置文件,將模塊中需要用到的共同的參數放到這個配置文件中,併在模塊基礎類中添加進去。
下麵就是我的用戶模塊的目錄和代碼
<?php namespace app\modules\goods; use Yii; class Goods extends \yii\base\Module { public $controllerNamespace = 'app\modules\goods\controllers'; public function init() { parent::init(); Yii::configure($this, require(__DIR__ . '/config.php')); } }
訪問路由
和訪問應用的控制器類似,路由也用在模塊中控制器的定址, 模塊中控制器的路由必須以模塊ID開始,接下來為控制器ID和操作ID。 例如,假定應用使用一個名為 forum
模塊,路由forum/post/index
代表模塊中 post
控制器的 index
操作, 如果路由只包含模塊ID,預設為 default
的yii\base\Module::defaultRoute 屬性來決定使用哪個控制器/操作, 也就是說路由 forum
可能代表 forum
模塊的 default
控制器。
一般訪問模塊的方式如下:
// 獲取ID為 "forum" 的模塊 $module = \Yii::$app->getModule('forum'); // 獲取處理當前請求控制器所屬的模塊 $module = \Yii::$app->controller->module;
模塊在大型項目中常備使用,這些項目的特性可分組,每個組包含一些強相關的特性, 每個特性組可以做成一個模塊由特定的開發人員和開發組來開發和維護。
在特性組上,使用模塊也是重用代碼的好方式,一些常用特性,如用戶管理,評論管理,可以開發成模塊, 這樣在相關項目中非常容易被重用。無恥的抄襲了官網文檔的最佳實踐,其實個人感覺官網文檔的最佳時間是最給力的內容,其他內容都太晦澀了^_^
過濾器
在模塊那一節,我們提到一個校驗的神器,behavior,過濾器是Controller動作執行之前或之後執行的對象,所以我說應該放在Controller一節去講,但是在model層數據處理中提到了,就在那邊一筆代了下,現在來詳細的說說這個吧。
過濾器可包含 預過濾(過濾邏輯在動作之前) 或 後過濾(過濾邏輯在動作之後),也可同時包含兩者。
我們上面僅是提及了過濾器的一個屬性,validation,這也是我自定義的一個屬性,用於校驗用戶輸入的正確性。
完整的官網對behaviors的定義如下:
public function behaviors() { return [ [ 'class' => 'yii\filters\HttpCache', 'only' => ['index', 'view'], 'lastModified' => function ($action, $params) { $q = new \yii\db\Query(); return $q->from('user')->max('updated_at'); }, ], ]; }
控制器類的過濾器預設應用到該類的所有action,你可以在only參數中指定應用到哪幾個action。也可以配置yii\base\ActionFilter::except屬性使一些動作不執行過濾器。
一般情況下,我們使用預過濾,很少會被用到後過濾,他們有啥區別呢。
預過濾
- 按順序執行應用主體中
behaviors()
列出的過濾器。 - 按順序執行模塊中
behaviors()
列出的過濾器。 - 按順序執行控制器中
behaviors()
列出的過濾器。 - 如果任意過濾器終止動作執行,後面的過濾器(包括預過濾和後過濾)不再執行。
- 成功通過預過濾後執行動作。
後過濾
- 倒序執行控制器中
behaviors()
列出的過濾器。 - 倒序執行模塊中
behaviors()
列出的過濾器。 - 倒序執行應用主體中
behaviors()
列出的過濾器。
創建過濾器,繼承 yii\base\ActionFilter 類並覆蓋 yii\base\ActionFilter::beforeAction() 和/或 yii\base\ActionFilter::afterAction() 方法來創建動作的過濾器,前者在動作執行之前執行,後者在動作執行之後執行。
下麵是我在項目中使用的公共驗證器。
<?php namespace ext\controller\behavior; use Yii; use yii\base\ActionFilter; use yii\base\Exception; class Validation extends ActionFilter { public $enabled = true; //預設打開驗證 public $verifyPath = null;//驗證目錄,必填項.命名空間格式填寫 public $whiteParams = null; //過濾之後的參數,供控制器使用 public $indexName = ''; public function beforeAction($action) { if (!$this->enabled) { return true; } return $this->_check($action); } protected function _check($action) { if (empty($this->verifyPath)) { throw new Exception('驗證目錄不存在!'); } //目前只支持兩級 $groups = explode('/', $this->owner->id); if (is_array($groups) && count($groups) >= 2) { $fileName = ucfirst($groups[0]).ucfirst($groups[1]); } else { $fileName = ucfirst($this->owner->id); } unset($groups); $className = $this->verifyPath . $fileName; if (!class_exists($className)) { return true; } $actionId = $action->id; $v = new $className($this->owner->getParam($this->indexName) ?: []); if (!method_exists($v, $actionId)) { return true; } $v->{$actionId}(); if (!$v->validate(null, false)) { $errorList = []; $errors = $v->getErrors(); foreach ($errors as $field => $error) { $errorList[] = implode(' ', $error); } Yii::$app->getResponse()->data = $this->owner->getBehavior('format')->afterAction($action, $this->owner->sendError(implode(' & ', $errorList), 400)); return false; } $this->whiteParams = $v->getAttributes(); return true; } }
在Controller中的behaviors中添加一條validation即可對某些action進行驗證啦。
'validation' => [ 'class' => 'ext\controller\behavior\Validation', 'verifyPath' => 'app\modules\goods\controller\validation\\', 'indexName' => 'body', 'enabled' => true ],
verifyPath即是被驗證的規則路徑。如果是驗證參數,就對Controller下的全體action進行驗證咯。
再來看下是如何編寫這些個規則的。創建對應modules的validation。
<?php namespace app\modules\goods\controllers\validation; use app\modules\api\models\base\BaseValidation; class SupplyGoods extends BaseValidation { public function detail() { $this->defineAttributes('goodsId'); $this->addRule('goodsId', 'number')->addRule('goodsId', 'required'); } public function add() { $this->defineAttributes('goodsTitle,goodsPrice,minNum,catId,childCatId,showImages,detailImages,goodsAttr'); $this->addRule('goodsTitle', 'trim')->addRule('goodsTitle', 'required'); $this->addRule('goodsPrice', 'required'); $this->addRule('catId', 'number')->addRule('catId', 'required') ->addRule('childCatId', 'number')->addRule('childCatId', 'required'); if (!is_array($this->showImages) || count($this->showImages) == 0) { $this->addError('showImages', '商品展示圖片必傳!'); } if (!is_array($this->detailImages) || count($this->detailImages) == 0) { $this->addError('detailImages', '圖文詳情必傳!'); } if (empty($this->goodsAttr['color']) || empty($this->goodsAttr['style_id']) || empty($this->goodsAttr['size_ids']) ) { $this->addError('goodsAttr', '請設置庫存'); } } public function update() { $this->defineAttributes('goodsId,goodsTitle,goodsPrice,minNum,catId,childCatId,showImages,detailImages,goodsAttr'); $this->addRule('goodsTitle', 'trim')->addRule('goodsTitle', 'required'); $this->addRule('goodsPrice', 'required'); $this->addRule('catId', 'number')->addRule('catId', 'required') ->addRule('childCatId', 'number')->addRule('childCatId', 'required') ->addRule('goodsId','number')->addRule('goodsId','required'); if (!is_array($this->showImages) || count($this->showImages) == 0) { $this->addError('showImages', '商品展示圖片必傳!'); } if (!is_array($this->detailImages) || count($this->detailImages) == 0) { $this->addError('detailImages', '圖文詳情必傳!'); } if (empty($this->goodsAttr['color']) || empty($this->goodsAttr['style_id']) || empty($this->goodsAttr['size_ids']) ) { $this->addError('goodsAttr', '圖片屬性必傳!'); } } public function edit() { $this->defineAttributes('goodsId'); $this->addRule('goodsId', 'number')->addRule('goodsId', 'required'); } public function lists() { } public function delete() { $this->defineAttributes('goodsId'); $this->addRule('goodsId', 'number')->addRule('goodsId', 'required'); } public function category() {