---
# System prepended metadata

title: 工程(5) Laravel 使用手冊
tags: [work - 教育訓練課程進修]

---

---
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裡面先不存到ＤＢ
```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();
        }
    }
    ```
ＣＲＵＤ的好幫手: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 下面要使用ＪＳ
        ```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. ＡＪＡＸ
    * 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

