Laravelメモ

随時更新

# インストール後にやっておいた方が良い事

* clone時にautoCRLFを無効にしていてもCRLFでcloneされてしまうので、以下の行を削除しておく

```:.gitattributes
* text=auto
```

* 各自判断になりますが、入力値をtrimするMiddlewareと入力値の空文字をnullにするMiddlewareのコメントアウト

```php:app/Http/Kernel.php
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
```

# 忘れた頃にやってくる罠（namespaceの更新）

修正等で新たなクラスを追加してnamespaceを追加した場合はnamespaceの更新が必要（思わぬところでClass not foundが出ます。既にあるネームスペース配下にクラスを追加した場合は特に問題なく見つかるので忘れがちになります。）

```
composer dump-autoload
```

# リダイレクト

```php
return redirect('/home');
```

名前付きルート

```php
return redirect()->route('home');
```

入力値を維持して$errors['test']を設定して元の画面に戻す。

```php
return redirect()->back()->withErrors(['test' => 'test error'])->withInput();
```

入力値を戻す際にはbladeに以下のような設定が必要です。

oldは第二引数にデフォルト値を設定出来るそうです。（コメントより）

```
name="email" value="{{ old('email') }}"

name="email" value="{{ old('email', $user_object->email) }}"

name="email" value="{{ old('email', $email ?? '') }}"
```

フラッシュデータをリダイレクト先に渡す

```php
return redirect()->route('update', ['id' => $id])->with('success', true);
```

画面に表示するにはbladeに以下のような設定します。

```
@if (session('success'))
    更新に成功しました。
@endif
```

コントローラ内から他のPOSTメソッドを呼び出す

```php
$request= new Request();
$request->replace(['test' => 'test']);
return $this->methodName($request);
```

