---
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的方式填充

* 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

2. 選擇Entity Relationship

3. 可從左邊欄位拉過來後更改

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

代表的意思如下:
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他會教怎麼設定

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(客製化錯誤訊息)
===

圖片中 $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設計

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設計

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設計

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