2016-08-17 補充 Exception 部分改造方案的內容
2016-08-13 補充 View 部分改造方案的內容
背景
項目包含若干子站點,不同站點功能各異,但共享底層數據及邏輯。為開發及運維效率期間,決定在一個 Laravel 應用內實現整套系統。
本文基于 Laravel 5.2,主要介紹如何針對多站點分別進行用戶認證的改造,用意是最大限度利用 Laravel 自帶的認證系統。不過默認的認證都是根據 『email』和『password』字段進行的。之后有時間可能再追加自定義字段比如『phone』的改造方案,本文暫不涉及。
具體方案
為清晰起見,項目按照不同站點組織成不同模塊。在 Laravel 原有目錄結構基礎內,分別給各個站點創設目錄。
laravel 5.2 project
├── app
│ └── Http
│ └── Controllers
│ ├── Site1
│ └── Site2
└── resources
└── views
├── site1
└── site2
本文以 Admin 為例進行說明,如需增加其他站點,進行類似改動即可。
Structure
執行下列命令生成默認路由、控制器及視圖。
php artisan make:auth
將默認的控制器和視圖結構分別復制到子模塊下,并創建相關模型、遷移表、修改路由、認證配置。
本例中,分別在 app/Http/Controllers 及 resources/views 下新建 Admin 及 admin 目錄,所有該站點相關的 Controller 及 view 均放置在上述兩目錄下。
最終涉及到的文件樹如下。
laravel 5.2 project
├── app
│ ├── Exceptions
│ │ └── Handler.php (變更)
│ ├── Http
│ │ ├── Controllers
│ │ │ └── Admin (新建)
│ │ │ ├── Auth
│ │ │ │ ├── AuthController.php
│ │ │ │ └── PasswordController.php
│ │ │ └── HomeController.php
│ │ └── routes.php (變更)
│ └── Admin.php (新建)
├── config
│ └── auth.php (變更)
├── database
│ └── migrations
│ ├── 2014_10_12_000000_create_admins_table.php (新建)
│ └── 2014_10_12_100000_create_admin_password_resets_table.php (新建)
└── resources
└── views
└── admin (新建)
├── auth
│ ├── emails
│ │ └── password.blade.php
│ ├── login.blade.php
│ ├── passwords
│ │ ├── email.blade.php
│ │ └── reset.blade.php
│ └── register.blade.php
├── errors
│ └── 503.blade.php
├── home.blade.php
├── layouts
│ └── app.blade.php
└── welcome.blade.php
Config
針對 Admin 新建 provider(下面的 admins),使用 Admin 的 Model。
針對 Admin 新建 guard,使用新建的 provider——『admins』。
config/auth.php
'guards' => [
'admins' => [
'driver' => 'session',
'provider' => 'admins',
],
],
'providers' => [
'admins' => [
'driver' => 'eloquent',
'model' => App\Admin::class, // 使用自定義的 Model(Admin)
],
],
Admin 站點使用 Auth 門面時均需指定 guard 為 admins,例如獲得當前登錄用戶:Auth::guard('admins')->user()。
Router
這里的作用當然是讓訪問 Admin 站點的請求被轉入該站點的 Controller。
本例中,不同系統是以站點域名來區分的,當然也可以用別的方法。
app/Http/routes.php
Route::group(['domain' => 'admin.example.com', 'namespace' => 'Admin'], function () { // 之前將默認認證相關類保持結構復制到了 Admin 下,此時只需簡單指定公共命名空間即可
Route::auth(); // 各種注冊、登錄、找回密碼的默認路由
Route::group(['middleware' => ['auth:admins']], function () { // 指定 auth 的 guard 為 新建的 admins
Route::get('/', 'HomeController@index'); // 登錄成功才能訪問的部分放在認證保護內
});
});
View
我們需要針對不同的站點使用不同的樣式或者用戶認證邏輯,所以將原 resources/views 下文件復制到 resources/views/admin 下,同時針對路徑變更修改代碼。
resources/views/vendor 由于框架中若干處硬編碼,所以沒法直接移走,用到相關的功能需另想辦法處理。
-
view() 方法
例如 view('auth、view('home、view('layouts、view('welcome,初始狀態下,這種形式的使用場所如下。
app/Http/Controllers/HomeController.php:27: return view('home');
app/Http/routes.php:15: return view('welcome');
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/controllers/HomeController.stub:27: return view('home');
vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php:37: return view('auth.login');
vendor/laravel/framework/src/Illuminate/Foundation/Auth/RegistersUsers.php:33: return view('auth.register');
vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:49: return view('auth.passwords.email');
vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:52: return view('auth.password');
vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:194: return view('auth.passwords.reset')->with(compact('token', 'email'));
vendor/laravel/framework/src/Illuminate/Foundation/Auth/ResetsPasswords.php:197: return view('auth.reset')->with(compact('token', 'email'));
app 目錄下的 view 直接加上 admin. 的前綴就好。
vendor 目錄下的都是通過 trait 的形式被 App\Http\Controllers\Auth\AuthController 使用的,而且都留好了自定義屬性可供改寫,例如下方先判斷若不存在 $this->loginView,才使用默認的 auth.login。所以我們只需要繼承默認的 AuthController 并指定這些相關屬性即可。(參見 Controller 部分介紹)
vendor/laravel/framework/src/Illuminate/Foundation/Auth/AuthenticatesUsers.php
public function showLoginForm()
{
$view = property_exists($this, 'loginView')
? $this->loginView : 'auth.authenticate';
if (view()->exists($view)) {
return view($view);
}
return view('auth.login');
}
-
@include()、@extends() 方法
同樣,還是針對 auth、home、layouts、welcome 進行處理,初始狀態下 Laravel 只使用到了 layouts.app 這個文件。
resources/views/auth/login.blade.php:1:@extends('layouts.app')
resources/views/auth/passwords/email.blade.php:1:@extends('layouts.app')
resources/views/auth/passwords/reset.blade.php:1:@extends('layouts.app')
resources/views/auth/register.blade.php:1:@extends('layouts.app')
resources/views/home.blade.php:1:@extends('layouts.app')
resources/views/welcome.blade.php:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/login.stub:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/email.stub:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/passwords/reset.stub:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/auth/register.stub:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/home.stub:1:@extends('layouts.app')
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/welcome.stub:1:@extends('layouts.app')
resources 目錄下的只需要簡單加上前綴 admin. 即可。
vendor 目錄下的文件在運行時沒有作用,所以不用處理。
-
Auth 門面
由于 Admin 站點新建了 guard,所以使用默認 Auth 門面的場合也需要更改。
resources/views/layouts/app.blade.php:56: @if (Auth::guest())
resources/views/layouts/app.blade.php:62: {{ Auth::user()->name }} <span class="caret"></span>
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/layouts/app.stub:56: @if (Auth::guest())
vendor/laravel/framework/src/Illuminate/Auth/Console/stubs/make/views/layouts/app.stub:62: {{ Auth::user()->name }} <span class="caret"></span>
resources 目錄下,用 Admin::guard('admins')-> 替換 Auth:: 來指定 guard。
vendor 目錄下的文件在運行時沒有作用,所以不用處理。
Migration & Model
畢竟是新增的站點,參考自帶的 users/password_resets 新建相關的 table 及 model 即可。
Controller
將原 app/Http/Controllers 下相關結構復制到 Admin 下后,修改 namespace,然后針對 Admin 相關進行變動。
此處以 AuthController 為例,PasswordController 類似:
app/Http/Controllers/Admin/Auth/AuthController.php
use App\Http\Controllers\Auth\AuthController as BaseAuthController;
class AuthController extends BaseAuthController // 簡單起見直接繼承默認類
{
/* 這里的屬性都是給 trait 用的,所以不能指定為 private */
protected $guard = 'admins'; // 指定 config/auth.php 中 guard
protected $loginView = 'admin.auth.login'; // 指定登錄用 view
protected $registerView = 'admin.auth.register'; // 指定注冊用 view
protected function validator(array $data) // 注冊時使用的驗證規則
{
return Validator::make($data, [ // 根據 Admin 所需的信息修改驗證配置
'name' => 'required|max:255',
'email' => 'required|email|max:255|unique:admins', // 使用 admins 表
'password' => 'required|confirmed|min:6',
'terms' => 'required',
]);
}
protected function create(array $data)
{
return Admin::create([ // 使用自定義的 Model(Admin)
'name' => $data['name'],
'email' => $data['email'],
'password' => bcrypt($data['password']),
]);
}
}
Exception
如果需要自定義 error 頁面(之前把 resources/views/errors 移到 resources/views/admin/errors)的情況下,由于 Laravel 的異常固定由 App\Exceptions\Handler 處理,所以需要在此類中針對不同站點分別指定錯誤處理 view。具體來說是修改指定錯誤頁的邏輯,在子類中根據請求的域名(本例的區分規則)指定該站點的錯誤頁 view。
app/Exceptions/Handler.php
class Handler extends ExceptionHandler
{
protected $host;
public function render($request, Exception $e)
{
$this->host = $request->getHost(); // 記錄請求站點的域名
return parent::render($request, $e);
}
protected function renderHttpException(HttpException $e) // 修改并覆蓋父類的方法,用于指定子站點錯誤頁 view
{
switch($this->host)
{
case 'admin.example.com': // 針對 Admin 站點指定路徑中的 admin
$prefix = 'admin';
break;
}
$prefix = empty($prefix) ? '' : $prefix . '.';
$status = $e->getStatusCode();
$errorView = $prefix . "errors.{$status}"; // 指定錯誤頁 view
if (view()->exists($errorView)) {
return response()->view($errorView, ['exception' => $e], $status, $e->getHeaders());
} else {
return $this->convertExceptionToResponse($e);
}
}
}
參考
整個改造過程中,主要參考了如下資料來源,感謝各位作者的同時也一并放出參考。