--- title: 工程(5) Laravel 使用手冊 tags: work - 教育訓練課程進修 --- laravel概論 === * 框架 (frameworks) => 把各種功能分類 使其比較好維護 * php的框架有 laravel codeIgniter Symfony CakePHP ZEND Framework ....很多 * 框架的好處 1. 良好的架構 2. 安全性 3. 統一 4. 實用的工具 * 安裝條件 PHP >= 7.1.3 環境設定 === 1. mac - 複製指令以後打開terminal貼上即可 * install Homebrew * install PHP 7 * install composer * Laravel 2. windows * PHP 1. PHP下載 解壓縮到C:\PHP7 複製C:\PHP7\php.ini-development到C:\PHP7\php.ini 使用編譯器打開C:\PHP7\php.ini 找 ;extension_dir = ext" 把分號(;)拿掉 找到很多dll的地方把需要的取消註解 2. 設定windows 10 系統的路徑 進階系統設定>path 加上 C:\PHP 設定完才可下指令 3. 打開cmd 輸入```php -v```確認是否安裝完成 * install composer * Laravel MVC架構解析 === * 資料夾結構 mail可以做一個使用laravel發email的設定 database裡面都是資料庫相關的內容 * 使用者進入網站都會通過public/index.php 這個檔案目的是使框架可以正常執行 這個檔案稱為bootstrap * 第三方程式庫都會放在vendor裡面 (不要動) * npm是前端的管理系統 .env === * laravel的基礎設定 用來統整設定值 * 裡面的```env(內容)``` 就是讓程式去env裡面找這些參數的值 維護安全性 * env不進git * ```env('參數1' , '參數2')``` * 參數1是.env裡面的NAME 例如 APP_NAME就填```env('name','參數2'),``` * 參數2如果沒有設定 就會以參數2寫的值為預設值 Routing === * 大多數的設定方式為 1. ```Route::get('/user/{id}','UserController@list'); //Read``` 2. ```Route::post('/user/{id}','UserController@create'); //Create``` 3. ```Route::put('/user/{id}','UserController@update'); //Update``` 4. ```Route::delete('/user/{id}','UserController@delete'); //Delete``` * 使用不同傳送方式(post get put delete)達到不同的效果稱之為RESTful laravel的錯誤訊息 === * 顯示最上面的都是最下層的錯誤 所以如果最下層的錯誤沒有找到解決方法的話 就可以一層一層往上層去找方法解決 * all frames就是相關的全部列出來 另一個是最有可能的錯誤 Routing實作 === 1. 進到 route/web.php 把一開始的welcome改成index 就可以把它指向resources/views/index.blade.php 2. 如果要指向resources/views/posts/list.blade.php的話 要改成 ```php= Route::get('/posts' , function(){ return view('posts.list'); }); ``` 3. 如果要設置後面有帶ID的話 要改成 ```php= Route::get('/posts/{id}' , function($id){ return view('posts.show'); }); ``` 4. 如果還沒做好頁面的話可以這樣印出文字 方便分辨頁面 ```php= Route::get('/posts/{id}' , function($id){ return 'posts.show'; }); ``` View的運作方式與Blade簡介 === * php+html=義大利麵程式碼(original原生的) * .blade是laravel設置的template系統 * template架構就是把html中間挖洞 使其可以做填入 Blade的layout設定方式 === * layout先定義架構html tag寫好 然後需要的地方挖空 (yield.show.section.....) * 寫view的時候使用extends的方式填充 ![](https://i.imgur.com/l7tXseF.png) * extends => 繼承哪一隻檔案 section('title','真實內容') 或者是section('content') 內容 @endsection @parent就是保留原先內容 Blade的程式控制 === * ```@if @else @endif``` => if...else * ```@unless @endunless``` =>ifnot * ```@isset($變數) // @endisset``` * ```@auth @endauth ``` =>如果有登入的話 * ```@forelse @empty @endforelse ``` => 如果有資料就進行flrelse裡面 空的的話就盡興emtpy裡面的判斷 CSS與JS === * 要寫前端之前要執行指令```npm install``` 使其前端的部分可以更好的運作 * 可執行```npm run watch```使其監聽專案中的前端資源變更 在這些文件被修改時自動編譯和重新構建 * css與js放置的位子是在public資料夾 路徑的話public對外而言為根 所以如果是放在public/css/xx.css 那路徑為/css/xx.css 以此類推 laravel mix / 自動 compile CSS 與 JS 的利器 === * laravel mix => 使用Webpack處理CSS和JS和compiled的操作 blade製作layout === * 把會變動的區域挖空 加入code ```php= @yield('content') ``` 或者 ```php= @yield('title') ``` * 挖空以後 複製一分到/resources/view/layouts/資料夾內 將其重新命名為app.blade.php 這個就是製作好的layout檔案 layout的使用 === * 例如需要使用layouts/app.blade.php做樣板去製作index.blade.php 那就在/resources/view資料夾內建立一個index.blade.php 然後在index.blade.php的空白文件當中 使用code 去進行繼承 ```php= @extends('layouts.app') ``` * 繼承的頁面內容寫法 ```php= @section('content') @endsection ``` 或者 ```php= @section('title') @endsection ``` subview === * subview => 切版 * 使用code ```php= @include('layouts.header') ``` 把header切版出去使版面看起來更簡潔 (layouts資料夾底下的header.balde.php) diff online === * 可以檢查兩個程式碼有何區別的工具 https://www.diffchecker.com/ 頁面有些許不同的處理方式 === * 使用isset判斷 ```php= @isset($overlay) <br> @else <hr> @endisset ``` 接著在需要使其呈現true的頁面上端繼承處寫下程式碼 ```php= @extends('layouts.app',['overlay'=>true]); ``` 然後在樣板處給一個判斷式 ```php= @include('layouts.header',['overlay'=>(isset($overlay))?$overlay:null]) ``` 使其可以判斷每個頁面需要呈現不同的結果是哪些 * 如果要判斷不同頁面呈現不同CSS可以在view使用request() ```php= @if(request()->is('/')) // 邏輯 @endif ``` 或者 ```php= @if(request()->is('/contact')) // 邏輯 @endif ``` LucidChart工具繪製資料庫 === * 網址 https://lucid.app/pricing/lucidchart?referer=https%3A%2F%2Fwww.lucidchart.com%2F#/pricing/chart 1. 點選shapes ![](https://i.imgur.com/ltd8uR2.png) 2. 選擇Entity Relationship ![](https://i.imgur.com/wE2kJPJ.png) 3. 可從左邊欄位拉過來後更改 ![](https://i.imgur.com/BqnOiUN.png) Model === * model不用設定就可以運作 * model可以做排序或者找出文章 * 建立model的步驟 1. 連上database - 設定檔放在 config / database.php - 主要修改在 .env 2. 設計database - 推薦的 SQL 工具 mac - sequelPro windows - HeidiSQL - 一開始一開始 database / migrations裡面就會有兩個檔案 - 指令```php artisan migrate```會檢查哪些檔案沒有執行過就會幫你進行創表 - 指令```php artisan migrate:rollback```可以進行表單的回溯 - 一開始可以使用指令測試連線資料庫是否成功 - 可使用LucidChart工具來設計資料庫 - 名稱為複數 3. 建立table - migration 可以進行資料庫的遷移 - migration 是為了寫下來方便做版本控制 傳統的是把database複製一份 有了migration的話就可以直接資料庫遷移 - 使用指令建立migration 例如 建立一個名為posts的資料表 ```php artisan make:migration create_posts_table``` - up 是新增的狀態 down 是還原 - 操作方面有三件事情要做 1. Table * Schema : 是Laravl的工具 專門拿來做migrations用的 * Table name * Blueprint $table * 範例: Schema後面會接create新增posts的資料表 使用Blueprint把posts丟進去使function內的內容可以對posts進行調整 function裡面會放需要新增的欄位 ```php= Schema::create('posts',function (Blueprint $table){ $table->increments('id'); $table->timestamps(); }) ``` * Schema::可以接的參數有 create , rename , deop/droplfExists , table * droplfExists 可先行確認是否存在 * 如果要做修改 比如新增一個comment的欄位 可為空值 沒設定nullable就是必填 就是新增一個migration 範例如下 ```php= Schema::table('posts',function(Blueprint $table){ $table->string('comment')->nullable; }) ``` 2. Columns * column的型態有 integer(整數) , boolean(真假) , string(字串) , text(文字) , enum(只接受Admin/Manager/Member三種字串) , date/dateTime/time(時間) , json(格式) , 以及一些laravel特殊的型態 Increments(Unsigned bigint) , timestamp (created_at/updated_at的自動更新) , unsignedlinteger(串連用) softDeletes(軟刪除) * 慣例 每筆資料都會有id(increments) 還有timestame(created_at/updated_at) * modifier(額外設定) nullable (可為空值) after (要放在哪一個欄位後面 沒寫就是最後面) default (預設值) useCurrent (預設值 用在時間戳記) unsigned (用在int上 mysql專用) comment (為欄位註記) 3. Indexes * primary * unique * index * dropPrimary 刪除primaryKey名稱為id ```php= Schema::table('posts',function(Blueprint $table){ $table->dropPrimary(['id']); }); ``` * dropUnique * dropIndex * Foreign Key 將user底下的id跟posts的user_id串連 ```php= Schema::table('posts',function(Blueprint $table){ $table->unsigneInteger('user_id'); $table->foreign('user_id')->reference('id')->on('users'); }) ``` 或者可以設定成 如果當作者被刪除時 文章一起刪除 ```php= Schema::table('posts',function(Blueprint $table){ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }) ``` 如果要刪除的話 ```php= Schema::table('posts',function(Blueprint $table){ $table->dropForeign(['user_id']); }) ``` 4. 建立model - php artisan make:model 5. 使用model - Eloquent migration 修改/刪除欄位 === 1. 先安裝套件 ```composer require doctrine/dbal``` 2. 進行修改 後面要加change() 範例 name的長度改成50 可空白 新增一個nickname的欄位 把url欄位更名為email (目前laravel不支援rename所以要先刪掉在建立): ```php= Schema::table('users', function (Blueprint $table){ $table->string('name', 50)->nullable()->change(); $table->string('nickname'); $table->renameColumn('url','email'); }) ``` 3. 進行刪除 * 範例 刪除vote欄位 刪除avator跟location欄位 ```php= Schema::table('users',function(Blueprint $table){ $table->dropColumn('votes'); $table->dropColumn(['avator','location']); }); ``` * 指令有下列幾種 * droRememberToken(remember_token) * dropSoftDeletes(deleted_at) * dropTimestamps(created_at/updated_at) 建立model === * 建立好的model為eloquent model 意思是 建立在eloquent底下 * eloquent是laravel的ActiveRecord 是ORM(物件導向) 也是Active record pattern(一個模式) * Active record pattern CRUD / getter / setter / properties / etc. Model -> table Model -> model(relationships) Validation OOP style query Convention > config * 指令(範例 建立一個model名為post) 會放在app/Post.php或者app/Models/Post.php ```php artisan make:model Post``` * 指令 (範例 建立一個model名為Flight 順便創一個migration) ```php artisan make:model Flight --migration``` * model的慣例 class名稱開頭便成小寫 後面變成複數就是資料表的名稱 預設primary keys是id 預設都會有timestamps(created_at 和 updated_at) * 不想使用預設就要自己設定 - 自訂table名稱 ```php= protected $table = 'new Name'; ``` - primary key ```php= protected $table = 'new Name'; ``` - 關掉timestamps ```php= public $timestamps = false; ``` - 連另一個database ```php= protected $connection = 'connection-name'; ``` Eloquent取model資料 === * all - 取得所有資料 ```php= $flights = App\Flight::all(); foreach ($flights as flight){ echo $flight->name } ``` * refreshing models - model變更之後需要取得原始資料 拿取資料並更新 ```php= $flight = App\Flight::where('number','FR 900')->first(); $flight->number = 'FR 456'; $flight->refresh(); $flight->number; // 'FR 900' ``` 或者只取資料 ```php= $flight = App\Flight::where('number','FR 900')->first(); $freshFlight = $flight->fresh(); ``` * collection(一堆資料的集合) ```php= foreach($flights as $flight){ echo $flight->name; // 箭頭的寫法就是collection } ``` * Chunking Results Chunk不會一次把資料拿回來 範例為 一次處理兩百筆的範例 ```php= FlightL::chunk(200, function ($flights){ foreach($flights as $flight){ // } }); ``` * Cursors 一次處理一筆 ```php= foreach(Flight::whwere('foo','bar')->cursor() as $flight){ // } ``` Eloquent取得單筆model === * find 給他id直接找那一筆資料 比如id=1 ```php= $flight = App\Flight::find(1); ``` 只抓取id為1,2,3的這三筆資料 ```php= $flight = App\Flight::find([1,2,3]); ``` * first 只取第一筆資料 ```php= $flight = App\Flight::where('active',1)->first(); ``` * Not Found Exceptions 找不到吐Exceptions ```php= $model = App\Flight::findOrFail(1); $model = App\Flight::where('legs' , '>' ,100)->firstOrFail(); ``` * Aggregates * count ```php= $count = App\Flight::where('active' ,1)->count(); ``` * max 回傳最大值的數字 ```php= $max = App\Flight::where('active' ,1)->max('price'); ``` Eloquent新增&更新資料 === * save (insert) ```php= use App\Flight; $flight=new Flight; $flight->name = request()->name; $flight->save(); ``` * save (update) ```php= use App\Flight; $flight=Flight::find(1); $flight->name = request()->name; $flight->save(); ``` * Mass Updates 一次更新好幾筆 ```php= App\Flight::where('active',1) ->where('destination','San Diego') ->update(['delayed' => 1 ]); ``` * Mass Assignment 一次更新非常多欄位 ```php= use Illuminate\Support\Facades\Input; $flight = new App\Flight; $flight->fill(Input::all()); //使用Input去抓使用者填入的資料 在使用fill丟到model中 $flight->save(); ``` 也可以簡化成 ```php= use Illuminate\Support\Facades\Input; $flight = App\Flight::create(Input::all()); ``` 但這個方法有風險 會被塞賈資料 所以要在model設定fillable(可寫進資料庫的資料) 或者可設定guarded(不可被寫入的資料) 不用兩個一起用 可選其一來寫 * firstOrCreate / firstOrNew 如果有這筆資料就把它找出來做更新 沒有的話就新增 ```php= $flight = App\Flight::firstOrCreate(['name' => 'Flight 10']); ``` 如果有name為flight10的資料就修改 否則就新增並且加入參數delayed=1 ```php= $flight = App\Flight::firstOrCreate(['name' => 'Flight 10'] , ['delayed'=>1]); ``` 如果有這筆資料就先把它存到model裡面 ```php= $flight = App\Flight::firstOrNew(['name' => 'Flight 10']); ``` 如果有name為flight10的資料就修改 否則就新增並且加入參數delayed=1 也是存到model裡面先不存到DB ```php= $flight = App\Flight::firstOrNew(['name' => 'Flight 10'] , ['delayed'=>1]); ``` * updateOrCreate 如果有找到departure是Oakland以及destination是San Diego的資料 就把它更新為price 99 如果沒有資料的話就直接新增 ```php= $flight = App\Flight::updateOrCreate( ['departure' => 'Oakland' , 'destination'=>'San Diego'] , ['price' => 99] ); ``` Eloquent刪除資料 === * delete 先找出資料 進行刪除 ```php= $flight = App\Flight::find(1); $flight->delete(); ``` * destroy 已經知道有這筆資料 對id進行刪除 ```php= App\Flight::destroy(['1,2,3']); ``` * Deleting Models By Query 使用Query進行刪除 ```php= $deletedRows = App\Flight::where('active',0)->delete(); ``` * Soft Deleting 1. 要使用軟刪除需要在migration進行處理 ```php= Schema::table('flights',function(Blueprint $table){ $table->softDeletes(); }) ``` 2. model要進行調整 ```php= use Illuminate\Database\Eloquent\SoftDeletes; ``` 3. model的class裡面要use 並且給他一個欄位 ```php= use SoftDeletes; protected $datas = ['deleted_at']; ``` 4. 設定完就可以直接進行刪除 ```php= $flight = App\Flight::find(1); $flight->delete(); ``` * restore 還原被軟刪除的資料 ```php= App\Flight::withTrashed() ->where('airline_id',1) ->restore(); ``` * forceDelete 真刪除 ```php= $flight->forceDelete(); ``` Querying Soft Deleted Models === * whthTrashed 不管是否被軟刪除 都要顯示在查詢中 ```php= $flights = App\Flight::withTrashed() ->where('account_id',1) ->get(); ``` * onlyTrashed 只查詢被刪除的 ```php= $flight = App\Flight::onlyTrashed() ->where('airline_id',1) ->get(); ``` Query Scopes - Local Scopes === 只看某一部分的內容 1. 先創一個找尋熱門投票數大於一百的文章的function ```php= public function scopePopular($query) { return $query->where('votes' , '>' , 100); } ``` 2. 再創只抓有發佈的文章的functiuon ```php= public function scopeActive($query) { return $query->where('active' , 1); } ``` 3. 下query 把剛剛的那兩個function放進來當搜索條件 ```php= use App\Post; $posts = Post::popular()->active()->get(); ``` * Dynamic Scopes 使其可以多一個條件去做搜尋 ```php= public function scopeOfType($query, $type) { return $query->where('type', $type); } ``` ```php= $user = App\User::ofType('admin')->get(); ``` * Comparing Models 用於確認 ```php= if($post->is($anotherPost)){ // } ``` Query Builder === * 把Query用物件導向的方式來實作 * Query Builder的好處 1. 可以提供給其他的class使用(Eloquent) 2. 與 PHP 完美融合 3. 安全性 * where exists 如果有where exist裡面的資料的話才會進行query語句的查詢 ```php= select * from users where exist( select 1 from orders where orders.user_id = users.id ) ``` 寫成 ```php= DB::table('users') ->whereExists(function ($query) { $query->select(DB::raw(1) ->from('orders') ->whereRaw('orders.user_id = users.id')); })->get(); ``` * 資料庫內如果有JSON格式也可進行where語句的搜尋 * groupBy/having 比如使用account_id來做分類 如果分出來的結果只有五筆 就會再去從這五筆資料中抓取符合having條件account_id>100的回傳 ```php= $user = DB::table('users') ->groupBy('account_id') ->having('account_id , '>' ,100) ->get(); ``` * when 當role這個條件是符合的時候 才會進行 ```php= $role = $request->input('role'); $users = DB::table('users') ->when($role, function($query, $role){ return $query->where('role_id', $role); })->get(); ``` * insertGetId 把資料寫入後會把id回傳 ```php= $id = DB::table('users')->insertGetId( ['email' => 'john@gmail.com' , 'votes' => 0] ); ``` * increment 如果只是要撈出資料+1時使用 省去了撈出資料進行加減在存回去的步驟 範例為所有使用者的投票數加一 ```php= DB::table('users')->increment('votes'); ``` * decrement 如果只是要撈出資料-5時使用 省去了撈出資料進行加減在存回去的步驟 範例為所有只用者的投票數減五 ```php= DB::table('users')->decrement('votes',5); ``` Query Builder跟Eloquent的差別 === Query Builder的寫法 ```pyp= use Illuminate\Support\Facades\DB; $user = DB::table('users')->where('name','John')->first(); ``` Eloquent的寫法 ```php= use App\User; $user = User::where('name' , 'John')->first(); ``` 看起來很類似 因為Eloquent是Query的延伸 Model 之間的關聯(relationships) === * 在兩張資料表 posts與users做關聯 posts中的user_id欄位對到users的id欄位 1. 要在寫migration時加入最下面那一行 ```php= Schema::create('posts', function (Blueprint $table){ $table->increment('id'); $table->unsignedInteger('user_id'); $table-> timestamps(); $table->foreign('user_id')->references('id')->on('users'); }) ``` 2. model當中去做處理 ```php= namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { public function posts() { return $this->hasMany('App\Post'); } } ``` * One To One 一個使用者可以有一個隱私設定 ```php= namespace App; use Illuminate\Database\Eloquent\Model; class User extends Model { public function privacy() { return $this->nasOne('App\Privacy'); } } ``` ```php= namespace App; use Illuminate\Database\Eloquent\Model; class Privacy extends Model { public function user() { return $this->belongsTo('App\User'); } } ``` 使用方式如下 ```php= use App\User; $user = User::find(1); echo $user->privacy->settings; ``` ```php= use App\Privacy; $user = Privacy::find(1); echo $privacy->user->name; ``` * One To Many 一個使用者可以有很多文章 ```php= namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function comments() { // 因為他可以對多個comment所以是function名稱是複數 return $this->hasMany('App\Comment'); } } ``` ```php= namespace App; use Illuminate\Database\Eloquent\Model; class Comment extends Model { public function post() { // 因為多個comment只會有一個post 所以function名稱為單數 return $this->belongsTo('App\Post'); } } ``` * Many To Many 一篇文章可以有很多個tag 所以會有三張表posts跟tags以及post_tag post_tag取名的條件是a-z p比較早出現所以是post在前面 post_tag裡面一定要有兩個欄位post_id以及tag_id 而且他們的model都是belogsToMany ```php= namespace App; use Illuminate\Database\Eloquent\Model; class Post extends Model { public function tags() { return $this->belogsToMany('App\Tag'); } } ``` ```php= namespace App; use Illuminate\Database\Eloquent\Model; class Tag extends Model { public function posts() { return $this->belogsToMany('App\Post'); } } ``` 取用方式如下 ```php= use App\Post; $tags = Post::find(1)->tags()->get(); ``` 如果多對多是接手別人專案且不符合慣例的命名方式的話 可以使用以下方式去告訴程式 model,資料表,關聯欄位一,關聯欄位二 ```php= namespace App; use Illuminate\Database\Eloquent\Mmodel; class Tag extends Model { public function posts() { return $this->belongsToMany( 'App\Post','post_tag_refs','my_post_id','my_tag_id' ) } } ``` * withCount 抓出post並且計算comments的數量 ```php= $posts = App\Post::withCount('comments')->get(); foreach($posts as $post){ echo $post->comments_count; } ``` 範例二: 想知道comment的數量有幾個 第二個count想要知道另一個條件的數量 所以第二個條件給予另一個名稱 ```php= $posts = App\Post::withCount([ 'comments', 'comments as pending_comments_count' => function ($query){ $query->where('approved' , false); } ])->get(); echo $posts[0]->comments_count; echo $posts[0]->pending_comments_count; ``` Lazy Loading & Eager Loading === * Lazy Loading的範例 ```php= $books = App\Book::all(); foreach($books as $book){ echo $book->author->name; } ``` * Eager Loading的範例 ```php= $books = App\Book::with('author')->get(); foreach($books as $book){ echo $book->author->name; } ``` 範例:先抓出作者的contacts ```php= $books = App\Book::with('author.contacts')->get(); ``` * Eager Loading比較有效率 解決了n+1的問題 Controller === * controller專門處理邏輯 * 建立controller 1. 輸入指令 範例 名為Post的controller ```php artisan make:controller PostController``` 2. 在PostController裡面加一個function ```php= public function show(Request $request){ $post = new Post; $post->title = 'title'; $post->content = '...'; $post->save(); return view('posts.show' ,['post'=>$post]); } ``` 如果只要回傳json格式的話這樣寫 ```php= public function show(Request $request) { return response()->json([ 'title' => 'test', 'content' => 'test', ]); } ``` 如果只是簡單刪除 會回傳200 所以不用return任何東西 3. route/web.php加入路由 ```php= Route::get('/posts', 'PostController@show'); ``` 注入參數的三種方法 (method injection) === 1. Request 前端傳來的 不管post或get之類的 都使用Request使funciton裡面可以接住參數 ```php= namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller{ public function store(Request $request){ $name = $request->name; } } ``` 2. 網址帶參數 ```php= Route::put('user/{id}','UserController@update'); ``` function update帶的id就是上面route put裡面帶的Id ```php= namespace App\Http\Controllers; use Illuminate\Http\Request; class UserController extends Controller{ public function update(Request $request, $id) { // } } ``` 3. 使用model承接 就可以直接省掉User::find($user); 的步驟 ```php= Route::put('user/{user}','UserController@destory'); ``` ```php= namespace App\Http\Controlleres; use Illuminate\Http\Request; use App\User; class UserController extends Controller { public function destroy(Request $request, User $user) { $user->destory(); } } ``` CRUD的好幫手:resource === * 使用指令 會幫你加入CRUD的controller內容 ```php artisan make:controller UserController --resource``` * +Model 直接帶入model不用接收id去拉 ```php artisan make:controller UserController --resource --model=User``` * route也可以使用resource 直接對7個routing ![](https://i.imgur.com/hbt02aU.png) 代表的意思如下: 1. 列表 2. 新增表單 3. 新增送出 4. view 5. 編輯表單 6. 編輯送出 7. 刪除 寫法是 ```php= Route::resource('photos','PhotoController'); ``` 但如果七個之中只想要兩個 可以寫成 ```php= Route::resource('photos','PhotoController')->only([ 'index','show' ]); ``` 如果七個之中不想要四個 ```php= Route::resource('photos','PhotoController')->except([ 'create','store','update','destory' ]); ``` Routing設計 === * C - post/posts 使用post去建立文章 * C(from) - get/posts/create 建立一個表單列表 * R - get/posts/{post} 使用get 單純讀取 帶id讀取特定文章 * R(from) - get/posts 建立一個列表顯示 * U - put/posts/{post} 修改某一篇文章 * U(form) - get/posts/{post}/edit 建立一個編輯表單 * D - delete/posts/{post} 對id進行刪除 Workflow(流程) === * 可以使用繪圖軟體去畫操作流程 例如 點擊title進去會可以看到文章內容 文章內容有哪些按鈕 然後按哪些按鈕可以進行什麼操之類的 CSRF === * 419 session has expired 沒有CSRF的token或者CSRF不一致 * 解決方法是在表單當中加入@scrf ```php= <form method="post" action="/posts"> @csrf <button type="submit" class="btn btn-primary">Submit</button> </form> ``` destroy的寫法 === 1. HTTP頁面跳轉 * blde給一個按鈕 ```html= <button class="btn btn-danger" onclick="deletePost({{ $post->id }})">Delete</button> ``` * 給他一個form使其可以送出 ```html= <form id="delete_form" action="/posts/id" method="post"> <input type="hidden" name="_method" value="delete"> @csrf </form> ``` * 為了可以抓到id 下面要使用JS ```jacascript= let deletePost = function(id){ let result = confirm('確定刪除嗎?'); // console.log(result); if(result){ let actionUrl = '/posts/' + id; // console.log(actionUrl); $('#delete_form').attr('action', actionUrl).submit(); } } ``` * controller頁面寫下destroy的function ```php= public function destroy(Post $post) { $post->delete(); return redirect('/posts/admin'); } ``` 2. AJAX * blade給他一個按鈕 ```php= <button class="btn btn-danger" onclick="deletePost({{ $post->id }})">Delete</button> ``` * blade下方給他一個js ```jacascript= let deletePost = function(id){ let result = confirm('確定刪除嗎?'); // console.log(result); if(result){ let actionUrl = '/posts/' + id; // console.log(actionUrl); $('#delete_form').attr('action', actionUrl).submit(); } } ``` * 要在HTML架構最上方的head加入csrf ```html= <meta name="csrf-token" content="{{ csrf_token() }}"> ``` * 在自訂的js中加入代碼 ```jacascript= $.ajaxSetup({ headers: { 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content') } }); ``` * controller頁面寫下destroy的function ```php= public function destroy(Post $post) { $post->delete(); } ``` Authentication === * 也就是laravel的會員系統 * 架設會員系統的方法 1. 指令創建 ```php artisan make:auth``` 2. 指令執行migration```php artisan migrate``` 3. 輸入網址http://localhost:8000/register * 上方指令做了哪些事情 1. routing 去設定了所有登入需要的路由 以及做好頁面 ```php= Auth::routes(); Route::get('/home', 'HomeController@index')->name('home'); ``` 2. 在view/auth裡面放置登入需要使用的view 3. 在App\Http\Controllers\Auth當中設置登入需要的控制器 * 預設是email當帳號,如果想變更,要到App\Http\Controllers\Auth\RegisterController.php當中做修改 * Auth的重要機制 1. 限制操作 區分行為的能力 比如 登入才可操作 2. 取得登入user的資訊 可以透過登入資訊取得他的文章或者資料 * 使用方式 1. 在routing限制 有登入才會進入 否則會跳到登入頁面 ```php= Route::get('profile',function(){ // code })->middleware('auth'); ``` 2. 如果全部都需登入的話 就寫在controller的construct裡面 ```php= public function __construct() { $this->middleware('auth'); } ``` 3. 如果需要在邏輯當中檢查是否登入 ```php= use Illuminate\Support\Facades\Auth; if(Auth::check()){ // code } ``` 4. 取得使用者資訊 ```php= use Illuminate\Support\Facades\Auth; // 取得使用者資訊 $user = Auth::user(); // 取得id $id = Auth::id(); ``` middleware === * 群組寫法 原本為 ```php= Route::get('/posts/admin' , [ PostController::class, 'admin'] )->middleware('auth'); Route::get('/posts/show/{post}' , [ PostController::class, 'show'] )->middleware('auth'); Route::post('/posts' , [ PostController::class, 'store'] )->middleware('auth'); ``` 可以寫成下面這樣比較簡潔 ```php= Route::middleware(['auth'])->group(function(){ Route::get('/posts/admin' , [ PostController::class, 'admin'] ); Route::get('/posts/show/{post}' , [ PostController::class, 'show'] ); Route::post('/posts' , [ PostController::class, 'store'] ); }); ``` 關於Laravel寄送mail === * 概論 * 使用PHP內建系統發送的email很容易被歸類到垃圾信件中 * 避免變成垃圾信 可以使用串接套件 如Mailgun(正式上線時使用) . mailtrap.io(測試時用這個) * laravel內建的mail系統 => SwiftMailer * 安裝Guzzle 1. 輸入指令```compoer require quzzlehttp/guzzle``` 2. 在config/mail.php當中找到下列代碼 設定mailgun或其他email設定 可設定的值為 smtp.sendmail.mailgun.mandrill.ses.sparkpost.log.array ```php= 'driver' => env('MAIL_DRIVER' , 'mailgun'); ``` 3. 去config/services.php當中設定參數 ```php= 'mailgun' => [ 'domain' => env('MAILGUN_DOMAIN'), 'secret' => env('MAILGUN_SECRET') ], ``` 4. 去.env設定 ```php= MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=null MAIL_PASSWORD=null MAIL_ENCRYPTION=null ``` Mailgun === * 優點:Laravel有內建支援Mailgun * 串接Mailgun的流程: 1. 註冊 2. 新增domain(Domains > Add New Domain > 輸入你的domain前面要記得加上mg. 如mg.gmail.com) 3. 設定DNS TXT(去買網域的網站裡面找到DNS設定>create DNS Record把設定貼上去) 4. 設定DNS MX,讓Mailgun可以透過domain寄信(跟TXT做法一樣) 5. 得到seret 6. 設定Laravel的mail config * 設定Laravel的mail設定 1. 輸入指令```composer require quzzlehttp/guzzle``` 2. 去.env把MAIL_DRIVER改成mailgun 3. .env下面加入兩行 並且把Domain Information的value填入.env的MAIL跟MAILGUN中 ```php= MAILGUN_DOMAIN= MAILGUN_SECRET= ``` 4. 更改寄件資訊的位子是.env裡面加下列兩行 ```php= MAIL_FROM_ADDRESS=tsubasababy1013@gmail.com MAIL_FROM_NAME=Hollie ``` mailtrap.io === * 網址為https://mailtrap.io/inboxes * mailtrap.io是測試專用的 他不會真的寄給使用者 會存在mailtrap.io裡面 * 免費版可以收50封 * 可以選擇Laravel他會教怎麼設定 ![](https://hackmd.io/_uploads/By_GvZAV2.png) Validation(驗證機制) === * 避免使用者送出不符合規定的資料噴錯 * HTTP request的範例: 儲存之前驗證 title必填 posts不可重複 最大255 body必填 符合以後才會發送 ```php= public function store(Request $request) { $validatedDate = $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required', ]); // 符合才會繼續下面程式碼 } ``` 發現錯誤後view層印出來 ```php= @if ($errors->any()) <div> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif ``` * AJAX request的話會回傳422 status code的JSON內容 * 如果比對第一個不符合就不要比對後面的話可以使用bail 所以 title如果檢查一個不符合就不會繼續往後檢查 但是body依然也會檢查參數 ```php= $request->validate([ 'title' => 'bail|required|unique:posts|max:255', 'body' => 'required' , ]); ``` * 如果是array的話 如 ```html= <input name="author[name]"> ``` 可以這樣寫 ```php= $request->validate([ 'title' => 'required|unique:posts|max:255', 'author.name' => 'required' , 'author.description' => 'required', ]); ``` * Laravel有個機制叫做TrimStrings / ConvertEmptyStringsToNull 會把空的字串轉為null 送去資料庫就會變成資料型態不一樣 所以碰到可保留空白的 要輸入nullable ```php= $request->validate([ 'title' => 'required|unique:posts|max:255', 'body' => 'required' , 'publish_at' => 'nullable|date', ]); ``` * 這些驗證的參數 可以在官網的Available Validation Rules當中找到 Validation如何印出錯誤訊息 === * view層有一個可以印出error的地方後即可打印訊息 ```php= @if ($errors->any()) <div> <ul> @foreach ($errors->all() as $error) <li>{{ $error }}</li> @endforeach </ul> </div> @endif ``` * 基礎的印出訊息 ```php= $errors = $validator->errors(); ``` * 印出特定欄位 1. 取得第一個email的錯誤訊息 ```php= echo $errors->first('email'); ``` 2. 取得所有的錯誤訊息 ```php= foreach($errors->get('email') as $message){ // } ``` 3. 把input是陣列的那種內容錯誤訊息拿來印 ```php= foreach($errors->get('attachements.*') as $message){ // } ``` 4. 如果有錯誤訊息就拿來印 ```php= if($errors->has('email')){ // } ``` Custom Error Messages(客製化錯誤訊息) === ![](https://hackmd.io/_uploads/H1tWNfRNh.png) 圖片中 $request就是$input 條件就是$rules $messages就是客製化的錯誤訊息 * $messages要這樣設定 ```php= $messages = [ 'required' => 'The :attribute field is required.' , ]; $validator = Validator::make($input, $rules, $messages); ``` 或者 ```php= $messages = [ 'same' => 'The :attribute and :other must match.', 'size' => 'The :attribute must be exactly :size.', 'between' => 'The :attribute value :input is not between :min = :max.', 'in' => 'The : attribute must be one of the following types: :values', ]; ``` Form Request Validation(在request的時候做驗證) === * 為了把method簡化 可以創一個Form Request專門處理驗證 * Form Request是一個完整的class * 創建指令為```php artisan make:request StoreBlogPost``` * rules裡面可以寫驗證條件 如 ```php= public function rules() { return [ 'title' => 'required|unique:posts|max:255', 'body' => 'required' , ]; } ``` * Form Request裡面一個authorize的function 可以進行驗證身份 確認是否有修改的權限 return ture就是可修改 範例: ```php= Route::post('comment/{comment}'); ``` ```php= public function authorize() { $comment = Comment::find($this->route('comment')); // request()->comment; //先找出來 看看是否有這個 return $comment && $this->user()->can('update', $comment); //確認是否有內容 有的話看去比對這個使用者是否有修改的權限 } ``` 或者可以寫成 ```php= public function authorize() { $comment = Comment::find($this->route('comment')); return $comment && ($this->user()->id == $comment->user_id); //確認是否有內容 有的話 去比對id是否一樣 一樣才可進行修改 } ``` Custom Validation Rules(自訂規則) === * 有兩種方法 1. Using Rule Objects(做一個物件) 2. Using Closures(使用closures) * Using Rule Objects 例如:設定全部大寫 1. 輸入指令```php artisan make:rule Uppercase``` 2. passese是可通過驗證的邏輯 ```php= public function passes($attribute, $value) { return strtoupper($value) === $value; } ``` 3. message是錯誤回傳訊息 ```php= public function message() { return 'The :attribute must be uppercase.'; } ``` 4. 寫法 ```php= use App\Rules\Uppercase; $request->validate([ 'name' => ['required', 'string', new Uppercase], ]); ``` * Using Closures 直接寫function ```php= $validator = Validator::make($request->all(), [ 'title' => [ 'required', 'max:255', function ($attribute, $value, $fail) { if($value === 'foo') { $fail($attribute.' is invalid.'); } }, ], ]); ``` 如何頁面是add還是edit === * 從route判斷(current routing) ```php= if($request->is('admin/*')){ // } ``` 或者 ```php= @php // 從路徑判斷是否為新增頁面 $isCreate = request()->is('*create'); $actionUrl = ($isCreate) ? '/posts' : '/posts/' . $post->id; @endphp ``` * 從model判斷(record is new) 如果model不存在的話 就是新的 ```php= $isCreate = !$post->exists; $actionUrl = ($isCreate) ? '/posts' : '/posts/' . $post->id; ``` 加上authorization === 身份驗證使使用者不可以更改其他人的文章 1. 到app/Http/Requests/StoreBlogPost.php 2. 找到authorize的function 3. ```php= public function authorize() { $post = request()->post; //找到文章 if ($post->user_id === Auth::id()){//檢查文章id是否跟使用者id吻合 return true; }else{ return false; } } ``` Category跟tab的差別 === * 都是分類但是不太一樣 * category有點像資料夾 category裡面可以有很多個內容 內容只能依附一個category (一對多) * tags 一個標籤可以對多個內容 一個內容也可以有多個標籤 (多對多) 製作Category(一對多) === 1. Database設計 ![](https://hackmd.io/_uploads/B1b4XkIr3.png) 2. Migration * 因為要在posts的表當中加上一個新的欄位 但是不要去動之前的migration 所以要在這次的migration新增欄位的下方加入下列程式碼做控制 ```php= /* 在posts表當中的user_id後面加上一個category_id的欄位 */ Schema::table('posts' , function (Blueprint $table) { $table->unsignedBigInteger('category_id')->after('user_id')->nullable(); $table->foreign('category_id')->references('id')->on('categories')->onDelete('set null'); // onDelete('cascade') 當刪除category時,post的category_id會變成null }); ``` 因為有設置關聯 所以down也要做設置 ```php= Schema::table('posts' , function (Blueprint $table) { $table->dropForeign(['category_id']); $table->dropColumn('category_id'); }); ``` 3. Model * 除了本身分類的model 還要記得去改關聯表的model設定 4. Workflow設計 * 後台 * CUD * list page * @create / @update post from * @post read * 前台 * categories list * category下面的文章列表 * @single post 5. CRUD routing 6. Controller 7. View 8. 完成基本的操作以後要做資料驗證避免被亂塞資料 * 指令```php artisan make:request StoreCategory``` * 控制器在新增跟修改時使用StoreCategory接受參數 製作Tag(多對多) === 1. database設計 ![](https://hackmd.io/_uploads/SkEy1MIS3.png) 2. migration * 當多對多的時候 需要新增中間表 所以會要一次Schema兩個表 並且要在中間表設置關聯 ```php= Schema::create('tags', function (Blueprint $table) { $table->increments('id'); $table->string('name'); $table->timestamps(); }); Schema::create('tags', function (Blueprint $table) { $table->increments('id'); $table->unsignedInteger('post_id'); $table->unsignedInteger('tag_id'); $table->timestamps(); $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade'); $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); }); ``` down要記得把關連刪掉 再刪除兩張表 ```php= Schema::table('post_tag' ,function(Blueprint $table){ $table->dropForeign(['post_id']); $table->dropForeign(['tag_id']); }); Schema::dropIfExists('post_tag'); Schema::dropIfExists('tags'); ``` 3. model 4. workflow設計 * 後台 * list page 跟 刪除 * @create / @update post from * @post read * 前端 * 標籤雲的呈現 @post list * 列表標籤呈現 * 只抓有文章的tag ```php= $tags = Tag::has('posts')->get(); ``` * 如果有文章 就把文章數量由大到小排序 ```php= $tags = Tag::has('posts')->withCount('posts')->orderBy('posts_count' , 'desc')->get(); ``` * @single post 5. CRUD routing 6. Controller 7. Views 圖片上傳 === 1. 前端頁面給予一個input使用multipart/post-data 3. controller裡面儲存檔案 ```php= $path = $request->file('thumbnail')->store('public'); ``` 這樣檔案會存在storage/app/public底下 因為只有根目錄的public底下的檔案才可被使用者瀏覽 所以需要創建軟連結 ```php artisan storage:link``` 4. File handling * Store to disc * Save path to DB 留言系統 === 1. database設計 ![](https://hackmd.io/_uploads/Bya4drwr3.png) 2. migration 3. model 4. routing 5. controller * 完成create * create儲存之前資料驗證 * 完成delete * 完成update * update的驗證 6. 前端的呈現 分頁機制 === * laravel有分頁機制 1. 在撈資料時把User::all()改成分頁機制即可 ```php= $users = App\User::paginate(15);//15筆為一頁 ``` 2. 在view層 ```php= {{ $posts->links() }} ``` 樣式的部分可以自定義或者使用vendor:publish (使用指令```php artisan vendor:publish --tag=laravel-pagination``` 把樣式複製出來後去更改bootstrap-4的檔案) https://hiskio.com/courses/234/about