Laravelのちょっとつかえる小技

基本自分用のメモですが、何かしらお役に立てれば幸いです。
随時更新していく予定です。

### Laravel Excelでウィンドウ枠の固定
```php
use Maatwebsite\Excel\Concerns\WithStyles;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class InvoicesExport implements FromCollection, WithStyles
{
	public function styles(Worksheet $worksheet)
	{
	    $worksheet->freezePane('A5');
	}
}
```

### イベントなしの単一モデル保存
モデルイベントを発行させずに、特定のモデルを「保存」したい場合があります。その場合は、saveQuietlyメソッドを利用する。
```php
// User.php
class User extends Model
{
    protected static function boot()
    {
        parent::boot();
        static::saving(function ($model) {
            $model->name = strtolower($model->name);
        });
    }
}

// UserController.php
$user = User::findOrFail(1);
$user->name = 'Victoria Faith';
$user->saveQuietly();

dd($user->name); #=> Victoria Faith
```

### ルーティングリストをJSONファイルに書き出す
```php
php artisan route:list --json > routes.json
```

### JSON配列を検索する
```php
+-----------------------------------------------------------------------------------------------+
| id | tags                                                                         |
+-----------------------------------------------------------------------------------------------+
|  1 | ["Ruby","PHP","Javascript"]                                                  |
+-----------------------------------------------------------------------------------------------+

$tag = 'Javascript';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'[\"{$tag}\"]')")->get();

+-----------------------------------------------------------------------------------------------+
| id | tags                                                                         |
+-----------------------------------------------------------------------------------------------+
|  1 | [{"id":1,"tag":"Ruby"},{"id":2,"tag":"PHP"},{"id":2,"tag":"Javascript"}]     |                                         
+-----------------------------------------------------------------------------------------------+

$tag = 'Javascript';
$posts = Post::whereRaw("JSON_CONTAINS(tags,'{\"tag\":{$tag}}')")->get();
```

## 共有configファイルの設定
```php
// 階層
- public_config
  - payment.php
- my_project
  - app
    - Providers
      - ConfigServiceProvider.php

// ServiceProvider追加
class ConfigServiceProvider extends ServiceProvider
{
    public function register()
    {
        $config = '../public_config/payment.php';
        $this->app['config']['payment'] = include $config;
    }
}
```

## setTestNow()でテスト日時をセットする
```php
Carbon::setTestNow(Carbon::parse('2020-11-04 16:16:16'));

echo Carbon::now();
// 結果 => 2020-11-04 16:16:16
echo Carbon::tomorrow();
// 結果 => 2020-11-03 00:00:00
```

## 登録したルーティングをすべて取得したい
```php
$routes = collect(\Route::getRoutes())->map(function ($route) {
    return [
        'url' => $route->uri(),
        'route_name' => $route->getName(),
        'method' => $route->methods()[0],
        'action_name' => $route->getActionName(),
    ];
});
```

## 全イベントハンドラの実行を停止したい
```php
class Order extends Model
{
    public static function boot()
    {
        parent::boot();
        static::creating(function($model){
            //
        })
        static::deleted(function($model){
            //
        })
    }
}

$order = Order::withoutEvents(function(){
        return // 処理コード
});
```

## 経過日数の計算
```php
$created_at = $post->created_at->format('Y-m-d');
$diff = now()->diffInDays($created_at);
if ($diff == 0) {
    echo '今日';
} else if ($diff == 1) {
    echo '昨日';
} else if ($diff == 2) {
    echo 'おととい';
} else {
    echo $created_at;
}
```

## 2つのカラムの値比較をする
```php
# 方法一
$items = Item::whereRaw('stock < safe_stock')->get();
# 方法二
$items = Item::whereColumn('stock', '<','safe_stock')->get();
```

## JSONの値を検索する。$.key名で値を探す
```php
+-----------------------------------------------------------------------------------------------+
| id | shopping_address                                                                         |
+-----------------------------------------------------------------------------------------------+
|  1 | {"name":"山口太郎","zip_code":"5460001","phone":"09012345678","address":"xxxxxxxxxxxxx"}  |
+-----------------------------------------------------------------------------------------------+

$order = Order::where(\DB::raw("json_extract(shopping_address, '$.phone')"), request('phone'))->get();
```

## unique属性で大文字や小文字が区別したい
重複しないデータを登録するためのフィールド属性として「UNIQUE」があります。半角アルファベットで「ABC」のデータがある状態で新たに「abc」を登録しようとすると重複エラーが出ます。

```php
Schema::create('codes', function (Blueprint $table) {
{
    // ......
    $table->string('key')->unique()->charset('utf8')->collation('utf8_bin');
    // ......
});
```
こうすると、ちゃんとアルファベットの大文字と小文字を区別してくれるようになります！

## ソートでNULLを最後に持ってくる
```php
Task::orderByRaw('finished_at IS NULL ASC')
	->orderBy('finished_at')
	->get();
```

## モデルの変更された属性を取得する
```php
$user = App\User::first();
$user->name = "Tanaka";
$user->age = 25;

$user->getChanges();
// 結果 => ["name" => "Tanaka", "age" => 25]
```

## モデルの元の属性値を取得する
```php
$user = App\User::first();
$user->name;                   // 元の値　Taro

$user->name = "Tanaka";         // 変更後の値　Tanaka

$user->getOriginal('name');    // 変更前の値 Taro
```