[oldとerrorsをコントローラ内で設定する方法](https://qiita.com/horikeso/items/095c0944c3191d5b7ce2)

# レスポンス

JSONを返す

```php
return response()->json(['message' => 'OK'], 200);
```

ダウンロードさせる

```php
return response()->download($pathToFile);
```

ファイルを開いて表示

```php
return response()->file($pathToFile);
```

# エラーページに飛ばす

[abortヘルパ](https://readouble.com/laravel/5.5/ja/errors.html#http-exceptions)を使う

# CSRF除外

```php:app/Http/Middleware/VerifyCsrfToken
protected $except = [
 'api/*'
];
```

# パラメータ取得(GET POST)

```php
use Illuminate\Http\Request as HttpRequest;
use Request;
```

`?query=test`

```php
Request::input('query');// test

// コントローラの引数を(HttpRequest $request)とする
$request->query;// test
```

`?query=`

```php
Request::input('query');// null

// コントローラの引数を(HttpRequest $request)とする
$request->query;// null
```

`<input type="text" name="test">`

```php
Request::input('test');

// コントローラの引数を(HttpRequest $request)とする
$request->test;
```

リクエストボディのjsonのプロパティも同じ用に取得できます。

# メソッド取得

```php
$request->method();// GET,POST...
```

# セッション

```php
use Session;

Session::get('key');
Session::put('key', $value);
Session::regenerate();
Session::forget('key');
Session::flush();
```

**コントローラ内のメソッドでは使用できたが、コンストラクタ内ではSession::get('key')で何故か取得できなかった。**

コントローラのコンストラクタ内でSessionを使うなら、[Kernel.phpのmiddlewareに以下を追加てmiddlewareGroupsのwebに`\Illuminate\Session\Middleware\StartSession::class,`が設定されている場合は削除しろとのこと](https://stackoverflow.com/questions/41542802/laravel-cant-get-session-in-controller-constructor#answer-41542867)

**middlewareGroupsのwebに`\Illuminate\Session\Middleware\StartSession::class,`が設定されている場合はコメントアウトする**

これが設定されている場合は二重に呼び出してしまいエラーで戻した場合のerrorやinputが空になってしまった。
二重に呼び出しするとグローバルにセットしたセッションは保持されているがローカルなセッションは消えてしまう様子？

```php
\Illuminate\Session\Middleware\StartSession::class,
```

# コントローラのコンストラクタ

Auth::user()のようなコントローラのコンストラクタで直では取得できないものや上記のSessionのようにうまく使えないものもあるのでmiddlewareを使用すると良い

```php
namespace App\Http\Controllers;

use Illuminate\Support\Facades\View;
// AuthやSessionを使う場合
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Session;
```

```php
public function __construct()
{
    $this->middleware(function ($request, $next) {

        $data = [];

        // viewに共通データを渡す
        View::share($data);

        return $next($request);
    });
}
```

# バリデーション

### routesでバリデーションを行う

```php
Route::get('/test/{id}', 'TestController@index')->where(['id' => '[0-9]+'])->name('test');
```

(条件に合わない場合は404)

### Validatorクラスを使う

#### クエリーストリングとセグメントをコントローラー内でチェックする

```php
use Request;

～～～～～～～～～～～～

public function index(string $id)
    {
        $data = [];
 
        // クエリーストリングチェック
        $validator = Validator::make(Request::all(), [
            'query' => 'required'
        ]);
        $validator->after(function($validator) use ($id) {
            // セグメントチェック
            if ($id !== '1') {
                $validator->errors()->add('id', 'idが不正です。');
            }
        });

        // errorsに入れるとこのページがフォームだった場合にフォームの実行結果から戻した場合のエラーに上書きされるので分ける
        $data['direct_errors'] = $validator->errors();

        // バリデーション実行
        if ($validator->fails()) {
            return view('sample_form', $data);
        }
        return view('sample_form', $data);
    }
```

クエリーストリングを取得するには`Request::query('page')`(?page=の場合)
**未設定の場合はnullを返します**

#### フォームデータをコントローラー内でチェックする

```php
use Illuminate\Http\Request;

～～～～～～～～～～～～

public function execute(Request $request)
    {
        $data = [];

        $validator = Validator::make($request->all(), [
            'name' => 'required',
            'tel' => 'required',
            'zip' => 'required',
            'textarea' => 'nullable|string|max:200',// textareaは何も入力しないとnullなのでnullも通す　maxは文字数
            'password' => 'required|regex:/^(?=.*?[A-Za-z])(?=.*?[0-9])(?=.*?[ -\/:-@\[-`\{-\~])/',
        ],
        [
            'password.regex' => '半角英数記号を含めてください。',
        ]);

        // 特定の値を変換して登録する場合用に変換後の値を保持しておくオブジェクト
        $std_object = new \stdClass();
        $std_object->tel = $request->tel;
        $std_object->zip = $request->zip;

        $validator->after(function($validator) use ($std_object) {
            $tel = str_replace(['-', '‐'], '', $std_object->tel);// ハイフンを取り除く
            $std_object->tel = mb_convert_kana($tel, 'n');// 全角数字を半角に変換
            if (preg_match('/^0[1-9][0-9]{8,9}$/', $std_object->tel) !== 1)
            {
                $validator->errors()->add('tel', '電話番号の形式が正しくありません。');
            }
            $zip = str_replace(['-', '‐'], '', $std_object->zip);// ハイフンを取り除く
            $std_object->zip = mb_convert_kana($zip, 'n');// 全角数字を半角に変換
            if (preg_match('/^[0-9]{7}$/', $std_object->zip) !== 1)
            {
                $validator->errors()->add('zip', '郵便番号の形式が正しくありません。');
            }
        });

        // バリデーション実行
        if ($validator->fails()) {
            // 入力値を維持して$errorsを設定して元の画面に戻す。
            return redirect()->back()->withErrors($validator->errors())->withInput();
        }

        return view('sample_result', $data);
    }
```

```:blade
@if ($errors->any())
    @foreach ($errors->all() as $error)
        {{ $error }}
    @endforeach
@endif
```

#### Validatorをエラー表示だけに使う

[Illuminate\Contracts\Validation\Factory](https://laravel.com/api/5.7/Illuminate/Contracts/Validation/Factory.html#method_make)
`make(array $data, array $rules, array $messages = [], array $customAttributes = [])`

```php
$validator = Validator::make([], []);
$validator->errors()->add('error', 'XXに失敗しました。');// フォームのバリデーションに使っていないのでフィールド名は適当で良い
return redirect()->back()->withErrors($validator->errors())->withInput();
```

#### バリデーションのエラー結果にそのまま表示されるname属性を変更する

例）
name属性がgenderのものを性別と表示する。
name属性がareaのものを地域と表示する。

```php:resources\lang\ja\validation.php
'attributes' => [
    'gender' => '性別',
    'area' => '地域',
],
```

#### 配列に含まれる値のみを許容する設定

```php
'status' => 'required|integer|in:' . implode(',', $statusList),
```

#### 配列のバリデーション

例）
name属性がarea[]とdetail_area[]のチェックボックスがあるとする。
どちらか必須入力。
要素は値が入っていて255文字までとする。

```html
<label><input type="checkbox" name="area[]" value="東北">東北</label>
<label><input type="checkbox" name="area[]" value="関東">関東</label>

