若想知道業界都是怎樣在玩Laravel,建議參考一下這篇:https://laravel-china.org/docs/laravel-specification/5.5
setup_xampp.bat
以進行環境設定,日後只要點擊xampp-control.exe
並啟動apache及MySQL即可)非必要 安裝 GitHub Desktop for Windows
setup_xampp.bat
以進行環境設定xampp-control.exe
,並分別按start啟動apache及MySQL\xampp\phpMyAdmin\config.inc.php
,將19行改為http
即可,如此,每次登入phpMyAdmin便會詢問帳號密碼。env
」,然後點擊「編輯您的帳戶的環境變數」即可。%USERPROFILE%
就是使用者家目錄,如:C:\Users\帳號);
%USERPROFILE%\xampp\php
//若是裝在c:\xampp請設定:
c:\xampp\php
{
"git.ignoreMissingGitWarning": true,
"update.channel": "none",
// 控制字型大小 (以像素為單位)。
"editor.fontSize": 18,
// 控制是否自動換行。
"editor.wordWrap": "on",
// 控制編輯器是否應自動設定貼上的內容格式。格式器必須可供使用,而且格式器應該能夠設定檔案中一個範圍的格式。
"editor.formatOnPaste": true,
// 使用滑鼠滾輪並按住 Ctrl 時,縮放編輯器的字型
"editor.mouseWheelZoom": true,
// 指向 PHP 可執行檔。
"php.validate.executablePath": "C:/Users/使用者名稱/xampp/php/php.exe",
"css.remoteStyleSheets": [
"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
],
"css.fileExtensions": [
"css",
"scss",
"tpl"
],
}
phpfmt
:格式化PHP程式碼用,請加入設定(php.exe的實際路徑請視實際情況修改
// php executable path
"phpfmt.php_bin": "C:/Users/使用者名稱/xampp/php/php.exe",
// enable auto align of ST_EQUAL and T_DOUBLE_ARROW
"phpfmt.enable_auto_align": true,
// fixes visibiliy order for method in classes - PSR-2 4.2
"phpfmt.visibility_order": true,
// convert multistatement blocks into multiline blocks
"phpfmt.smart_linebreak_after_curly": true,
// Enable per-language
"[php]": {
"editor.formatOnSave": true
},
vscode-goto-documentation
:快速檔案搜尋AutoFileName
: 讓編輯器自動完成圖片或檔案路徑。Auto Rename Tag
: 讓成對的標籤自動一起修改。Auto Close Tag
: 讓標籤自動閉合的。Bootstrap 4, Font awesome 4, Font Awesome 5 Free & Pro snippets
: 插入Bootstrap 4 或 Font awesome 語法片段。DotENV
:讓.env檔也可以套用高亮度設定Beautify
:美化CSS
"beautify.language": {
"js": {
"type": [
"javascript",
"json"
],
"filename": [
".jshintrc",
".jsbeautifyrc"
]
// "ext": ["js", "json"]
// ^^ to set extensions to be beautified using the javascript beautifier
},
"css": [
"css",
"scss"
],
"html": [
"htm",
"html",
"blade.php"
]
// ^^ providing just an array sets the VS Code file type
},
Git History
:可觀看所有的Git提交紀錄indent-rainbow
:以顏色標出縮排Laravel Blade Snippets
:Laravel 樣板的快速語法
// enable tab to expanse emmet tags
"emmet.triggerExpansionOnTab": true,
// if you would like to enable blade format
"blade.format.enable": true,
Laravel 5 Snippets
:Laravel 快速語法(作者為 Winnie Lin 的那個)Laravel goto view
:可直接快速開啟視圖檔案laravel-goto-controller
:可直接快速開啟控制器檔案Material Icon Theme
:精美的檔案圖示PHP IntelliSense
:自動提示已定義的class
// 設定是否啟用內建 PHP 語言建議。此支援會建議 PHP 全域和變數。
"php.suggest.basic": false,
這裡有全部安裝好的VSCodePortable1.24.0自解壓縮檔
composer diagnose
若是執行時出現「composer : 無法辨識 'composer' 詞彙是否為 Cmdlet、函數、指令檔或可執行程式的名稱。請檢查名稱拼字是否正確,如果包含路徑的話,請確認路徑是否正確,然後再試一次。」那影在環境變數中加入以下兩個路徑即可(其中%USERPROFILE%
就是使用者家目錄,如:C:\Users\帳號):
%USERPROFILE%\AppData\Local\ComposerSetup\bin
%USERPROFILE%\AppData\Roaming\Composer\vendor\bin
如果有key設定失敗的問題,請執行以下指令,並連結到 https://composer.github.io/pubkeys.html 依據指示貼入金鑰設定值即可:
composer self-update --update-keys
composer self-update
如果更新時出現下面訊息,表示composer太舊,那就請移除composer,並安裝 > 1.73 版的composer
[RuntimeException]
SHA384 is not supported by your openssl extension, could not verify the phar file integrity
composer update
Failed to decode response: zlib_decode(): data error
那麼可以試試執行清除快取的指令,有時候就可以解決了。
composer clear-cache
composer global require hirak/prestissimo
如此,Composer 便會自動判斷每個元件的最穩定版本編號,並下載該元件,以及更新相關元件。
hirak/prestissimo 是一個可以加快composer安裝速度的套件,可以讓 composer 也能多進程並行下載。
若要查詢某套件的詳情或版本:
composer show 套件名稱
若要解除已安裝套件
composer remove 套件名稱
(以下節錄自30 天精通 Git 版本控管 (05):了解儲存庫、工作目錄、物件與索引之間的關係)
cd ~
vagrant box add laravel/homestead
接著安裝Homestead
git clone https://github.com/laravel/homestead.git Homestead
執行Homestead初始化(用來產生Homestead.yaml等檔案)
cd .\Homestead\
.\init.bat
先檢查ssh的金鑰是否存在
ls ~/.ssh
裡面若有id_rsa
及id_rsa.pub
就OK,可以跳過此步驟。若出現「ls : 找不到 'C:\Users\使用者名稱\.ssh' 路徑,因為它不存在。」或者檔案不存在就請從開始選單打開「Git→Git Bash」,並執行以下指令產生金鑰(Email 請用GitGub的登入Email), 過程可以都選擇預設,一路 Enter
鍵即可 。
ssh-keygen -t rsa -C "your_email@example.com"
看起來像這樣:
接著編輯設定檔Homestead.yaml
(主要用來設定 Homestead 的站點和資料庫等訊息 )
---
ip: "192.168.10.10"
memory: 2048
cpus: 1
provider: virtualbox
authorize: ~/.ssh/id_rsa.pub
keys:
- ~/.ssh/id_rsa
folders:
- map: ~/xampp/htdocs
to: /home/vagrant/public_html
- map: ~/xampp/phpMyAdmin
to: /home/vagrant/public_html/phpmyadmin
sites:
- map: 專案目錄名稱.test
to: /home/vagrant/public_html/專案目錄名稱/public
- map: phpmyadmin.test
to: /home/vagrant/public_html/phpmyadmin
databases:
- homestead
# blackfire:
# - id: foo
# token: bar
# client-id: foo
# client-token: bar
# ports:
# - send: 50000
# to: 5000
# - send: 7777
# to: 777
# protocol: udp
以上這樣的設定,就可以把local端的網頁目錄htdocs
和Homestead的網頁目錄下public_html
同步(實際上就是共享資料夾),但要注意的是,一定要多一層資料夾(如:public_html
名稱可換),但不能只有/home/vagrant
否則會無法登入。
另外,由於xampp裡有內建phpMyAdmin,而Homestead剛好沒有,所以,我們也設定一組對應,以便能夠操作虛擬機裡面的資料庫。
此外,僅程式是共用的,但資料庫是分別儲存的(除非設定到遠端的資料庫,例如:https://db4free.net/)
編輯C:\Windows\System32\drivers\etc\hosts
檔,在最後加入(輸入http://exam.test就是跑Homestead上的網站,輸入http://phpmyadmin.test則是管理Homestead上的資料庫,輸入http://exam.local則是跑xampp上的網站)
192.168.10.10 exam56.test
192.168.10.10 phpmyadmin.test
127.0.0.1 exam56.local
由於phpMyAdmin實際上是放在windows底下,所以其權限會是777,如此,當您在執行phpMyAdmin時就會出現「設定檔權限錯誤,檔案不應開啟所有寫入權!」的訊息且無法使用,因此,我們必須將該檢查關閉,才能順利使用phpMyAdmin。故開啟xampp\phpMyAdmin\libraries\config.default.php
編輯之(約2998行):
$cfg['CheckConfigurationPermissions'] = false;
最後就可以啟動Homestead囉!(須切換到Homestead目錄裡面)當設定檔有異動的時候,必須加個--provision
,若沒異動,不加該參數也沒關係。(注意,須移除HyperV,且virtualbox也必須是最新版才能正常啟動)
cd ~/Homestead
vagrant up --provision
啟動過程中確保網路設定正確(有勾選「線路已連接」)
亦可登入操作(無須帳號密碼)
vagrant ssh
登出請執行
exit
關閉Homestead請執行
vagrant halt
C:\Users\使用者\xampp\htdocs
等同~/xampp/htdocs
)下,並將裡頭的東西清空。(之後凡提及「專案目錄」,請視實際情形,自行輸入正確目錄)C:\Users\使用者\xampp\htdocs
。~/xampp/htdocs
下貼上以下語法(別在Homestead中貼,會比較慢,因為Homestead中沒有安裝 hirak/prestissimo 套件),以直接建立專案(可指定版本,但要確定環境變數中的PHP是7.1.3以後的版本,若未指定版本,則會依據可抓到的PHP版本取用最新的laravel):
composer create-project laravel/laravel exam56 "5.6.*" --prefer-dist
exam56
資料夾,以及相關所需檔案。--prefer-dist
表示是從壓縮檔下載,可加快下載速度(不過其實還是大概需要五到十分鐘左右的安裝時間)。
composer require "laravel/installer"
laravel new exam56
cd exam56
php artisan --version
exam56
目錄加至左邊目錄,以方便存取。目錄或檔名 | 用途說明 |
app | 專案核心,專案程式都在裡面 |
-- Console | 和專案相關的命令列檔案 |
-- Exceptions | 例外狀況處理 |
-- Http | 放置Http請求流程中所執行內容 |
----Controllers | 控制器 |
----Middleware | 中介層 |
-- Providers | 放置應用程式的服務提供者,由config/app.php中的providers設定載入 |
-- User.php | |
bootstrap | 框架啟動的程式碼 |
--cache | 快取目錄,需777(寫入權限) |
config | 設定檔案的目錄 |
database | 專案資料表 |
public | 專案網站的根目錄,都是靜態檔案 |
resources | 專案相關的資源檔案,包括 views、lang、assets等。 |
routes | 路由目錄 |
storage | 儲存設定目錄,需777(含底下目錄及檔案都要有寫入權限) |
tests | 單元測試目錄 |
vendor | composer的套件目錄 |
.env | 專案設定檔 |
.env.example | 專案設定檔範本 |
.gitattributes | git用檔案 |
.gitignore | git忽略檔 |
artisan | 主程式進入點,非常常用的指令 |
composer.json | composer 檔案,紀錄所使用的php套件資訊及版本 |
composer.lock | composer 鎖定檔,紀錄該專案下載的php套件資訊及版本 |
package.json | 專案相關composer套件檔 |
phpunit.xml | |
readme.md | git用 |
server.php | 啟動內建伺服器 |
webpack.mix.js | webpack模組整合工具 |
exam56
git init
git add --all
git commit -m "建立專案"
config
目錄下storage
目錄下的所有目錄和 bootstrap/cache
目錄需有寫入權限。/專案/config/app.php
。如果VS Code已經在專案目錄下,可以直接在終端機用code
命令,快速利用VS Code開啟檔案,例如:
code config/app.php
timezone
和 locale
:
'timezone' => 'Asia/Taipei',
'locale' => 'zh-TW',
'fallback_locale' => 'zh-TW',
env('xxx', 'ooo')
的,代表可以直接到 .env
檔設定即可,會以 .env 的設定值優先。後面的ooo
一般並不會生效,除非中.env
沒有xxx
的設定項目,此時ooo
才會有作用。'name' => env('APP_NAME', '線上測驗系統')
並不會真的有作用,因為會去找.env
裡面的APP_NAME=Laravel
設定,所以,屆時呈現的仍是Laravel
而非線上測驗系統
,除非去把.env
裡面的APP_NAME
設定刪除,那才會顯示成線上測驗系統
。
$timezone = config('app.timezone');
config(['app.name' => '我的第一個專案']);
.env
設定(不支援中文),尤其是資料庫部份一定要修改,請開啟 /專案/.env
APP_NAME=Exam
//寄信設定(請填入mailtrap的設定資訊)
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=5f51684abd9780
MAIL_PASSWORD=9dc25ae9e2b955
MAIL_ENCRYPTION=tls
APP_NAME=線上測驗系統
APP_NAME="Laravel Exam"
homestead
,密碼為secret
,並建立一個同名資料庫即可。utf8_unicode_ci
」或「utf8_general_ci
」即可。.env
不會被送上 git,所以,環境可以使用自己的 .env
設定
php artisan tinker
App::environment();
看起來像這樣:
App::environment()
是印出目前的使用環境,若有傳參數進去,那就會判斷是否為該值,例如:
if (App::environment('local')) {
// 判斷環境是否為 local
}
if (App::environment(['local', 'staging'])) {
// 判斷環境是否為 local 或 staging
}
.env
檔案中列出的所有變數將被加載到 PHP 的超級全局變數 $ _ENV
中
git config --global credential.helper wincred
/專案/public/index.php
開始,這裡是系統的入口點。index.php
它主要是負責載入 Composer 生成的自動加載器,然後從 /專案/bootstrap/app.php
取得 Laravel 的服務容器物件。/專案/app/Http/Kernel.php
中的HTTP內核,HTTP內核就會傳回 HTTP 回應。接著Request
請求就會被轉交給路由器來進行調度。
路由器將請求發送到路由或控制器或任何運行於路由的特定中間件。
artisan
指令只有在專案資夾中才能使用。
php artisan make:auth
utf8mb4
所以,若資料庫使用的版本是 MySQL 5.7.7 或MariaDB 10.2以下的版本沒做修改的話將無法順利執行建立資料表動作,會出現如下訊息:\專案\app\Providers\AppServiceProvider.php
,先在上方加入:
use Illuminate\Support\Facades\Schema;
接著在boot()
中加入以下語法,告知預設文字字元數為191個:
public function boot()
{
Schema::defaultStringLength(191);
}
~/xampp/htdocs/專案/
底下執行;若要在Homestead中生效,須vagrant ssh
進入虛擬機後,到 ~/public_html/專案/
下執行):
php artisan migrate
migrations
、password_resets
、users
php artisan serve
use Illuminate\Support\Facades\Auth;
if (Auth::check()) {
// 這個用戶已經登錄...
}
$user = Auth::user();
指定條件來查找使用者:
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
//某Email和密碼,且已啟用的用戶
}
登入
Auth::login($user);
// 登錄並且「記住」用戶
Auth::login($user, true);
登出
Auth::logout();
\xampp\apache\conf\httpd.conf
) 中的設定
DocumentRoot
請設定至專案的 public 目錄,如:/安裝路徑/xampp/htdocs/exam56/public
(若不打算變更的話,屆時只要在瀏覽器上輸入 http://localhost/exam56/public/ 亦可)Options Indexes FollowSymLinks Includes ExecCGI
改為
Options Indexes FollowSymLinks MultiViews Includes ExecCGI
DocumentRoot "/Users/tad/Dropbox/xampp/htdocs/exam56/public" <Directory "/Users/tad/Dropbox/xampp/htdocs/exam56/public"> # # Possible values for the Options directive are "None", "All", # or any combination of: # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews # # Note that "MultiViews" must be named *explicitly* --- "Options All" # doesn't give it to you. # # The Options directive is both complicated and important. Please see # http://httpd.apache.org/docs/2.4/mod/core.html#options # for more information. # Options Indexes FollowSymLinks MultiViews Includes ExecCGI # # AllowOverride controls what directives may be placed in .htaccess files. # It can be "All", "None", or any combination of the keywords: # AllowOverride FileInfo AuthConfig Limit # AllowOverride All # # Controls who can get stuff from this server. # Require all granted </Directory>
MultiViews
的作用是當訪問到目錄中不存在的檔案時( 如訪問http://localhost/test/target), 則apache會尋找該目錄下的所有target.*檔案。如果test目錄下存在target.jpg檔案, 則會把這個檔案返回給客戶, 而不是直接傳回404錯誤訊息。extension=openssl extension=fileinfo extension=pdo_mysql extension=mbstring
The only supported ciphers are AES-128-CBC and AES-256-CBC with the correct key lengths.
的訊息,請執行以下指令重設app_key即可
php artisan key:generate php artisan config:clear
composer require caouecs/laravel-lang:~3.0
/exam56/resources/lang/zh-TW
C:\Windows\System32\drivers\etc\hosts
檔的話,可以連上 http://exam56.local(XAMPP網站)、http://exam56.test(Homestead網站)當使用者在瀏覽器輸入網址,或者透過連結執行網站某個操作,這些「網址」會變成Laravel要執行什麼動作的依據,這樣的機制,就是Route(路由)。
/專案/routes/web.php
,請開啟之。
Route::get('/', function () {
return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
Route::get('網址', 動作);
來取得符合條件的網址,並告知系統要做啥事。get
是動詞(亦即HTTP的傳輸方式),其他動詞才還有post
、patch
、delete
等。/
目錄時,會傳回名字叫做welcome
的視圖(View),該視圖會去讀取名為 welcome.blade.php
的樣板,簡寫為view('welcome')
。專案/resources/views/
」底下,檔名一律為「xxx.blade.php」。/專案/resources/views/welcome.blade.php
,高興的話,可以隨意改一下內容。->with()
,如:
Route::get('/', function () {
return view('welcome')->with('name','tad')->with('say','嗨!');
});
Route::get('/', function () {
$data = ['name' => 'tad', 'say' => '嗨!'];
return view('welcome', $data);
});
當然也可以簡寫為
Route::get('/', function () {
return view('welcome', ['name' => 'tad', 'say' => '嗨!']);
});
compact()
函數
Route::get('/', function () {
$name = 'tad';
$say = '嗨!';
return view('welcome', compact('name', 'say'));
});
/專案/resources/views/welcome.blade.php
樣板,可以加入:
<div class="title m-b-md">
{{$name}} {{$say}} Laravel
</div>
看起來像這樣
/專案/resources/views/layouts/app.blade.php
實際上是我們上個單元加入使用者認證下的產物,預設其實是沒有的。/專案/resources/views/layouts/app.blade.php
並直接將 app.blade.php
作為系統的主樣板來使用。
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
@include('layouts.nav')
<main class="py-4">
@yield('content')
</main>
</div>
</body>
</html>
{{ }}
裡面可以放PHP變數、函數...等內容,詳情可見:https://laravel-china.org/docs/laravel/5.6/blade/1375#6a96da{{ }}
語句會自動調用 PHP的 htmlspecialchars
函數防止 XSS 攻擊。不想轉義的話,可以使用{!! !!}
語法。{{ }}
,或者有其他前端套件也是使用{{ }}
來進行渲染,那麼,只要寫成@{{ }}
即可。/專案/resources/views/layouts/nav.blade.php
),並於此處用@include('layouts.nav')
引入該檔案。layouts/nav.blade.php
,並在主樣板中用@include()
引入子樣板即可,以保持樣板容易理解的狀態。nav
即可,不須寫完整的nav.blade.php
,若該樣板放在views的子目錄下,如layouts/nav.blade.php
,則寫成layouts.nav
即可。
<nav class="navbar navbar-expand-md navbar-light navbar-laravel">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav mr-auto">
</ul>
<!-- Right Side Of Navbar -->
<ul class="navbar-nav ml-auto">
<!-- Authentication Links -->
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
</div>
</nav>
{{ __('Login') }}
這樣的用法,這表示此字串是可以翻譯成其他語系的。/專案/resources/lang/zh-TW.json
語系檔__('
來搜尋所有有語系的部份。zh-TW.json
語系檔中(JSON格式):
{
"Login": "登入",
"Password": "密碼",
"Remember Me": "記住我",
"E-Mail Address": "電子信箱",
"Forgot Your Password?": "忘記密碼?",
"Reset Password": "重設密碼",
"Send Password Reset Link": "寄送重設密碼連結",
"Confirm Password": "確認密碼",
"Register": "註冊",
"Name": "姓名",
"Logout": "登出"
}
/
)就傳回view('welcome')
,也就是去呼叫 /專案/resources/views/welcome.blade.php
這個檔的意思。
Route::get('/', function () {
return view('welcome');
});
/專案/resources/views/welcome.blade.php
以修改之:
@extends('layouts.app')
@section('content')
<div class="container">
<h1>隨機題庫系統</h1>
</div>
@endsection
此檔主要設定content
樣板變數,到時後會套入主樣板 layouts/app.blade.php
中。
@extends('layouts.app')
,就是說,目前這個樣板是源自於(或是繼承自)layouts/app.blade.php
視圖。
@section('樣板變數名稱','值')
用來定義一個樣板變數,及其對應值
若是只有@section('樣板變數名稱')
,那麼下方可直接填入內容,然後用 @endsection
來做個結束亦可。
@yield('xxx')
會顯示定義的樣板變數xxx
的內容
/專案/resources/views/layouts/app.blade.php
變這樣:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<!-- Fonts -->
<link rel="dns-prefetch" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Raleway:300,400,600" rel="stylesheet" type="text/css">
<!-- Styles -->
<link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
<div id="app">
@include('layouts.nav')
<main class="py-4">
<div class="container">
@yield('content')
</div>
</main>
</div>
</body>
</html>
首頁視圖/專案/resources/views/welcome.blade.php
變得更簡單了:
@extends('layouts.app')
@section('content')
<h1>隨機題庫系統</h1>
@endsection
詳細 Blade 樣板語法請參考:https://laravel-china.org/docs/laravel/5.6/blade/1375
Blade語法 | 說明或用法 | 控制器對應 |
{{-- 註解 --}} | 就是註解 | |
{!!$content!!}} | 輸出$content內容(不會過濾) | return view('welcome', ['content' => $content]); |
{{$content}} | 經過htmlentities過濾的內容(< 會變成< ) |
return view('welcome', ['content' => $content]); |
Blade語法 | 說明或用法 |
@if(條件) @elseif(條件) @else @endif |
if 判斷 |
@unless(條件) @endunless |
當條件為false時才成立 |
@for(起始值;條件;變化量) @endfor |
for迴圈 |
@while(條件) @endwhile |
while迴圈 |
@foreach(陣列 as 鍵值) @endforeach |
foreach迴圈,可用 $loop->first以及$loop->last變數 |
@forelse(陣列 as 鍵值) @empty @endforelse |
效果同foreach迴圈,只是會檢查陣列是否為空,若為空會執行@empty以下的內容 |
標記區塊的結束,通常作為@section
的結束標籤,例如:這樣只是定義content的內容,並不會直接呈現,需利用@yield('content')
才能顯示。
@section('content')
<h1>隨機題庫系統</h1>
@endsection
專案\resources\views\layouts\nav.blade.php
,在@else
下方多一個 @section('my_menu')
,也就是自定義一個樣板變數my_menu
並直接顯示。
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
</li>
@else
@section('my_menu')
<li><a class="nav-link" href="/home">{{ __('Home') }}</a></li>
@show
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
專案\resources\views\welcome.blade.php
,在最後加上:
@section('my_menu')
<li><a class="nav-link" href="/home">我的選項</a></li>
@parent
@stop
@parent
刪除,則會以「我的選項」取代原有選項
composer require backpack/crud
執行安裝 backpack:base,會自動綁訂backpack:base套件,這裡會需要一點時間,因為檔案蠻多的。
php artisan backpack:base:install
執行安裝 backpack crud 套件,用來建立CRUD機制。安裝過程中會問您是否要建立elfinder檔案管理套件,請直接按Enter(即yes)進行安裝。
php artisan backpack:crud:install
修改 /專案/vendor/backpack/base/src/resources/lang/zh-hant
及 /專案/vendor/backpack/crud/src/resources/lang/zh-hant
,將 zh-hant 改為 zh-TW 如此,然後把原本放在套件中的中文語系複製到專案中,才能看到完整中文語系,未來更新時語系也才不會消失。
mv resources/lang/vendor/backpack/zh-hant resources/lang/vendor/backpack/zh-TW
cp vendor/backpack/base/src/resources/lang/zh-hant/base.php resources/lang/vendor/backpack/zh-TW/base.php
最後,只要輸入http://localhost/admin/dashboard就可以連到後台囉!
composer require backpack/permissionmanager
建立設定檔:
php artisan vendor:publish --provider="Backpack\PermissionManager\PermissionManagerServiceProvider"
建立資料表:
php artisan migrate
請修改/專案/app/User.php
加上以下程式,如此,權限機制才能正常使用:
<?php
namespace App;
use Backpack\CRUD\CrudTrait;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable
{
use Notifiable;
use CrudTrait;
use HasRoles;
/**
* The attributes that are mass assignable.
*
* @var array
*/
接著修改/專案/config/auth.php
,把原本的使用者資料模型換成此套件的使用者資料模型
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => Backpack\Base\app\Models\BackpackUser::class,
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
修改 /專案/resources/views/vendor/backpack/base/inc/sidebar_content.blade.php
加入權限管理選項
<!-- Users, Roles Permissions -->
<li class="treeview">
<a href="#"><i class="fa fa-group"></i> <span>使用者角色權限管理</span> <i class="fa fa-angle-left pull-right"></i></a>
<ul class="treeview-menu">
<li><a href="{{ url(config('backpack.base.route_prefix', 'admin') . '/user') }}"><i class="fa fa-user"></i> <span>使用者</span></a></li>
<li><a href="{{ url(config('backpack.base.route_prefix', 'admin') . '/role') }}"><i class="fa fa-group"></i> <span>角色</span></a></li>
<li><a href="{{ url(config('backpack.base.route_prefix', 'admin') . '/permission') }}"><i class="fa fa-key"></i> <span>權限</span></a></li>
</ul>
</li>
最後直接下載中文語系解壓縮到\專案\resources\lang\vendor\backpack
中,然後解壓縮覆蓋!這樣日後若升級套件,加入的中文語系才不會又消失。
為了方便連結,我們可以修改\專案\resources\views\layouts\nav.blade.php
的樣板檔,加入自製選項,由於只開放管理員進到後台,所以,利用@role('管理員')
及@endrole
來進行身份判斷。:
...略...
@else
@section('my_menu')
@role('管理員')
<li><a class="nav-link" href="/admin">{{ __('Admin') }}</a></li>
@endrole
@show
...略...
然後,編輯一下\專案\resources\lang\zh-TW.json
語系檔
{
"Login": "登入",
//....略...
"Admin":"後台管理"
}
如此,就有方便的連結可以按了。
詳情可見:https://github.com/laravel-backpack/permissionmanager#using-blade-directives
php artisan make:middleware AdminMiddleware
\專案\app\Http\Middleware\AdminMiddleware.php
內容為:
<?php
namespace App\Http\Middleware;
use Closure;
class AdminMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (!$request->user()->hasAnyPermission('後台管理')) {
abort(403);
}
return $next($request);
}
}
\專案\config\backpack\base.php
的路由設定:
'middleware_class' => [
\Backpack\Base\app\Http\Middleware\CheckIfAdmin::class,
\App\Http\Middleware\AdminMiddleware::class,
],
\專案\resources\views\errors\403.blade.php
,改成:
@extends('layouts.app')
@section('content')
<h1>403 Forbidden.</h1>
<p>您沒有權限可以執行目前的動作喔!</p>
@endsection
die(var_dump())
這種斷點除錯,可以使用dd()
這個函數
$user = Auth::user();
dd($user);
$user = Auth::user();
dd(get_class_methods($user));
--dev
參數:
composer require barryvdh/laravel-debugbar --dev
\專案\config\app.php
'aliases' => [
//...略...
'View' => Illuminate\Support\Facades\View::class,
'Debugbar' => Barryvdh\Debugbar\Facade::class,
],
php artisan vendor:publish --provider="Barryvdh\Debugbar\ServiceProvider"
一個Eloquent 模型對應一張表,model (模型)就是用來操作資料庫資料用的。
migration 可視為建立SQL資料表的方法(有點類似xxx.sql的作用),可分次建立,亦可回覆上一動。
php artisan make:model Exam --migration
模型的第一個字請用大寫(大駝峰命名法),單複數不拘,但似乎使用單數的人居多。
若 migration 檔案已經存在了,則用下面任一語法均可
php artisan make:model Exam
php artisan make:model Exam --no-migration
/專案/app/Exam.php
模型
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Exam extends Model
{
//
}
還有 /專案/database/migrations/日期_create_exams_table.php
(如:2018_07_01_161014_create_exams_table.php)
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateExamsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('exams', function (Blueprint $table) {
$table->increments('id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('exams');
}
}
Exam
,其內定資料表名稱辨識exams
,所以資料表名稱若不符合此內規,則可自行定義一個 $table
屬性來告知正確的資料表名稱。$primaryKey
屬性來覆寫這個慣例。(/專案/app/Exam.php
)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Exam extends Model
{
//若符合慣例可以不用設定這些
protected $table = 'exams';
protected $primaryKey = 'id';
}
/專案/app
底下,若要放在/專案/app
下的子目錄,記得也要修改 namespace建立一個隨機題庫,並能進行測驗,會用到許多表。為了減低複雜程度,我們暫時先做一下限制:
migrate 檔案就是用來定義資料表欄位的檔案,屆時可用指令自動建立(或增減)資料表欄位
先確定 /專案/.env
中的資料庫設定有設定正確
確定有啟動MySQL資料庫,並確定已經建立 DB_DATABASE 定義的資料庫
確認有無 /專案/database/migrations/日期_create_exams_table.php
(上一節有做了),若是還沒有上述檔案(或者是只要建立資料表,但不需要建立Model的時候),執行以下語法會自動生出 migrate 檔案(其中,是create_exams_table
檔案名稱,--create=exams
則是資料表名稱,慣例為複數):
php artisan make:migration create_exams_table --create=exams
編輯 /專案/database/migrations/日期_create_exams_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateExamsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('exams', function (Blueprint $table) {
$table->increments('id');
$table->string('title');
$table->integer('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->boolean('enable');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('exams');
}
}
title
:測驗的名稱user_id
:建立測驗的使用者編號enable
:是否啟用測驗, boolean()
在 MySQL 中其實是 tinyint(1)
類型,未來我們可能需要做一下型別轉換。若是要建立 foreign key
必須在還沒有資料的時候就先建立,日後若已經有資料,要再建立 foreign key
就會比較麻煩。(因為必須資料能對應的起來才可以建立)
$table->foreign('user_id')->references('id')->on('users');
建立各種欄位類型可參考:https://laravel-china.org/docs/laravel/5.6/migrations/1400#creating-columns
替欄位加入各種屬性請參考:https://laravel-china.org/docs/laravel/5.6/migrations/1400#column-modifiers
要修改欄位方法請參考:https://laravel-china.org/docs/laravel/5.6/migrations/1400#55682c
各種索引的建立請參考:https://laravel-china.org/docs/laravel/5.6/migrations/1400#b271e4
最後執行資料庫遷移即可建出新的資料表
php artisan migrate
php artisan migrate:rollback
若想刪除全部資料表重來,可以執行
php artisan migrate:reset
動詞 | 網址路徑 | 行為 | 路由名稱 | 一般路由寫法 | 有控制器的路由寫法 |
---|---|---|---|---|---|
GET | /資源 |
index 列表 |
資源.index |
Route::get('/exam', function () {
|
Route::get('/exam', 'ExamController@index') ->name('exam.index'); |
GET | /資源/create |
create 新增 |
資源.create |
Route::get('/exam/create', function () {
|
Route::get('/exam/create', 'ExamController@create') ->name('exam.create'); |
POST (支援 CSRF保護) |
/資源 |
store 儲存 |
資源.store | Route::post('/exam', function () { return view('store'); })->name('exam.store'); |
Route::post('/exam/store', 'ExamController@store') ->name('exam.store'); |
GET | /資源/{id} |
show 顯示一筆 |
資源.show | Route::get('/exam/{id}', function () { return view('show'); })->name('exam.show'); |
Route::get('/exam/{id}', 'ExamController@show') ->name('exam.show'); |
GET | /資源/{id}/edit |
edit 編輯 |
資源.edit | Route::get('/exam/{id}/edit', function () { return view('edit'); })->name('exam.edit'); |
Route::get('/exam/{id}/edit', 'ExamController@edit') ->name('exam.edit'); |
PUT/PATCH (支援 CSRF保護) |
/資源/{id} |
update 更新 |
資源.update | Route::patch('/exam/{id}', function () { return view('update'); })->name('exam.update'); |
Route::patch('/exam/{id}', 'ExamController@update') ->name('exam.update'); |
DELETE (支援 CSRF保護) |
/資源/{id} |
destroy 刪除 |
資源.destroy | Route::delete('/exam/{id}', function () { return view('destroy'); })->name('exam.destroy'); |
Route::delete('/exam/{id}', 'ExamController@destroy') ->name('exam.destroy'); |
exams
,那麼,路由可以這樣設定,左邊是路由,右邊是動作,底下的動作內容都是直接呼叫對應視圖(暫時不要跟著做,因為我們會直接用控制器來做更好):
Route::get('/exam', function () {
return view('index');
})->name('exam.index');
Route::get('/exam/create', function () {
return view('create');
})->name('exam.create');
Route::post('/exam', function () {
return view('store');
})->name('exam.store');
Route::get('/exam/{id}', function () {
return view('show');
})->name('exam.show');
Route::get('/exam/{id}/edit', function () {
return view('edit');
})->name('exam.edit');
Route::patch('/exam/{id}', function () {
return view('update');
})->name('exam.update');
Route::delete('/exam/{id}', function () {
return view('destroy');
})->name('exam.destroy');
function () {
return view('視圖名稱');
}
name('路由名稱')
加上命名,例如:
Route::get('/exam', function () {
return view('index');
})->name('exam.index');
<a href="{{ route('exam.index') }}">測驗一覽</a>
查詢有多少 Route 可用
php artisan route:list
exams
,而且已經有控制器(或者直接就想用控制器作法),那麼,路由可以這樣設定:
Route::get('/exam', 'ExamController@index')->name('exam.index');
Route::get('/exam/create', 'ExamController@create')->name('exam.create');
Route::post('/exam/store', 'ExamController@store')->name('exam.store');
Route::get('/exam/{id}', 'ExamController@show')->name('exam.show');
Route::get('/exam/{id}/edit', 'ExamController@edit')->name('exam.edit');
Route::patch('/exam/{id}', 'ExamController@update')->name('exam.update');
Route::delete('/exam/{id}', 'ExamController@destroy')->name('exam.destroy');
Route::resource('exam' , 'ExamController');
/create
(強烈建議,但非強制),搭配資源,其路由便是「/exam/create
」http://網址/exam/create
」時,系統便會新增測驗的界面get
/專案/resources/views/exam/create.blade.php
的視圖中,利用view()
來呼叫之exam.create
,以方便連結。所以,請開啟\專案\routes\web.php
網站路由並編輯之:
Route::get('/exam/create', function () {
return view('exam.create');
})->name('exam.create');
ExamController@create
去操作該做的後續動作。
Route::get('/exam/create', 'ExamController@create')->name('exam.create');
/專案/resources/views/exam/create.blade.php
@extends('layouts.app')
@section('content')
<h1>{{ __('Create Exam') }}</h1>
@endsection
如此 create.blade.php
就會繼承 layouts/app.blade.php
的樣板,並將設定值帶入使用。
由於有用到語系,所以,記得在\專案\resources\lang\zh-TW.json
增加一組語系設定
"Create Exam":"建立測驗"
請執行「http://localhost/exam/create」試試~
/專案/resources/views/layouts/nav.blade.php
把建立測驗的選項加入:
@section('my_menu')
@can('後台管理')
<li><a class="nav-link" href="/admin">{{ __('Admin') }}</a></li>
@endcan
@can('建立測驗')
<li><a class="nav-link" href="{{ route('exam.create') }}">{{ __('Create Exam') }}</a></li>
@endcan
@show
由於教師和管理員都有「建立測驗」的權限,與其判斷兩個角色,不如判斷一個權限來的簡單,所以,利用@can('建立測驗')
及@endcan
來判斷目前登入者有無「建立測驗」的權限。
後台管理部份原本是利用@role('管理員')
來判斷,但實際上,直接根據權限來判斷會更準確。
其中連結部份我們用 route('exam.create')
的寫法,直接呼叫路由名稱即可。
詳情請看:https://github.com/laravel-backpack/permissionmanager#using-blade-directives
composer require marvinlabs/laravel-html-bootstrap-4
{{ bs()->openForm('post', '/exam' , [ 其他選項陣列]) }}
{{ bs()->closeForm() }}
'files' => true, //需要上傳檔案時
'model' => $exam, //綁預設值時
'hideErrors' => true, //隱藏錯誤
'inline' => true, //行內表單
//自定義表單屬性
'attributes' => [
'id' => 'create_user_form',
'v-cloack' => '',
]
{{ bs()->input('text', 'username', '吳弘凱')->placeholder('請填入姓名') }}
{{ bs()->text('username')->placeholder('請填入姓名') }}
{{ bs()->text('username', '小型輸入框')->sizeSmall() }}
{{ bs()->text('username', '大型數入框')->sizeLarge() }}
{{ bs()->hidden('op', '隱藏欄位') }}
{{ bs()->password('pass', '密碼欄位') }}
{{ bs()->email('email', 'Email欄位') }}
{{ bs()->token() }}
{{ bs()->textArea('content', '這是大量文字框') }}
{{ bs()->select('enable', ['1' => '開啟', '0' => '關閉'], '1') }}
//多選
{{ bs()->select('color', ['red' => '紅色', 'green' => '綠色', 'biue' => '藍色'])
->multiple()
->value(['red', 'green'])
->placeholder('可多選') }}
//高興的話placeholder()也可以用第二個參數賦予值
{{ bs()->select('color', ['red' => '紅色', 'green' => '綠色', 'biue' => '藍色'])
->placeholder('請選擇顏色', -1) }}
{{ bs()->checkbox('remember')->description('記住我') }}
//預設勾選
{{ bs()->checkbox('remember')->description('記住我')->checked() }}
//整合上方的一行式寫法
{{ bs()->checkbox('remember', '記住我', true) }}
//可以自訂值,也可以設成inline
{{ bs()->checkbox('remember', '記住我')->value('yes')->inline() }}
//不指定值的話,預設的值為1
{{ bs()->radio('enable')->description('啟用') }}
//預設勾選
{{ bs()->radio('enable')->description('啟用')->checked() }}
//整合上方的一行式寫法
{{ bs()->radio('enable', '啟用', true) }}
//可以自訂值,也可以設成inline
{{ bs()->radio('enable', '啟用')->value('yes')->inline() }}
//多個選項的寫法
{{ bs()->radioGroup('enable', [1 => '啟用', 0 => '關閉'])
->selectedOption(1)
->inline()
->radioDisabled()
->addRadioClass(['bg-light', 'my-3']) }}
'files' => true
)
{{ bs()->file('avatar2', '選擇一個檔案') }}
{{ bs()->simpleFile('avatar') }}
primary
, secondary
, success
, danger
, warning
, info
, light
, dark
)
{{ bs()->submit('送出按鈕') }}
{{ bs()->button('一般按鈕') }}
//第二個參數指定樣式,第三個是否為外框按鈕
{{ bs()->button('外框按鈕', 'success', true) }}
//把連結做成按鈕
{{ bs()->a('#', '把連結做成按鈕')->asButton('secondary') }}
primary
, secondary
, success
, danger
, warning
, info
, light
, dark
)
{{ bs()->badge()->text('預設徽章') }}
{{ bs()->badge()->text('顯示成藥丸狀')->pill() }}
{{ bs()->badge('info')->text('加上連結')->link('#') }}
->sizeSmall()
或 ->sizeLarge()
來控制尺寸,亦可和 textarea
及 button
搭配使用)
{{ bs()->inputGroup()
->prefix('共')
->suffix('元')
->control(bs()->text('username')->placeholder('請填入金額')) }}
{{ bs()->text('username', '這是唯讀的')->readOnly() }}
{{ bs()->text('username', '這是唯讀的,而且顯示成一般文字(但仍是欄位)')->readOnly(true) }}
{{ bs()->text('username', '這是無效的')->disabled() }}
姓名
」替換成想要的標籤,bs()->text('username')
換成想要的元件即可。
{{ bs()->formGroup()
->label('姓名')
->control(bs()->text('username'))
->helpText('請在此填入姓名')}}
showAsRow()
就可以使用水平表單label('姓名', false, 'text-sm-right')
的第二個參數是是否使用 sr-only
,若true
,標籤會消失(預設為false
)。第三個參數是可以自行加入額外的class,例如用text-sm-right
讓標籤只有在螢幕大於576px時才會靠右 。姓名
」替換成想要的標籤,bs()->text('username')
換成想要的元件即可。
{{ bs()->formGroup()
->label('姓名', false, 'text-sm-right')
->control(bs()->text('username'))
->showAsRow() }}
type
可套用 primary
、secondary
、success
、danger
、warning
、info
、light
、dark
),原component位於\專案\vendor\marvinlabs\laravel-html-bootstrap-4\resources\views\alert.blade.php
@component('bs::alert', ['type' => 'info', 'animated' => true, 'dismissible' => true, 'data' => ['alert-id' => 40, 'context' => 'sample-code']])
@slot('heading')
info 警告視窗
@endslot
<p>dismissible 右上會出現 x 符號,用來關閉;animated 在關閉時會有漸變效果;data 可以用來設置一些屬性</p>
<p>除了 type 外,其餘參數截可省略</p>
@endcomponent
\專案\vendor\marvinlabs\laravel-html-bootstrap-4\resources\views\jumbotron.blade.php
@component('bs::jumbotron', ['fluid' => true])
@slot('heading')
巨幕主標題
@endslot
@slot('subheading')
這裡是次標題
@endslot
<hr class="my-3">
<p>這裡愛寫啥都行</p>
@slot('actions')
{!! bs()->a('#', '這是個連結按鈕')->asButton('primary') !!}
@endslot
@endcomponent
/專案/resources/views/exam/create.blade.php
,簡易版可以這樣寫:
@extends('layouts.app')
@section('content')
<h1>{{ __('Create Exam') }}</h1>
@can('建立測驗')
{{ bs()->openForm('post', '/exam') }}
{{ bs()->text('title')->placeholder('請填入測驗標題') }}
{{ bs()->radioGroup('enable', [1 => '啟用', 0 => '關閉'])
->selectedOption(1)
->inline() }}
{{ bs()->submit('建立測驗') }}
{{ bs()->closeForm() }}
@else
@component('bs::alert', ['type' => 'danger'])
@slot('heading')
無建立測驗的權限
@endslot
@endcomponent
@endcan
@endsection
先用@can('建立測驗')
來判斷有無權限,若有,就製作表單。若無,則進入@else
顯示警告畫面。
{{ bs()->openForm('post', '/exam') }}
是用來產生表單,以{{ bs()->closeForm() }}
做結尾,裡面的設定說明如下:
回顧一下用來儲存的路由:動作是post,路徑是/exam
,別名是exam.store
,詳情請看這裡。
用url('/exam')
來設定表單欲傳送的位址,因為是用post方式傳遞,所以路徑直接指向測驗的路徑即/exam
即可,不需要指定/exam/store
。
@extends('layouts.app')
@section('content')
<h1>{{ __('Create Exam') }}</h1>
@can('建立測驗')
{{ bs()->openForm('post', '/exam') }}
{{ bs()->formGroup()
->label('測驗標題', false, 'text-sm-right')
->control(bs()->text('title')->placeholder('請填入測驗標題'))
->showAsRow() }}
{{ bs()->formGroup()
->label('測驗狀態', false, 'text-sm-right')
->control(bs()->radioGroup('enable', [1 => '啟用', 0 => '關閉'])
->selectedOption(1)
->inline())
->showAsRow() }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('建立測驗'))
->showAsRow() }}
{{ bs()->closeForm() }}
@else
@component('bs::alert', ['type' => 'danger'])
@slot('heading')
無建立測驗的權限
@endslot
@endcomponent
@endcan
@endsection
php artisan make:controller ExamController --resource
/專案/app/Http/Controllers/ExamController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ExamController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
//
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
//
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
//
}
/**
* Display the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function show($id)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function edit($id)
{
//
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return \Illuminate\Http\Response
*/
public function update(Request $request, $id)
{
//
}
/**
* Remove the specified resource from storage.
*
* @param int $id
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
}
index()
顯示資料(一般是列表)create()
建立新資料(通常是表單界面)store($request)
儲存資料show($id)
顯示某筆資料edit($id)
編輯某筆資料(通常是表單界面)update($request, $id)
更新某筆資料destroy($id)
刪除某筆資料
php artisan make:controller ExamController
/專案/routes/web.php
,從原本直接送到視圖,改成送到控制器,由控制器去處理後續動作。
Route::get('/exam', 'ExamController@index')->name('exam.index');
Route::get('/exam/create', 'ExamController@create')->name('exam.create');
修改控制器 /專案/app/Http/Controllers/ExamController.php
,將列表(首頁)或發佈界面,直接回傳視圖。
public function index()
{
return view('welcome');
}
public function create()
{
return view('exam.create');
}
再次連到http://localhost/exam或http://localhost/exam/create,如果畫面都和之前一樣的話,就表示路由已經成功由傳回視圖,改成用控制器來控制了。
\專案\resources\views\exam\create.blade.php
視圖,加入隱藏欄位,並用Auth::id()
取得目前登入者的使用者編號:
{{ bs()->openForm('post', '/exam') }}
{{ bs()->formGroup()
->label('測驗標題', false, 'text-sm-right')
->control(bs()->text('title')->placeholder('請填入測驗標題'))
->showAsRow() }}
{{ bs()->formGroup()
->label('測驗狀態', false, 'text-sm-right')
->control(bs()->radioGroup('enable', [1 => '啟用', 0 => '關閉'])
->selectedOption(1)
->inline())
->showAsRow() }}
{{ bs()->hidden('user_id', Auth::id()) }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('建立測驗'))
->showAsRow() }}
{{ bs()->closeForm() }}
Auth::id()
是用來取得目前登入者的編號,詳細用法可參考:https://laravel-china.org/docs/laravel/5.6/authentication/1379#retrieving-the-authenticated-user/專案/routes/web.php
,儲存部份改用控制器:
Route::post('/exam', 'ExamController@store')->name('exam.store');
/專案/app/Http/Controllers/ExamController.php
,修改 store
方法,儲存後本來應該要轉向到建立題目的頁面,但因為相關頁面都還沒做,所以還是暫時轉回測驗首頁。
public function store(Request $request)
{
$exam = new Exam;
$exam->title = $request->title;
$exam->user_id = $request->user_id;
$exam->enable = $request->enable;
$exam->save();
return redirect()->route('exam.index');
}
<?php
namespace App\Http\Controllers;
use App\Exam;
use Illuminate\Http\Request;
$request
就是使用者輸入的內容,以物件方式存在。詳情可參考:https://laravel-china.org/docs/laravel/5.6/requests/1367#accessing-the-requestExam
則是Eloquent Model,也就是用來操作exam資料表的模型。儲存部份可參考:https://laravel-china.org/docs/laravel/5.6/eloquent/1403#inserting-and-updating-modelsredirect()
用來轉向,詳細用法可參考:https://laravel-china.org/docs/laravel/5.6/responses/1368#c51dd1
public function store(Request $request)
{
Exam::create([
'title' => $request->title,
'user_id' => $request->user_id,
'enable' => $request->enable,
]);
return redirect()->route('exam.index');
}
接著到 /專案/app/Exam.php
設定哪些欄位可以使用 fillable
class Exam extends Model
{
protected $fillable = [
'title', 'user_id', 'enable',
];
}
enable
是boolean
類型,但在資料庫卻被存成數字,所以,需要做一下型別轉換,因此,我們利用$casts來進行型別轉換。關於型別轉換,可參考:https://laravel-china.org/docs/laravel/5.6/eloquent-mutators/1406#attribute-casting
protected $casts = [
'enable' => 'boolean',
];
protected $guarded = [
'id', 'password',
];
$request->all()
取得使用者填寫的所有資料陣列
public function store(Request $request)
{
Exam::create($request->all());
return redirect()->route('exam.index');
}
完工!
關於 Request 的詳情可以查看:https://laravel-china.org/docs/laravel/5.6/requests/
/專案/app/Http/Controllers/ExamController.php
,修改儲存方法:
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|min:2|max:255',
]);
Exam::create($request->all());
return redirect()->route('exam.index');
}
接著修改/專案/resources/views/exam/create.blade.php
樣板檔,當有錯誤的時候顯示出來:
@if (count($errors) > 0)
@component('bs::alert', ['type' => 'danger'])
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endcomponent
@endif
如此就可以測試看看了!
若發現沒有中文訊息,可以直接將\專案\vendor\caouecs\laravel-lang\src\zh-TW\
整個目錄複製到\專案\resources\lang\
底下
cp -R vendor\caouecs\laravel-lang\src\zh-TW resources\lang\
若是想自己加上屬於自己的訊息,那可以這樣做:
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|min:2|max:255',
], [
'required' => '「:attribute」為必填欄位',
'min' => '「:attribute」至少要 :min 個字',
'max' => '「:attribute」最多只能 :max 個字',
]);
Exam::create($request->all());
return redirect()->route('exam.index');
}
:attribute
會帶出有錯誤的表單元件的name,如此,會比較清楚是哪個欄位沒填好。
php artisan make:request ExamRequest
會產生 /專案/app/Http/Requests/ExamRequest.php
,authorize()
部份是用來判別目前登入者是否有權限執行此動作(驗證後要執行的動作),若懶得設可先填入 true
,以免無法使用表單。但我們因為有裝了權限控制的Backpack\PermissionManager套件,所以,直接用裡面的方法can()
來判斷有無權限即可。
public function authorize()
{
return $this->user()->can('建立測驗');
}
再將原先規則搬入rules()
中。
public function rules()
{
return [
'title' => 'required|min:2|max:255',
];
}
public function messages()
{
return [
'required' => '「:attribute」為必填欄位',
'min' => '「:attribute」至少要 :min 個字',
'max' => '「:attribute」最多只能 :max 個字',
];
}
若是想替欄位命名則可利用attribute來進行設定
public function attributes()
{
return [
'title' => '測驗標題',
];
}
修改 /專案/app/Http/Controllers/ExamController.php
控制器,修改 store
,$request
的類別改用 ExamRequest
,並將原本在裡面的驗證拿掉。
public function store(ExamRequest $request)
{
Exam::create($request->all());
return redirect()->route('exam.index');
}
在上方加入使用我們自製的 ExamRequest
use App\Http\Requests\ExamRequest;
update
時,也是比照辦理即可,無須在控制器裡面寫驗證。
public function update(ExamRequest $request, $id)
{
//
}
/exam
,控制器為 ExamController@index
,所以,我們先開啟routes/web.php
確認有沒有以下這個路由。
Route::get('/exam', 'ExamController@index')->name('exam.index');
若希望以後一登入就到此畫面,亦可將原本的/
及/home
路由修改一下,控制器均改為ExamController@index
,這樣的意思就是凡是連到首頁或者/home(登入後頁面)都會連到測驗首頁。
Route::get('/', 'ExamController@index')->name('index');
Route::get('/home', 'ExamController@index')->name('home.index');
接著開啟控制器 /專案/app/Http/Controllers/ExamController.php
,編輯裡面的index()
函數,為了儘量符合預設,我們把index()
的樣板改為exam.index
,也就是直接用view()
來執行/專案/resources/views/exam/index.bload.php
樣版的意思
public function index()
{
return view('exam.index');
}
由於index.bload.php
樣板還不存在,所以,可以直接把/專案/resources/views/welcome.blade.php
另存為 /專案/resources/views/exam/index.blade.php
樣板。
mv resources/views/welcome.blade.php resources/views/exam/index.blade.php
接著將home.blade.php
裡面的顯示轉向訊息,複製到/專案/resources/views/layouts/app.blade.php
中,如此,日後若有轉向訊息便可呈現出來(不管在什麼功能中)。詳情請參考:https://laravel-china.org/docs/laravel/5.6/responses/1368#1bca7f
<div class="container">
@yield('content')
@if(session('status'))
@component('bs::alert', ['type' => 'info'])
{{ session('status') }}
@endcomponent
@endif
</div>
home.blade.php
刪除(因為也用不到了)接著開啟控制器 /專案/app/Http/Controllers/ExamController.php
,編輯裡面的index()
函數,並將所有測驗的資料陣列用compact('exams')
傳至樣板
public function index()
{
$exams = Exam::all();
return view('exam.index',compact('exams'));
}
至於Exam::all()
則是Exam
模型取得所有資料的用法(在上面有use App\Exam;
才能這樣使用),更多方法可參考:https://laravel-china.org/docs/laravel/5.6/eloquent-collections/1405#26e4a9
接著開啟/專案/resources/views/exam/index.blade.php
利用@foreach
及@endforeach
寫法來產生迴圈:
@extends('layouts.app')
@section('content')
<h1>測驗一覽</h1>
<ul class="list-group">
@foreach($exams as $exam)
<li class="list-group-item">
{{ $exam->created_at->format("Y年m月d日") }} -
<a href="exam/{{ $exam->id }}">
{{ $exam->title }}
</a>
</li>
@endforeach
</ul>
@endsection
$exam->created_at->format("Y年m月d日")
是用Carbon 日期時間套件的功能,該套件已經內建於 Laravel 5 中,詳細用法可以參考:https://laravel-china.org/docs/laravel/5.6/eloquent-mutators/1335#7a3eff或 http://laravel5-book.kejyun.com/package/tool/package-tool-carbon.html
預設情況下,Eloquent 會把 created_at
和 updated_at
字段轉換成 Carbon 實例, 它繼承了 PHP 原生的 DateTime
類,並提供了各種有用的方法。若有其他日期欄位想轉換成Carbon 實例,可以直接在Model中利用 $dates
屬性,自行定義哪些日期類型字段會被自動轉換:
/**
* 應被轉換為日期的屬性。
*
* @var array
*/
protected $dates = ['birthday'];
至於日期格式,可以參考 PHP 手冊:http://php.net/manual/en/function.date.php
另外一種@forelse
搭配@empty
及@endforelse
寫法,可以在沒有資料的情形顯示自訂內容:
@extends('layouts.app')
@section('content')
<h1>測驗一覽</h1>
<ul class="list-group">
@forelse($exams as $exam)
<li class="list-group-item">
{{ $exam->created_at->format("Y年m月d日") }} -
<a href="exam/{{ $exam->id }}">
{{ $exam->title }}
</a>
</li>
@empty
<li class="list-group-item">尚無任何測驗</li>
@endforelse
</ul>
@endsection
大功告成!請直接在網址輸入: http://localhost/exam或 http://localhost(注意,後面不可以有 / ,否則路由以後會誤判)
其中每個測驗的連結均為:exam/編號
,如此,符合顯示單一資料的路由寫法
/專案/app/Http/Controllers/ExamController.php
public function index()
{
$exams = Exam::where('enable', 1)
->orderBy('created_at', 'desc')
->take(10)
->get();
return view('exam.index', compact('exams'));
}
where()
用來指定條件,我們指定有啟用的測驗
orderby()
用來指定排序,我們指定用建立時間做反向排序(由新到舊)
take()
用來抓出數量
get()
取得資料
若要用兩個(and)篩選條件:
$exams = Exam::where('enable', 1)
->where('created_at', '>', '2018-05-30')
若要用 or,則用閉包+orWhere()方式:
$exams = Exam::where(function ($query) {
$query->where('enable', 1)
->orWhere('user_id', 1);
})
詳情可參考:https://laravel-china.org/docs/laravel/5.6/queries/1398#387ca8
/專案/app/Http/Controllers/ExamController.php
控制器,將 get()
換成 paginate(數量)
即可
public function index()
{
$exams = Exam::where('enable', 1)
->orderBy('created_at', 'desc')
->paginate(2);
return view('exam.index', compact('exams'));
}
亦可用simplePaginate()
,如此,會顯示上頁、下頁。
修改 /專案/resources/views/exam/index.blade.php
樣板,加入分頁工具:
<div class="my-3">
{{ $exams->links() }}
</div>
亦可用$exams->total()來顯示所有資料數量:
<h1>測驗一覽<small>(共 {{$exams->total()}} 筆資料)</small></h1>
更多方法請參考:https://laravel-china.org/docs/laravel/5.6/pagination/1399#f704ff
exam/{{$exam->id}}
,所以,在/routes/web.php
新增一筆路由資料
Route::get('/exam/{id}', 'ExamController@show')->name('exam.show');
{id}
可以直接在控制器中變成變數$id
使用,所以,無須自己帶參數過去。不過路由的參數和控制器的變數是以先後位置來對應的,無關名稱,換言之,控制器中不命名為 $id
也是可以的。{id?}
這樣的方式。->where()
限制參數格式(否則exam/create
中的 create
也可能會被當成id),例如:限制id 只允許 0~9 的數字,如此可以降低路由被誤判的機會
Route::get('/exam/{id}', 'ExamController@show')->name('exam.show')->where('id', '[0-9]+');
Route::pattern()
方式來統一宣告(記得放最上面):
Route::pattern('id' , '[0-9]+');
接著在 /專案/app/Http/Controllers/ExamController.php
控制器中,修改 show
的方法,其中 $id
便是從路由過來的:
public function show($id)
{
$exam = Exam::find($id);
return view('exam.show', compact('exam'));
}
find()
是 Eloquent 根據主鍵用來取出該筆資料用的。詳情請看:https://laravel-china.org/docs/laravel/5.6/eloquent/1332#retrieving-single-models
最後,只要製作好樣板,讓資料顯示出來即可,請建立 /專案/resources/views/exam/show.blade.php
:
@extends('layouts.app')
@section('content')
<h1 class="text-center">{{$exam->title}}</h1>
<div class="text-center">
發佈於 {{$exam->created_at->format("Y年m月d日 H:i:s")}} / 最後更新: {{$exam->updated_at->format("Y年m月d日 H:i:s")}}
</div>
@endsection
如果還要更簡單一點,Laravel 提供「路由模型綁定」功能來簡化找出資料的方式,它可以讓我們自己定義一個特定的參數名稱來指示路由解析器去尋找一筆Eloquent紀錄(亦即根據流水號取得該筆資料),並將該資料傳入,而非將流水號傳入。
我們先修改路由,將{id}
直接改為{exam}
Route::pattern('exam', '[0-9]+');
//略
Route::get('/exam/{exam}', 'ExamController@show')->name('exam.show');
然後修改控制器\專案\app\Http\Controllers\ExamController.php
,將show()
括號中的$id
改為和路由傳進來的名稱一致,亦即$exam,然後在類型約束(typehint)上加入Exam
模型。如此,Laravel 就會自動去抓取該編號的所有資料,因此,以下第3行的$exam
實際上是整個Exam
模型資料,而非編號。
public function show(Exam $exam)
{
return view('exam.show', compact('exam'));
}
建立Topic(題目)的 Eloquent 模型,以便將一個資料表變成一個物件來操作,並且順便產生 migration 檔案
php artisan make:model Topic --migration
編輯 /專案/database/migrations/日期_create_topics_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTopicsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('topics', function (Blueprint $table) {
$table->increments('id');
$table->string('topic');
$table->unsignedInteger('exam_id');
$table->foreign('exam_id')->references('id')->on('exams')->onDelete('cascade');
$table->string('opt1');
$table->string('opt2');
$table->string('opt3');
$table->string('opt4');
$table->unsignedTinyInteger('ans');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('topics');
}
}
topic
:題目exam_id
:對應的測驗編號opt1
~opt4
:選項1~4ans
:正確解答onDelete('cascade')
是一個約束條件,也就是當測驗刪除時,連同題目也一併刪除之意。 詳情:https://laravel-china.org/docs/laravel/5.6/migrations/1400#499c95最後執行資料庫遷移即可建出新的資料表
php artisan migrate
exam.show
)列出該測驗標題,及所有題目和選項解答,同時,又可以在該頁面放個表單以方便建立新題目。這樣該怎麼做?首先,先來放個表單。
Route::get('/exam/{exam}', 'ExamController@show')->name('exam.show');
也就是執行的控制器是ExamController@show
,所以,我們開啟/專案/app/Http/Controllers/ExamController.php
,找出裡面的show()
來看看其前端是送到哪裡去!我們好在該樣板產生一個題目用的表單。
public function show(Exam $exam)
{
return view('exam.show', ['exam' => $exam]);
}
由view()
可以得知,會套用/專案/resources/views/exam/show.blade.php
,所以,我們開啟它,然後在裡面加入表單的語法:
@can('建立測驗')
{{ bs()->openForm('post', '/topic') }}
{{ bs()->formGroup()
->label('題目內容', false, 'text-sm-right')
->control(bs()->textarea('topic')->placeholder('請輸入題目內容'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項1', false, 'text-sm-right')
->control(bs()->text('opt1')->placeholder('輸入選項1'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項2', false, 'text-sm-right')
->control(bs()->text('opt2')->placeholder('輸入選項2'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項3', false, 'text-sm-right')
->control(bs()->text('opt3')->placeholder('輸入選項3'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項4', false, 'text-sm-right')
->control(bs()->text('opt4')->placeholder('輸入選項4'))
->showAsRow() }}
{{ bs()->formGroup()
->label('正確解答', false, 'text-sm-right')
->control(bs()->select('ans',[1=>1, 2=>2, 3=>3, 4=>4])->placeholder('請設定正確解答'))
->showAsRow() }}
{{ bs()->hidden('exam_id', $exam->id) }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('儲存'))
->showAsRow() }}
{{ bs()->closeForm() }}
@endcan
一樣要用@can()
來判斷權限,免得其他人來胡亂新增題目
記得設定exam_id
隱藏欄位,並利用$exam->id
來取得目前測驗編號,如此,才知道此題目是屬於那一個測驗的。
Route::post('/topic', 'TopicController@store')->name('topic.store');
因為我們還沒有題目的控制器,所以,先在終端機中建立之:
php artisan make:controller TopicController --resource
如此,會產生 /專案/app/Http/Controllers/TopicController.php
,開啟之,找到 store()
函數,修改之:
public function store(Request $request)
{
$topic = Topic::create($request->all());
return redirect()->route('exam.show', $topic->exam_id);
}
我們希望儲存會回到原畫面,以便繼續新增題目,故route()
一樣指向到 exam.show
,並將測驗的編號$topic->exam_id
傳到路由中。詳情: https://laravel-china.org/docs/laravel/5.6/routing/1363#redirect-routes
另外,上方記得加上以下語法,如此Topic::create()
才能使用
use App\Topic;
由於是用批量賦值的寫法,所以記得到 /專案/app/Topic.php
設定哪些欄位可以使用 fillable。
修改 /專案/app/Topic.php
其內容為:
class Topic extends Model
{
protected $fillable = [
'topic', 'exam_id', 'opt1', 'opt2', 'opt3', 'opt4', 'ans',
];
}
exam.show
)列出該測驗標題及表單以方便建立新題目,執行的路由是:
Route::get('/exam/{exam}', 'ExamController@show')->name('exam.show');
也就是執行的控制器是ExamController@show
,所以,我們若打算把該測驗的既有題目也顯示在此頁面,我們就必須開啟/專案/app/Http/Controllers/ExamController.php
,找出裡面的show()
來取出目前已經有的所有題目列表。
public function show(Exam $exam)
{
$topics = Topic::where('exam_id', $exam->id)->get();
return view('exam.show', compact('exam', 'topics'));
}
use App\Topic;
/專案/resources/views/exam/show.blade.php
樣板當中加入題目列表:
<dl>
@forelse ($topics as $key => $topic)
<dt>
<h3>
@can('建立測驗')
({{$topic->ans}})
@endcan
{{ bs()->badge()->text($key+1) }}
{{$topic->topic}}
</h3>
</dt>
<dd>
{{ bs()->radioGroup("ans[$topic->id]", [
1=>"<span class='opt'>❶ $topic->opt1</span>",
2=>"<span class='opt'>❷ $topic->opt2</span>",
3=>"<span class='opt'>❸ $topic->opt3</span>",
4=>"<span class='opt'>❹ $topic->opt4</span>"
])->inline()->addRadioClass(['mx-3']) }}
</dd>
@empty
<div class="alert alert-danger">尚無任何題目</div>
@endforelse
</dl>
選項數的地方我們是利用unicode來做的,例如:❶
就可以顯示成 ❶ ,看起來比較清楚。
圈圈數字可以看這裡:http://xahlee.info/comp/unicode_circled_numbers.html,所有的符號可以看這裡:http://www.utf8-chartable.de/unicode-utf8-table.pl
修改 /public/css/app.css
,加入選項的顏色設定和大小的設定
.opt{
color: rgb(17, 112, 136);
font-size: 1.2em;
}
身份為教師時畫面如下:
若是未登入者,暫時會呈現這樣(不過實際上,未登入應該不出現任何題目,以免題目外洩,這稍後處理):
'2'
取代為'新數字'
inline()
拿掉,讓選項垂直排列。
<dl>
@forelse ($topics as $key => $topic)
<dt>
<h3>
@can('建立測驗')
({{$topic->ans}})
@endcan
{{ bs()->badge()->text($key+1) }}
{{$topic->topic}}
</h3>
</dt>
<dd>
{{ bs()->radioGroup("ans[$topic->id]", [
1=>"<span class='opt'>❶ $topic->opt1</span>",
2=>"<span class='opt'>❷ $topic->opt2</span>",
3=>"<span class='opt'>❸ $topic->opt3</span>",
4=>"<span class='opt'>❹ $topic->opt4</span>"
])->addRadioClass(['mx-3']) }}
</dd>
@empty
<div class="alert alert-danger">尚無任何題目</div>
@endforelse
</dl>
$topics
由於是多筆,所以$topics
是Collection
類別,可以當物件用,亦可當陣列用,甚至可以輸出json)
$topics=\App\Topic::all();
以主索引取出單筆資料(抓出來的$topic 由於只有一筆,所以$topic
是一個Model)
$topic=\App\Topic::find(12);
以主索引取出多筆資料
$topic=\App\Topic::find([2,7,12,35]);
增加搜尋條件
$topic=\App\Topic::where('欄位' , '條件' ,'值');
設定排序
$topic=\App\Topic::orderBy('欄位' '排序方式');
串連使用
$topic=\App\Topic::where('欄位' , '條件' ,'值')->orderBy('欄位' '排序方式')->get();
隨機抓幾筆
$topic=\App\Topic::random(數量);
輸出成 json
$topic=\App\Topic::all();
$topic->toJson();
where()
或orderBy()
之類的方法。/專案/app/Topic.php
,加入對測驗的 belongsTo
關聯:
class Topic extends Model
{
protected $fillable = [
'topic', 'exam_id', 'opt1', 'opt2', 'opt3', 'opt4', 'ans',
];
public function exam()
{
return $this->belongsTo('App\Exam');
}
}
App\Exam
也可以寫成Exam::class
, 自 PHP 5.5 起,關鍵詞 class 也可用於類名的解析。使用 類名::class
可以得到一個字串,包含了類 ClassName 的完全限定名稱,如:命名空間\類名
。exam()
之後會變成集合的變數名稱,例如$topic->exam
/app/Exam.php
,加入對題目的 hasMany
關聯:
class Exam extends Model
{
protected $fillable = [
'title', 'user_id', 'enable',
];
protected $casts = [
'enable' => 'boolean',
];
public function topics()
{
return $this->hasMany('App\Topic');
}
}
$exam
內容時,就會自動加入 $exam->topics
資料陣列,所以,可以利用「 $exam->topics['欄位名稱']
」或「 $exam->topics->欄位名稱
」的方式來取得題目的相關資料。/專案/app/Http/Controllers/ExamController.php
,修改 show()
的方法,基本上,就是回到原狀而已,寫法更簡單:
public function show(Exam $exam)
{
return view('exam.show', compact('exam'));
}
/專案/resources/views/exam/show.blade.php
,把其中的$topics
@forelse ($topics as $key => $topic)
改為$exam->topics
即可,如:
@forelse ($exam->topics as $key => $topic)
make:factory
並指定模型來快速建立模型工廠:
php artisan make:factory TopicFactory --model=Topic
/專案/database/factories
目錄中。
<?php
use Faker\Generator as Faker;
$factory->define(App\Topic::class, function (Faker $faker) {
return [
//
];
});
<?php
use Faker\Generator as Faker;
$factory->define(App\Topic::class, function (Faker $faker) {
$items = [1, 2, 3, 4];
shuffle($items);
$random_date = $faker->dateTimeBetween('-3 days', '+3 days');
$num1 = rand(1, 99);
$num2 = rand(1, 99);
return [
'topic' => $num1 . " + " . $num2,
'opt' . $items[0] => $num1 + $num2,
'opt' . $items[1] => $num1 . $num2,
'opt' . $items[2] => rand(1, 99),
'opt' . $items[3] => rand(1, 999),
'ans' => $items[0],
'created_at' => $random_date,
'updated_at' => $random_date,
];
});
shuffle()
是為了給選項隨機排序,並指定第一個選項$items[0]
為正確答案,避免答案都是固定的選項。\專案\vendor\fzaninotto\faker\src\Faker\Factory.php
<?php
namespace Faker;
class Factory
{
const DEFAULT_LOCALE = 'zh_TW';
exit
)
php artisan tinker
factory(App\Topic::class)->make()
factory(App\Topic::class,5)->make()
factory(App\Topic::class,5)->create(['exam_id' => 3])
php artisan make:seeder TopicsTableSeeder
\專案\database\seeds\TopicsTableSeeder.php
,我們加入第15行的部份,也就是把剛剛在tinker的指令拿到這裡來。
<?php
use Illuminate\Database\Seeder;
class TopicsTableSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
// 每次建立 20 個題目
factory(\App\Topic::class, 20)->create(['exam_id' => 3]);
}
}
php artisan db:seed --class=TopicsTableSeeder
composer dump-autoload
autoload檔案在:\專案\vendor\composer\autoload_classmap.php
詳情可參考:https://laravel-china.org/docs/laravel/5.6/seeding/1401
修改原來的 /專案/resources/views/exam/show.blade.php
樣板,加入編輯按鈕:
<h1 class="text-center">
{{$exam->title}}
@can('建立測驗')
<a href="{{route('exam.edit', $exam->id)}}" class="btn btn-warning">編輯</a>
@endcan
</h1>
@can()
就可以判斷不同身份。 /專案/routes/web.php
加入 edit
的路由,{exam}
放在前後其實都沒關係。
Route::get('/exam/{exam}/edit', 'ExamController@edit')->name('exam.edit');
修改的界面主要就是讀出原始內容,然後塞到原來的建立標單裡,也就是套用到exam/create.blade.php
的表單中。這裡我們不另外做edit.blade.php用來編輯的視圖,因為編輯和建立的視圖其實90%以上都一樣,弄個兩套維護起來比較不易(容易改了A卻忘了B),所以,我們來看一下用同一個視圖,需要注意哪些事?
建立的http動詞是post
,修改則是patch
(如此,會自動加上CSRF保護,避免跨站攻擊)
建立的表單action
是/exam
,修改則是/exam/編號
建立時,啟用欄位(enable)預設值為1,修改時,則是視實際情況
所以,根據以上,我們在控制器中 /專案/app/Http/Controllers/ExamController.php
,先加入 edit
的方法:
public function edit(Exam $exam)
{
return view('exam.create', compact('exam'));
}
$exam
是一筆完整Exam資料,$exam->id
就是該資料的編號
最後,修改 /專案/resources/views/exam/create.blade.php
樣板:
@extends('layouts.app')
@section('content')
<h1>{{ __('Create Exam') }}</h1>
@can('建立測驗')
@if(isset($exam))
{{ bs()->openForm('patch', "/exam/{$exam->id}" , [ 'model' => $exam]) }}
@else
{{ bs()->openForm('post', '/exam') }}
@endif
{{ bs()->formGroup()
->label('測驗標題', false, 'text-sm-right')
->control(bs()->text('title')->placeholder('請填入測驗標題'))
->showAsRow() }}
{{ bs()->formGroup()
->label('測驗狀態', false, 'text-sm-right')
->control(bs()->radioGroup('enable', [1 => '啟用', 0 => '關閉'])
->selectedOption(isset($exam)?$exam->enable:1)
->inline())
->showAsRow() }}
{{ bs()->hidden('user_id', Auth::id()) }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('建立測驗'))
->showAsRow() }}
{{ bs()->closeForm() }}
@if (count($errors) > 0)
@component('bs::alert', ['type' => 'danger'])
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
@endcomponent
@endif
@else
@component('bs::alert', ['type' => 'danger'])
@slot('heading')
無建立測驗的權限
@endslot
@endcomponent
@endcan
@endsection
patch
。action
路徑也不同,加入測驗編號$exam->id
。此外,多了['model' => $exam]
,也就是此表單要套用預設值。/專案/app/Http/Controllers/ExamController.php
,編輯 index
的方法:
public function index()
{
$user = Auth::user();
if ($user and $user->can('建立測驗')) {
$exams = Exam::orderBy('created_at', 'desc')
->paginate(3);
} else {
$exams = Exam::where('enable', 1)
->orderBy('created_at', 'desc')
->paginate(2);
}
return view('exam.index', compact('exams'));
}
主要是先取得使用者資料,此時,上方需告知要使用Illuminate\Support\Facades\Auth
才行
use Illuminate\Support\Facades\Auth;
接著判斷有無$user物件(沒登入者不會有),若有,判斷有無權限。
有權限者就不加上where('enable', 1)
這個條件(顯示數也可以調多一點)
接著修改\專案\resources\views\exam\index.blade.php
,加入判斷,若$exam->enable
不等於1,就顯示一個關閉的徽章
@extends('layouts.app')
@section('content')
<h1>測驗一覽<small>(共 {{$exams->total()}} 筆資料)</small></h1>
<ul class="list-group">
@forelse ($exams as $exam)
<li class="list-group-item">
@if($exam->enable!=1)
{{ bs()->badge()->text('關閉') }}
@endif
{{$exam->created_at->format("Y年m月d日") }} -
<a href="exam/{{$exam->id}}">
{{$exam->title}}
</a>
</li>
@empty
<li class="list-group-item">尚無任何測驗</li>
@endforelse
</ul>
<div class="my-3">
{{ $exams->links() }}
</div>
@endsection
看起來就像這樣:
修改路由 /專案/routes/web.php
加入 update
的對應
Route::patch('/exam/{exam}', 'ExamController@update')->name('exam.update');
更新的方法用 patch
,路徑直接給 {exam}
(此時是編號)即可。
接著在控制器中 /專案/app/Http/Controllers/ExamController.php
,加入用Model的update
方法(一樣要fillable有設定才能用):
public function update(ExamRequest $request, Exam $exam)
{
$exam->update($request->all());
return redirect()->route('exam.show', $exam->id);
}
/專案/routes/web.php
加入topic 的 edit
路由,一樣使用路由模型綁定,{topic}
就是編號,放在前後其實都沒關係。
Route::pattern('exam', '[0-9]+');
Route::pattern('topic', '[0-9]+');
//略
Route::get('/topic/{topic}/edit', 'TopicController@edit')->name('topic.edit');
修改原來的 /專案/resources/views/exam/show.blade.php
樣板,加入編輯按鈕:
<h3>
@can('建立測驗')
<a href="{{route('topic.edit', $topic->id)}}" class="btn btn-warning">編輯</a>
({{$topic->ans}})
@endcan
{{ bs()->badge()->text($key+1) }}
{{ $topic->topic }}
</h3>
要修改題目,會遇到和修改測驗一樣的問題(而且更複雜一點),因為是共用樣板,所以,必須針對「建立」和「修改」兩種情形來修改部份視圖的設定
建立的http動詞是post
,修改則是patch
(如此,會自動加上CSRF保護,避免跨站攻擊)
建立題目的表單action
是/topic
,修改題目則是/topic/編號
在控制器中 /專案/app/Http/Controllers/TopicController.php
,加入 edit
的方法:
public function edit(Topic $topic)
{
$exam = $topic->exam;
return view('exam.show', compact('exam', 'topic'));
}
主要就是讀出題目$topic
的原始內容,以便套用到表單中。我們將Topic注入$topic
,$topic
就成了一個Topic內容物件
此外,測驗內容$exam
也會被用到,用來顯示測驗標題,而且因為我們已經有設好關聯,所以,不用另外抓,$topic->exam
就是所屬測驗的完整資料。
最後,修改 /專案/resources/views/exam/show.blade.php
樣板:
@can('建立測驗')
@if(isset($topic))
{{ bs()->openForm('patch', "/topic/{$topic->id}", ['model' => $topic]) }}
@else
{{ bs()->openForm('post', '/topic') }}
@endif
{{ bs()->formGroup()
->label('題目內容', false, 'text-sm-right')
->control(bs()->textarea('topic')->placeholder('請輸入題目內容'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項1', false, 'text-sm-right')
->control(bs()->text('opt1')->placeholder('輸入選項1'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項2', false, 'text-sm-right')
->control(bs()->text('opt2')->placeholder('輸入選項2'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項3', false, 'text-sm-right')
->control(bs()->text('opt3')->placeholder('輸入選項3'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項4', false, 'text-sm-right')
->control(bs()->text('opt4')->placeholder('輸入選項4'))
->showAsRow() }}
{{ bs()->formGroup()
->label('正確解答', false, 'text-sm-right')
->control(bs()->select('ans',[1=>1, 2=>2, 3=>3, 4=>4])->placeholder('請設定正確解答'))
->showAsRow() }}
{{ bs()->hidden('exam_id', $exam->id) }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('儲存'))
->showAsRow() }}
{{ bs()->closeForm() }}
@endcan
修改路由 /專案/routes/web.php
加入topic
的 update
的路由
Route::patch('/topic/{topic}', 'TopicController@update')->name('topic.update');
更新的方法用 patch
,路徑直接給 {topic}
編號即可。
接著在控制器中 /專案/app/Http/Controllers/TopicController.php
,加入用Model的update
方法(一樣要fillable有設定才能用):
public function update(Request $request, Topic $topic)
{
$topic->update($request->all());
return redirect()->route('exam.show', $topic->exam_id);
}
當 route 刪除使用 delete
方法時,無法直接用連結的方式來做,因為連結的方法屬於get
。所以我們自行產生表單,並送出_method
值為DELETE
的參數,可以利用 Laravel 5.6 新的@method('delete')
來達成,以及用@csrf
(取代原先的csrf_field()
)產生令牌,以加入CSRF保護,避免跨站攻擊。詳情:https://d.laravel-china.org/docs/5.6/csrf
修改 /專案/resources/views/show.blade.php
樣板,加入刪除按鈕:
@can('建立測驗')
<form action="{{route('topic.destroy', $topic->id)}}" method="post" style="display:inline">
@csrf
@method('delete')
<button type="submit" class="btn btn-danger">刪除</button>
</form>
<a href="{{route('topic.edit', $topic->id)}}" class="btn btn-warning">編輯</a>
({{$topic->ans}})
@endcan
style="display:inline"
只是為了讓刪除按鈕可以和其他按鈕放在一起。method="post"
也不可拿掉編輯路由 /專案/routes/web.php
加入 topic
的刪除路由
Route::delete('/topic/{topic}', 'TopicController@destroy')->name('topic.destroy');
/專案/app/Http/Controllers/TopicController.php
,加入 delete
的方法:
public function destroy(Topic $topic)
{
$topic->delete();
return redirect()->route('exam.show', $topic->exam_id);
}
destroy()
也可以,但此例我們需要抓出exam_id
的值,故用destroy()
也沒比較省事,因此底下參考一下即可。
public function destroy($id)
{
Topic::destroy($id);
return redirect()->route('exam.show', $id);
}
destroy()
用法(詳細請參考:)https://laravel-china.org/docs/laravel/5.6/eloquent/1403#bb7a2e:
//刪除一筆
Topic::destroy(1);
//刪除多筆
Topic::destroy([1,3,5,7]);
Topic::destroy(1,3,5,7);
編輯路由 /專案/routes/web.php
加入 exam 的刪除路由
Route::delete('/exam/{exam}', 'ExamController@destroy')->name('exam.destroy');
修改 /專案/resources/views/show.blade.php
樣板,加入測驗的刪除按鈕:
<form action="{{route('exam.destroy', $exam->id)}}" method="POST" style="display:inline">
@csrf
@method('delete')
<button type="submit" class="btn btn-danger">刪除</button>
</form>
/專案/app/Http/Controllers/ExamController.php
,加入 delete 的方法,我們一樣用delete()
刪除即可,也不需要在抓取任何參數,所以,直接刪完回首頁即可。
public function destroy(Exam $exam)
{
$exam->delete();
return redirect()->route('exam.index');
}
$table->foreign('exam_id')->references('id')->on('exams')->onDelete('cascade');
,所以在刪除測驗,也會順便把相關聯的題目都一併刪除。\專案\resources\views\layouts\app.blade.php
主視圖,引入js以及子視圖,並新增一個 @yield
命令,已方便各個子視圖放置一些各自的 javascript 語法:
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Scripts -->
<script src="{{ asset('js/app.js') }}" defer></script>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
<script src="https://unpkg.com/sweetalert2@7.18.0/dist/sweetalert2.all.js"></script>
<!-- 中間略 -->
@yield('js')
</body>
</html>
\專案\resources\views\layouts\show.blade.php
子視圖,先把刪除題目的按鈕簡化為一般的按鈕,在class中,我們加入btn-del-topic
好讓jquery可以偵測刪除題目的按鈕是否有被按下,並利用data-id
來紀錄題目編號,等一下刪除時會用到。
<h3>
@can('建立測驗')
<button type="button" class="btn btn-danger btn-del-topic" data-id="{{ $topic->id }}">刪除</button>
<a href="{{route('topic.edit', $topic->id)}}" class="btn btn-warning">編輯</a>
({{$topic->ans}})
@endcan
{{ bs()->badge()->text($key+1) }}
{{ $topic->topic }}
</h3>
@section('js')
加入 sweetalert2 語法,語法可參考https://sweetalert2.github.io/:
@section('js')
<script>
$(document).ready(function(){
$('.btn-del-topic').click(function(){
var topic_id=$(this).data('id');
swal({
title: "確定要刪除題目嗎?",
text: "刪除後該題目就消失救不回來囉!",
type: 'warning',
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "是!含淚刪除!",
cancelButtonText: "不...別刪",
}).then((result) => {
if (result.value) {
axios.delete('/topic/' + topic_id)
.then(function(){
return swal("OK!刪掉題目惹!", "該題目已經隨風而逝了...", "success");
}).then(function () {
location.reload();
});
}
})
});
});
</script>
@endsection
$('.btn-del-topic').click()
來偵測該刪除按鈕被按下的事件。data()
函數取的data-id
的值,這樣才知道要刪除哪一篇location.reload();
以更新畫面\專案\app\Http\Controllers\TopicController.php
,把刪除的部份再簡化(無須轉向),到此,題目的確認後刪除就大公告成了!
public function destroy(Topic $topic)
{
$topic->delete();
}
\專案\resources\views\layouts\show.blade.php
子視圖,先把測驗的刪除按鈕簡化為一般的按鈕,在class中,我們加入btn-del-exam
好讓jquery可以偵測測驗的刪除按鈕是否有被按下,並利用data-id
來紀錄測驗編號。
<h1 class="text-center">
{{$exam->title}}
@can('建立測驗')
<button type="button" class="btn btn-danger btn-del-exam" data-id="{{ $exam->id }}">刪除</button>
<a href="{{route('exam.edit', $exam->id)}}" class="btn btn-warning">編輯</a>
@endcan
</h1>
@section('js')
裡面再加上測驗刪除的事件,複製上方得來修改即可。
// 刪除按鈕點擊事件
$('.btn-del-exam').click(function() {
// 獲取按鈕上 data-id 屬性的值,也就是編號
var id = $(this).data('id');
// 調用 sweetalert
swal({
title: "確定要刪除測驗嗎?",
text: "刪除後該測驗連同所有題目就消失救不回來囉!",
type: 'warning',
showCancelButton: true,
confirmButtonColor: "#DD6B55",
confirmButtonText: "是!含淚刪除!",
cancelButtonText: "不...別刪",
}).then((result) => {
if (result.value) {
swal("OK!刪掉測驗惹!", "該測驗所有資料已經隨風而逝了...", "success");
// 調用刪除介面,用 id 來拼接出請求的 url
axios.delete('/exam/' + id).then(function () {
location.href='/exam';
});
}
});
});
$('.btn-del-exam').click()
來偵測測驗刪除按鈕被按下的事件。data()
函數取的data-id
的值,這樣才知道要刪除哪一篇測驗location.href
做轉向。\專案\app\Http\Controllers\ExamController.php
,一樣把刪除的部份再簡化即可!
public function destroy(Exam $exam)
{
$exam->delete();
}
學生進行考試時,也要有一個表來紀錄學生填答及分數。
建立Test(測驗)的 Eloquent 模型,以便將一個資料表變成一個物件來操作,並且順便產生 migration 檔案
php artisan make:model Test --migration
編輯/專案/database/migrations/日期_create_tests_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTestsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('tests', function (Blueprint $table) {
$table->increments('id');
$table->text('content');
$table->unsignedInteger('exam_id');
$table->foreign('exam_id')->references('id')->on('exams');
$table->unsignedInteger('user_id');
$table->foreign('user_id')->references('id')->on('users');
$table->unsignedTinyInteger('score');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('tests');
}
}
content
:紀錄此測驗的題目編號及答案exam_id
:對應的測驗編號user_id
:學生編號score
:得分最後執行資料庫同步即可建出新的資料表
php artisan migrate
/專案/app/Test.php
,加入對測驗的 belongsTo
關聯:
class Test extends Model
{
protected $fillable = [
'content', 'user_id', 'exam_id', 'score',
];
public function exam()
{
return $this->belongsTo('App\Exam');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
$test
資料,就可以順便帶出$test->exam
測驗資料,以及$test->user
使用者資料$fillable
也順便一下,這些通常都是必做的。/專案/app/Exam.php
,加入對題目的 hasMany
關聯:
class Exam extends Model
{
protected $fillable = [
'title', 'user_id', 'enable',
];
protected $casts = [
'enable' => 'boolean',
];
public function topics()
{
return $this->hasMany('App\Topic');
}
public function tests()
{
return $this->hasMany('App\Test');
}
}
$exam
內容時,就會自動加入 $exam->test
資料陣列,可以輕鬆的取得測試的相關資料。如果有老師想看這個測驗底下有多少考試,就可以很方便的讀出。/專案/app/Http/Controllers/ExamController.php
控制器,修改原本的show()
public function show(Exam $exam)
{
$user = Auth::user();
if ($user and $user->can('進行測驗')) {
$exam->topics = $exam->topics->random(10);
}
return view('exam.show', compact('exam'));
}
$exam->topics
會是一個集合($exam
此時是Exam
的資料物件),我們利用random(10)
來從題目集合中,隨機取10筆來呈現。/專案/resources/views/exam/show.blade.php
樣板,一樣依據權限來呈現不同畫面,不過因為程式碼越來越長,所以,我們可以把一些表單獨立成另外的視圖檔案:
\專案\resources\views\exam\form.blade.php
@if(isset($topic))
{{ bs()->openForm('patch', "/topic/{$topic->id}", ['model' => $topic]) }}
@else
{{ bs()->openForm('post', '/topic') }}
@endif
{{ bs()->formGroup()
->label('題目內容', false, 'text-sm-right')
->control(bs()->textarea('topic')->placeholder('請輸入題目內容'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項1', false, 'text-sm-right')
->control(bs()->text('opt1')->placeholder('輸入選項1'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項2', false, 'text-sm-right')
->control(bs()->text('opt2')->placeholder('輸入選項2'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項3', false, 'text-sm-right')
->control(bs()->text('opt3')->placeholder('輸入選項3'))
->showAsRow() }}
{{ bs()->formGroup()
->label('選項4', false, 'text-sm-right')
->control(bs()->text('opt4')->placeholder('輸入選項4'))
->showAsRow() }}
{{ bs()->formGroup()
->label('正確解答', false, 'text-sm-right')
->control(bs()->select('ans',[1=>1, 2=>2, 3=>3, 4=>4])->placeholder('請設定正確解答'))
->showAsRow() }}
{{ bs()->hidden('exam_id', $exam->id) }}
{{ bs()->formGroup()
->label('')
->control(bs()->submit('儲存'))
->showAsRow() }}
{{ bs()->closeForm() }}
/專案/resources/views/exam/show.blade.php
樣板,利用@include()
引入該檔案,位置放在exam\form.blade.php
,引入時需寫成exam.form
@can('建立測驗')
@include('exam.form')
@endcan
同樣的,我們把題目的呈現也獨立成一個視圖檔案\專案\resources\views\exam\topic.blade.php
<dl>
@forelse ($exam->topics as $key => $topic)
<dt>
<h3>
@can('建立測驗')
<button type="button" class="btn btn-danger btn-del-topic" data-id="{{ $topic->id }}">刪除</button>
<a href="{{route('topic.edit', $topic->id)}}" class="btn btn-warning">編輯</a>
({{$topic->ans}})
@endcan
{{ bs()->badge()->text($key+1) }}
{{$topic->topic}}
</h3>
</dt>
<dd>
{{ bs()->radioGroup("ans[$topic->id]", [
1=>"<span class='opt'>❶ $topic->opt1</span>",
2=>"<span class='opt'>❷ $topic->opt2</span>",
3=>"<span class='opt'>❸ $topic->opt3</span>",
4=>"<span class='opt'>❹ $topic->opt4</span>"
])->addRadioClass(['mx-3']) }}
</dd>
@empty
<div class="alert alert-danger">尚無任何題目</div>
@endforelse
</dl>
再修改/專案/resources/views/exam/show.blade.php
樣板,一樣利用@include()
引入該檔案,位置放在exam\topic.blade.php
,引入時需寫成exam.topic
@include('exam.topic')
@if(Auth::check('建立測驗') || Auth::check('進行測驗'))
@can('進行測驗')
{{ bs()->openForm('post', '/test') }}
@include('exam.topic')
{{ bs()->hidden('user_id', Auth::id()) }}
{{ bs()->hidden('exam_id', $exam->id) }}
<div class="text-center my-5">
{{ bs()->submit('寫完送出') }}
</div>
{{ bs()->closeForm() }}
@else
@include('exam.topic')
@endcan
@else
@component('bs::alert', ['type' => 'info'])
共 {{ $exam->topics->count() }} 題
@endcomponent
@endif
@can
無法使用,故改用Auth::check('建立測驗')
搭配@if
就可以達成。Auth::id()
來抓取即可。$exam->topics
本身是一個集合,要算數量可以用count()
class="alert"
來做,也可以用@component
的方式來做。如果只是簡單的訊息,其實用單純HTML語法來做更簡單。\專案\routes\web.php
,儲存部份用test的控制器TestController
來處理:
Route::post('/test', 'TestController@store')->name('test.store');
php artisan make:controller TestController --resource
\專案\app\Http\Controllers\TestController.php
,修改 store
方法,儲存後轉向到考試結果頁面。
public function store(Request $request)
{
$content = collect($request->ans)->toJson();
$score = 0;
foreach ($request->ans as $topic_id => $ans) {
$topic = Topic::find($topic_id);
$score += ($topic->ans == $ans) ? 20 : 0;
}
$test = Test::create([
'content' => $content,
'user_id' => $request->user_id,
'exam_id' => $request->exam_id,
'score' => $score,
]);
return redirect()->route('test.show', $test->id);
}
$request
就是使用者輸入的內容,以物件方式存在。詳情可參考:https://laravel-china.org/docs/laravel/5.6/requests/1297#7ecd03$content
將會以json格式來儲存題號以及使用者填寫的答案,而$request->ans
是所有填答的陣列,可以用json_encode($request->ans)
這個PHP內建函數來將之轉成json格式,也可以利用Laravel的collect()
來將陣列轉為集合,以便用集合的toJson方法。\專案\app\Test.php
有沒有進行fillable設定redirect()
用來轉向到考試結果頁面\App\Test
及App\Topic
模型
use App\Test;
use App\Topic;
test.show
的路由,避免錯誤。
Route::pattern('exam', '[0-9]+');
Route::pattern('topic', '[0-9]+');
Route::pattern('test', '[0-9]+');
//略
Route::get('/test/{test}', 'TestController@show')->name('test.show');
test.show
是用控制器TestController@show
來產生,故開啟/專案/app/Http/Controllers/TestController.php
,修改 show
的方法
public function show(Test $test)
{
$topics = json_decode($test->content, true);
$content = [];
$i = 1;
foreach ($topics as $topic_id => $ans) {
$content[$i]['topic'] = Topic::find($topic_id);
$content[$i]['ans'] = $ans;
$i++;
}
return view('exam.test', compact('test', 'content'));
}
首先,此處一樣用路由模型綁定,$test
即為Test的資料物件,由於有設定關聯,所以,會一併抓出$test->exam
及$test->user
的資料。
由於我們用的是比較簡易的作法,把考試者的填答做成json格式存入content中,故讀出時,可以利用json_decode($id->content, true)
將json資料轉回陣列。
接著把陣列一個一個讀出,其陣列索引為題目編號$topic_id
,值為使用者填寫的答案,因此每跑一圈,我們就可以利用Topic::find($topic_id)
來抓取該題目的資訊,並將題目資訊存到$content
陣列中。
$content
的索引一樣從0開始,以方便等一下製作題號。
此外,我們也順便把使用者的作答一併存入$content
陣列中,以便待會比對是否作答正確。
exam.test
樣板,因此,我們建立一個/專案/resources/views/exam/test.blade.php
樣板:
@extends('layouts.app')
@section('content')
<h1 class="text-center">{{$test->exam->title}}</h1>
<h3 class="row">
<div class="col-sm-6">時間:<u>{{$test->created_at->format("Y年m月d日 H:i:s")}}</u></div>
<div class="col-sm-6 text-right">姓名:<u>{{$test->user->name}}</u> 得分:<u>{{$test->score}}</u></div>
</h3>
<hr>
<dl>
@forelse ($content as $key => $data)
<dt>
<h3>
@if($data['ans']==$data['topic']->ans)
<img src="{{asset('yes.png')}}" alt="yes" title="正確答案為 {{$data['topic']->ans}}">
@else
<img src="{{asset('no.png')}}" alt="no" title="正確答案為 {{$data['topic']->ans}}">
@endif
{{ bs()->badge()->text($key+1) }}
({{$data['ans']}})
{{$data['topic']->topic}}
</h3>
</dt>
<dd>
<div class="ml-5 my-2 opt">
❶ {{$data['topic']->opt1}}
</div>
<div class="ml-5 my-2 opt">
❷ {{$data['topic']->opt2}}
</div>
<div class="ml-5 my-2 opt">
❸ {{$data['topic']->opt3}}
</div>
<div class="ml-5 my-2 opt">
❹ {{$data['topic']->opt4}}
</div>
</dd>
@empty
<div class="alert alert-danger">尚無任何題目</div>
@endforelse
</dl>
@endsection
其中會用到以下兩個圖檔,故請下載之,並存到public底下:
要使用public下的資源,可以直接用asset('檔案名稱')
來取用之即可。
composer require t301000/laravel-ntpc-openid
'providers' => [
...
T301000\LaravelNtpcOpenid\NtpcOpenidServiceProvider::class,
];
php artisan vendor:publish --provider="T301000\LaravelNtpcOpenid\NtpcOpenidServiceProvider" --tag=config
return [
....略
'required' => [
'namePerson/friendly', //暱稱
'contact/email', //公務信箱
'namePerson', //姓名
'birthDate', //出生年月日
'person/gender', //性別
'contact/postalCode/home', //識別碼
'contact/country/home', //單位(學校名),如:xx國中
'pref/language', //年級班級座號 6 碼
'pref/timezone' // 授權資訊[學校別、身分別、職稱別、職務別]
],
....略
];
['unitCode' => '014569'],
['unitCode' => '014569', 'role' => '教師'],
['unitCode' => '014569', 'role' => ['教師', '學生']],
['role' => '教師'],
['unitCode' => '014569', 'title' => ['主任', '組長']],
['group' => '資訊組長'],
['openID' => ['somebody']],
unitCode
單位代碼, role
身份, title
職務, group
職稱, openID
OpedID 帳號unitCode
為字串之外,其餘可為字串或陣列\專案\resources\views\layouts\nav.blade.php
,加入登入選項:
<li class="nav-item">
<form action="/auth/login/openid" method="post" style="display:inline">
@csrf
<button class="btn btn-link">OpenID 登入</button>
</form>
</li>
php artisan make:controller OpenIDController
\專案\app\Http\Controllers\OpenIDController.php
:
<?php
namespace App\Http\Controllers;
use Backpack\Base\app\Models\BackpackUser;
use Illuminate\Support\Facades\Auth;
class OpenIDController extends Controller
{
public function ntpcopenid()
{
$openid = app('ntpcopenid');
return redirect($openid->authUrl());
}
public function get_ntpcopenid()
{
$openid = app('ntpcopenid');
switch ($openid->mode) {
case 'cancel': // 取消授權
return 'User Canceled';
break;
case 'id_res': // 同意授權
if (!$openid->validate()) {
// 驗證未過
// 導向至登入畫面
return redirect('auth/login');
}
// 驗證通過,檢查是否允許登入
if ($openid->canLogin()) {
// 允許登入
// 取得 user data 陣列
$data = $openid->getUserData('*');
//搜尋使用者是否存在,沒有就新增
$user = BackpackUser::firstOrNew(['name' => $data['namePerson']]);
$user->email = $data['contact/email'];
$user->password = password_hash($user->email, PASSWORD_DEFAULT);
// 註冊使⽤用者
if (!$user->exists) {
$user->save();
if ($data['pref/timezone'][0]['role']) {
$user->assignRole('學生');
}
}
// 登⼊入使⽤用者
Auth::login($user);
// 將取得的資料,轉成陣列存入session中
session($data);
}
// 不允許登入,例如導回登入頁面或顯示訊息
return redirect('/');
break;
default: // 其他,如直接輸入網址瀏覽
return redirect('/');
break;
}
}
}
\專案\routes\web.php
:
// 處理表單,導向至 NTPC OpenID 登入
Route::post('auth/login/openid', 'OpenIDController@ntpcopenid')->name('ntpcopenid');
// OpenID 導回
Route::get('auth/login/openid', 'OpenIDController@get_ntpcopenid')->name('get_ntpcopenid');
php artisan make:migration add_tests_cols --table=tests
\專案\database\migrations\2018_08_10_001435_add_tests_cols.php
:
public function up()
{
Schema::table('tests', function (Blueprint $table) {
$table->unsignedTinyInteger('grade');
$table->unsignedTinyInteger('class');
$table->unsignedTinyInteger('num');
});
}
以及
public function down()
{
Schema::table('tests', function (Blueprint $table) {
$table->dropColumn(['grade', 'class', 'num']);
});
}
\專案\app\Http\Controllers\TestController.php
public function store(Request $request)
{
$content = collect($request->ans)->toJson();
$score = 0;
foreach ($request->ans as $topic_id => $ans) {
$topic = Topic::find($topic_id);
$score += ($topic->ans == $ans) ? 20 : 0;
}
$class_info = session('pref/language');
$test = Test::create([
'content' => $content,
'user_id' => $request->user_id,
'exam_id' => $request->exam_id,
'score' => $score,
'grade' => substr($class_info, 0, 2),
'class' => substr($class_info, 2, 2),
'num' => substr($class_info, 4, 2),
]);
return redirect()->route('test.show', $test->id);
}
\專案\app\Test.php
,主要是修改$fillable新增加入的欄位
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Test extends Model
{
protected $fillable = [
'content', 'user_id', 'exam_id', 'score','grade','class','num',
];
public function exam()
{
return $this->belongsTo('App\Exam');
}
public function user()
{
return $this->belongsTo('App\User');
}
}
php artisan down
此時,系統會傳送503訊息,網站會顯示503頁面,您也可以自行修改\專案\resources\views\errors\503.blade.php
頁面內容,使之和網站風格有一致性。
@extends('layouts.app')
@section('content')
@component('bs::jumbotron', ['fluid' => true])
@slot('heading')
503 網站目前進廠維護中
@endslot
@slot('subheading')
沒什麼大事啦~例行維護嘛...您懂的...
@endslot
<hr class="my-3">
<p>不就是改改程式,抓抓臭蟲...應該不用兩三天就好了啦!</p>
@endcomponent
@endsection
看起來像這樣:
php artisan up
Route::prefix()
將之群組起來,例如:
Route::get('/exam/', 'ExamController@index')->name('exam.index');
Route::get('/exam/create', 'ExamController@create')->name('exam.create');
exam
,因此,可利用prefix('exam')
設定成:
Route::prefix('exam')->group(function () {
Route::get('/', 'ExamController@index')->name('exam.index');
Route::get('/create', 'ExamController@create')->name('exam.create');
});
Route::prefix('exam')->group(function () {
Route::get('/', 'ExamController@index')->name('exam.index');
Route::get('/create', 'ExamController@create')->name('exam.create');
Route::post('', 'ExamController@store')->name('exam.store');
Route::get('/{exam}', 'ExamController@show')->name('exam.show');
Route::delete('/{exam}', 'ExamController@destroy')->name('exam.destroy');
Route::get('/{exam}/edit', 'ExamController@edit')->name('exam.edit');
Route::patch('/{exam}', 'ExamController@update')->name('exam.update');
});
Route::prefix('topic')->group(function () {
Route::post('/', 'TopicController@store')->name('topic.store');
Route::get('/{topic}/edit', 'TopicController@edit')->name('topic.edit');
Route::patch('/{topic}', 'TopicController@update')->name('topic.update');
Route::delete('/{topic}', 'TopicController@destroy')->name('topic.destroy');
});
Route::prefix('test')->group(function () {
Route::post('/', 'TestController@store')->name('test.store');
Route::get('/{test}', 'TestController@show')->name('test.show');
});
.
即可。Route::name()
將之群組起來,例如:
Route::get('/exam/', 'ExamController@index')->name('exam.index');
Route::get('/exam/create', 'ExamController@create')->name('exam.create');
exam
,因此,可利用prefix('exam')
設定成:
Route::name('exam.')->group(function () {
Route::get('/exam', 'ExamController@index')->name('index');
Route::get('/exam/create', 'ExamController@create')->name('create');
});
Route::group([
'prefix' => 'exam',
'as' => 'exam.',
], function () {
Route::get('/', 'ExamController@index')->name('index');
Route::get('/create', 'ExamController@create')->name('create');
Route::post('', 'ExamController@store')->name('store');
Route::get('/{exam}', 'ExamController@show')->name('show');
Route::delete('/{exam}', 'ExamController@destroy')->name('destroy');
Route::get('/{exam}/edit', 'ExamController@edit')->name('edit');
Route::patch('/{exam}', 'ExamController@update')->name('update');
});
Route::group([
'prefix' => 'topic',
'as' => 'topic.',
], function () {
Route::post('/', 'TopicController@store')->name('store');
Route::get('/{topic}/edit', 'TopicController@edit')->name('edit');
Route::patch('/{topic}', 'TopicController@update')->name('update');
Route::delete('/{topic}', 'TopicController@destroy')->name('destroy');
});
Route::group([
'prefix' => 'test',
'as' => 'test.',
], function () {
Route::post('/', 'TestController@store')->name('store');
Route::get('/{test}', 'TestController@show')->name('show');
});
Route::resource('exam', 'ExamController');
Route::resource('topic', 'TopicController');
Route::resource('test', 'TestController');
.gitignore
檔來看,裡面有一些目錄及檔案並不會放到GitHub上,包括:
git clone https://github.com/您的帳號/exam56 exam56
cd exam56
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate
Homestead.yaml
設定檔:
php vendor/bin/homestead make
vagrant up
vagrant ssh
cd public_html/exam56
php artisan migrate