## Collection へpaginateメソッドを使う方法
```php:App\Providers\AppServiceProvider
use Illuminate\Support\Collection; // 追加
use Illuminate\Pagination\LengthAwarePaginator; // 追加

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // ↓↓↓　追加　↓↓↓
        Collection::macro('paginate', function ($perPage, $total = null, $page = null, $pageName = 'page') {
            $page = $page ?: LengthAwarePaginator::resolveCurrentPage($pageName);
            return new LengthAwarePaginator(
                $this->forPage($page, $perPage),
                $total ?: $this->count(),
                $perPage,
                $page,
                [
                    'path' => LengthAwarePaginator::resolveCurrentPath(),
                    'pageName' => $pageName,
                ]
            );
        });

    }
}
```

## パラメータはIDか、Modelかを判断する

```php
use App\Item;

public function scopeOfProduct($query, $item)
{
    $item = $item instanceof Item ? $item->id : $item;
    return $query->where('item_id', $item);
}
```

## モデルのタイムスタンプだけをアップデートする
ユーザログインした際に、最終ログオン日時を記録したい場合

```php
User::find(1)->touch();
```

## CSRFエラーのチェック
長時間ログインフォームを放置するとログインした際にCSRFエラーが出ます。回避ため以下

```php:App/Exceptions/Handler.php

use Illuminate\Auth\AuthenticationException; // 追加

class Handler extends ExceptionHandler
{
    public function render($request, Exception $exception)
    {
        // ↓↓↓　追加　↓↓↓
        // csrf 例外の場合はログイン画面に戻る
        if ($exception instanceof \Illuminate\Session\TokenMismatchException){
            return back()->withErrors(['セッションタイムアウト']);
        }

        return parent::render($request, $exception);
    }
}
```

## HTTPリクエストインスタンスに配列を追加する
```php
public function login(Request $request)
{
    $request->merge(['status' => 1]);
}
```

## シンプルな日付フィルタ
```php
/*** 日付フィルタ ***/
// 以前の書き方
Post::where('created_at', 'LIKE', '%2017-09-11%')->get();
// 今後の書き方
Post::whereDate('created_at', '2017-09-11')->get();

/*** 年フィルタ ***/
// 以前の書き方
Post::where('created_at', 'LIKE', '%2017-%')->get();
// 今後の書き方
Post::whereYear('created_at', '2017')->get();

/*** 月フィルタ ***/
// 以前の書き方
Post::where('created_at', 'LIKE', '%-09-%')->get();
// 今後の書き方
Post::whereMonth('created_at', '09')->get();

/*** 日フィルタ ***/
// 以前の書き方
Post::where('created_at', 'LIKE', '%-15%')->get();
// 今後の書き方
Post::whereDay('created_at', '15')->get();
```

## 現在表示されているページURL・リファラURLを表示する
```php
// 現在表示されているページURL
url()->current();

// リファラURL
url()->previous();
```

## 配列の空要素を削除する

```php
$data = ['test', 0, false, null, []];

array_filter($data);
// 結果 => ['test']

collect($data)->filter();
// 結果 => ['test']
```


## 指定したキーの全の値を一次元配列に代入する
多次元配列のキーは「ドット」記法で表示することができます。

```php
$data = [
    ['item'=> ['name'=> 'test 1']],
    ['item'=> ['name'=> 'test 2']],
];

collect($data)->pluck('item.name');
// 結果 => ['test 1', 'test 2'],
```

## 簡単なデバッグ出力（一）
複数の変数を表示する事は知りませんでした。

```php
dd($array);

dd($array, $string, 100);
```

## 簡単なデバッグ出力（二）

```php
// 以前の書き方
$users = User::where('name', 'Taro')->get();
dd($users);

// 今後の書き方
$users = User::where('name', 'Taro')->get()->dd();
```

## `@includewhen`ディレクティブを使用する

```php
// Laravel5.3以前の書き方
@if ($user->isAdmin)
    @include('admin.navbar')
@endif

// Laravel5.4以降の書き方
@includeWhen($user->isAdmin, 'admin.navbar')
```

## リダイレクト専用ルーティング
`Route::redirect`はLaravel5.5から使用可能になります。[詳しく](https://readouble.com/laravel/5.5/ja/routing.html#redirect-routes)

```php
// 以前の書き方
Route::get('/user', function () {
    return redirect('/user/profile');
});

// 今後の書き方
Route::redirect('/user', '/user/profile');
```

## 配列からランダムに値を取得する
`array_random`はLaravel5.5から使用可能になります。[詳しく](https://readouble.com/laravel/5.5/ja/helpers.html#method-array-random)

```php
// 以前の書き方
return $names[array_rand($names)];

// 今後の書き方
return array_random($names);
```

## 指定した値を配列中にラップする
`array_wrap`はLaravel5.5から使用可能になります。[詳しく](https://readouble.com/laravel/5.5/ja/helpers.html#method-array-wrap)

```php
// 以前の書き方
$names = is_array($names) ? $names : [$names];

// 今後の書き方
return array_wrap($names);
```

## `->latest()`でデータを簡単に整列する
引数指定してない場合、`created_at`カラムによりソートされます。

```php
// 以前の書き方
$articles = Article::orderBy('published_at', 'desc')->get();

// 今後の書き方
$articles = Article::latest('published_at')->get();
```