<label><input type="checkbox" name="detail_area[]" value="東京">東京</label>
<label><input type="checkbox" name="detail_area[]" value="千葉">千葉</label>
```

```php
$validator = Validator::make($request->all(), [
    'area' => 'required_without:detail_area|array',// detail_areaが設定されていない場合は必須 配列
    'area.*' => 'required|max:255',// 配列に値が入った場合に次の条件を満たす 空のvalueは許可しない 255文字まで
    'detail_area' => 'required_without:area|array',// areaが設定されていない場合は必須 配列
    'area.*' => 'required|max:255',// 配列に値が入った場合に次の条件を満たす 空のvalueは許可しない 255文字まで
]);
```

エラー結果表示設定

```php:resources\lang\ja\validation.php
'attributes' => [
    'area' => '地域',
    'area.*' => '地域',
    'detail_area' => '地域',
    'detail_area.*' => '地域',
]
```

bladeではそれぞれのバリデーションエラーには

```
@if ($errors->has('area'))
    {{ $errors->first('area') }}
@endif

@if ($errors->has('area.*'))
    {{ $errors->first('area.*') }}
@endif
```

のようにアクセス可能

また以下のようにもアクセスできます

```
@if ($errors->has('area.0'))
    {{ $errors->first('area.0') }}
@endif
```

入力値を保持する場合（エラー時にページを戻した場合）

```
<label><input type="checkbox" name="area[]" value="東北"{{ old('area') ? (in_array('東北', old('area')) ? ' checked' : '') : '' }}>東北</label>
<label><input type="checkbox" name="area[]" value="関東"{{ old('area') ? (in_array('関東', old('area')) ? ' checked' : '') : '' }}>関東</label>

<label><input type="checkbox" name="detail_area[]" value="東京"{{ old('detail_area') ? (in_array('東京', old('detail_area')) ? ' checked' : '') : '' }}>東京</label>
<label><input type="checkbox" name="detail_area[]" value="千葉"{{ old('detail_area') ? (in_array('千葉', old('detail_area')) ? ' checked' : '') : '' }}>千葉</label>
```

##### 多次元配列の場合

[多次元配列のバリデーション](https://qiita.com/horikeso/items/4f8093b0871fba3b4c1d)

# セグメントの取得

```php
$request->segment($number);// string|null numberは1から
```

[Illuminate/Http/Request](https://laravel.com/api/5.5/Illuminate/Http/Request.html)
[segment](https://laravel.com/api/5.5/Illuminate/Http/Request.html#method_segment)

## [フォームリクエストバリデーション](https://readouble.com/laravel/5.8/ja/validation.html#form-request-validation)（バリデーションをコントローラに書かない）

設定例

```
App\Http\Requests\コントローラ名\メソッド名Request
```

```
public function authorize()
{
    // 認証系の判定(falseを返すと401特に判定を行わない場合はtrueで良い)
    // Middlewareで判定した方がすっきりすることも（validation→middleware）
    return true;
}

public function rules()
{
    return [
        'date' => ['nullable', 'date_format:Y-m-d', new myCustomRule()],
    ];
}

// resoureces/langのvalidation.phpの設定を使わない、または上書きする場合
public function attributes()
{
    return [
        'date' => '日付',
    ];
}

// resoureces/langのvalidation.phpの設定を使わない、または上書きする場合
public function messages()
{
    return [
        'date.nullable' => 'エラー',
    ];
}
```

[カスタムバリデーションルール](https://readouble.com/laravel/5.5/ja/validation.html#using-rule-objects)

```
App\Rules\myCustomRule
```

```
public function passes($attribute, $value)
{
    // バリデーションの判定
    return true;
}

public function message()
{
    return ':attributeが失敗したときのメッセージ';
}
```


### 使い方

以下でvalidationが通ったものだけがこのコントローラにたどり着ける

コントローラ

```
use App\Http\Requests\コントローラ名\メソッド名Request

public function メソッド名(メソッド名Request $request)
{
}
```

# アップロード

[storeAs](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Http/UploadedFile.php)

```php
$validator = Validator::make($request->all(), [
    'file' => 'required|file|max:10|mimes:jpeg|mimetypes:image/jpeg|dimensions:min_width=80,min_height=80,max_width=360,max_height=360',// maxはKBになる
]);

if ($validator->fails())
{
    // エラー
}

$filename = 'icon.jpeg';
// storage/app/imageに保存 絶対パスにしたところでstorage/app配下に保存される ディレクトリは勝手に作成してくれた
$path = $request->file('file')->storeAs('image', $filename);// string|false

if ($path === false)
{
    // エラー
}
```

**バリデーションのmimetypesを設定する際の確認方法**
`$request->file_name->getMimeType();`
または普通に
`mime_content_type($_FILES['file_name']['tmp_name']);`

※バリデーションのmimesはそのままの拡張子ではなく、mimetypesに対応する拡張子になる。

## 公開ディレクトリに置く

```php
// storage/app/public/imageに保存
$request->file('file')->storeAs('public/image', $filename);
```

のようにして以下でシンボリックリンクを作成

```
ln -s storage/app/public public/storage
```

# セグメントが変数で定義されたルートに変数を渡してアクセスする

```php:routes\web.php
Route::get('/test/{id}', 'TestController@index')->name('test');
```

```:resources\views\XXXX.blade.php
{{ route('test', $id) }}
{{ route('test', ['id' => $id]) }}

引数が複数ある場合
{{ route('test', [$id, $id2]) }}
Keyは無くても可
```

（第二引数を設定しないとエラーになります。）


# ログイン後にユーザー登録をさせたい

AuthのRegisterControllerのコンストラクター等に記載されている

```php:app\Http\Controllers\Auth\RegisterController
$this->middleware('guest');
```

は

```php:app\Http\Kernel.php
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
```

`RedirectIfAuthenticated`にはログインしていればリダイレクトするという処理が記載されている。

RegisterControllerのメソッドはログインしていればリダイレクトされてしまうのでログイン済みの場合は実行できない。

RegisterControllerにメソッドを追加したりした際にリダイレクトさせたくない場合（ログイン後（管理者等で）に一般ユーザーを登録させたい場合等）は例外を設定すれば良い。

```php:app\Http\Controllers\Auth\RegisterController
$this->middleware('guest', ['except' => [
    'showGeneralRegistrationForm',
    'generalRegister',
]]);
```

# ログイン済みの場合のログイン画面からのリダイレクト先を変更する

`RedirectIfAuthenticated`の`handle`メソッド内で処理を変更する

```php:app\Http\Middleware\RedirectIfAuthenticated
if (Auth::guard($guard)->check())
{
    if ($guard === 'web')
    {
        return redirect('/sample');
    }
}
```

# ログインしていない場合のリダイレクト先の設定

```php:app\Exceptions\Handler.php 
// 認証されていない場合のリダイレクト先設定の上書き

/**
 * Convert an authentication exception into a response.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Illuminate\Auth\AuthenticationException  $exception
 * @return \Illuminate\Http\Response
 */
protected function unauthenticated($request, AuthenticationException $exception)
{
    if ($request->expectsJson()) {
        return response()->json(['error' => 'Unauthenticated.'], 401);
    }

    if (in_array('backend_web', $exception->guards())) {
        return redirect()->guest(route('backend.login'));
    }

    return redirect()->guest(route('login'));
}
```


# middlewareで複数のゲートを設定したい場合

adminとgeneralが通れるallゲートを作成する

```php:app\Providers\AuthServiceProvider.php
Gate::define('all', function ($user) {
    return $user->role === \App\Model\Role::ADMIN || $user->role === \App\Model\Role::GENERAL;
});

// 管理者判定
Gate::define('admin', function ($user) {
    return $user->role === \App\Model\Role::ADMIN;
});

// 一般ユーザー判定
Gate::define('general', function ($user) {
    return $user->role === \App\Model\Role::GENERAL;
});
```

allでゲートを設定する

```php:routes\web.php
Route::group(['middleware' => ['auth:web', 'can:all']], function () {
    Route::get('/home', 'HomeController@index')->name('home');
});
```

### 以下の二通りの方法で出し分けしたりする

* コントローラでviewを出し分ける

```php
use Illuminate\Support\Facades\Auth;

if (Auth::user()->can('admin') === true)
{
    return view('home');
}
if (Auth::user()->can('general') === true)
{
    return view('general_home');
}
```

* 画面でadminとgeneralを出し分ける

```:resources\views\home.blade.php
@can('admin')
    ADMIN
@endcan

@can('general')
    GENERAL
@endcan
```

# 強制ログイン

```php
use App\User;

$user = User::find(1);
$this->guard()->login($user);
```

# 自前でパスワードチェック

```php
use Illuminate\Support\Facades\Hash

Hash::check($request->password, 'DBにパスワードとして登録されているハッシュ文字列');// return bool
```

ハッシュ値を作るのは

```php
Hash::make($password)
```


# Guard webでログイン判定

## PHP

```php
if (Auth::guard('web')->check())
{
    // ログイン済み
}
```

## blade

```
@guest('web')
// 未認証
@endguest

@auth('web')
// 認証済
@endauth
```

# bladeの中で変数を定義する

```
@php
    $variable = 0;
@endphp
```

```
<?php $variable = 0; ?>
```

# 現在のURLを取得

```
url()->current();
```

# [キャッシュ](https://readouble.com/laravel/5.8/ja/cache.html)

```
use Illuminate\Support\Facades\Cache;
```

基本

```php
Cache::get('key' . Auth::user()->id);// 存在しない場合はnull 第二引数にデフォルト値を設定可能
Cache::put('key' . Auth::user()->id, $value, $expireMinutes);// $expireMinutesは期限切れ時刻（DateTime）でも可
Cache::forget('key');
```

キー存在チェック

```php
Cache::has('key');// bool
```

存在する場合はそのまま取得し、存在しない場合はクロージャの戻り値を登録しその値を取得する

```php
Cache::remember('key' . Auth::user()->id, $expireMinutes, function () {
    return 'test';
});
```

取得時して削除

```php
Cache::pull('key');
```

# DB([クエリービルダ](https://readouble.com/laravel/5.7/ja/queries.html))

[Builder](https://laravel.com/api/5.8/Illuminate/Database/Query/Builder.html)

```php
use Illuminate\Support\Facades\DB;
```

## INSERT

```php
try
{
    $result_flag = DB::table($table_name)
        ->insert($data);
}
catch (\Throwable $error)
{
    return false;
}

return $result_flag;
```

または

```php
// 失敗時は$insert_idはnull
$insert_id = DB::table($table_name)->insertGetId($data);
```

### バルクインサート

```
$result_flag = DB::table($table_name)->insert([$data, $data, $data]);
```

* 失敗時は何も登録されません。
* insertGetIdはバルクインサートに使えません。
* バルクインサートで入れたIDが欲しい場合は実行前の数値(for updateして取得)と登録件数から計算する必要があります。

## WHERE系

whereNull / whereNotNull
whereIn / whereNotIn
whereBetween / whereNotBetween
など

## LIKE

ワイルドカードの`%`（０回以上の任意の文字）と`_`（任意の1文字）はエスケープされないので入力値の`%`と`_`という文字列は検索できないのでエスケープする必要があります

```php
$input = str_replace(['%', '_'], ['\%', '\_'], $input);
```

```php
->where('name', 'like', '%' . $input . '%');
```

## UPDATE

```php
try
{
    $count = DB::table($table_name)
        ->where('id', $id)
        ->update($data);
}
catch (\Exception $exception)
{
    return false;
}

return $count === 1;
```

## １レコード取得

**\stdClassまたはnullを返す。**

```php
$object = DB::table($table_name)
        ->where('id', $id)
        ->first();
```


## レコードからpluckで取得

**値の入った配列を返す。**
（pluckはIlluminate\Support\Collectionを返す）

```php
$name_list = DB::table($table_name)->pluck('name')->toArray();
```

## リストで取得

**\stdClassの配列を返す。**

```php
$object_list = DB::table($table_name)
        ->where('delete_flag', 0)
        ->orderBy('id', 'desc')
        ->offset($offset)
        ->limit($limit)
        ->get()->all();
```

## A and (B or C)

```php
$builder = DB::table($table_name)
    ->where('delete_flag', 0);// A

if ( ! empty($search))
{
    $search = str_replace(['%', '_'], ['\%', '\_'], $search);
    $builder->where(function($query) use ($search){
        $query->where('name', 'like', '%' . $search . '%')// B
            ->orWhere('email', 'like', '%' . $search . '%');// C
    });
}
```

## SQL実行

```php
$preparedSql = 'INSERT INTO `table` (`name`, `value`) VALUES (?, ?), (?, ?) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`)';
$valueList = ['名前1', 1, '名前2', 2];

$resultFlag = DB::statement(
    $preparedSql,
    $valueList
);
```


## トランザクション

```
DB::beginTransaction();

DB::commit();

DB::rollback();
```

### FOR UPDATE

```php
$object_list = DB::table($table_name)
        ->where('id', 1)
        ->lockForUpdate()
        ->get()
        ->all();
```

# ログ

## ログファイルをファイル名を指定して作る

```
Log::useFiles(storage_path().'/logs/test_'.$test_id.'.log');
```

[useFiles](https://laravel.com/api/5.5/Illuminate/Log/Writer.html#method_useFiles)

## 暗号化

[暗号化](https://readouble.com/laravel/5.5/ja/encryption.html)

## その他

[ログファイルの解放](https://qiita.com/horikeso/items/669b54eedc1851fbabb3)