実務経験に入ってから知ったLaravel/PHPの実装法まとめ


実務に入る前は知らなかったor使わなかった機能が多数用いられていた為、今後のために残していきます。

何かの参考になればいいな

一つ一つを細かに解説したものではなく、
あくまでもざっくり「へーこんなのあるんだ」となるための
まとめの記事ですのでお手柔らかに。

一貫性皆無かもしれない

間違い箇所あればガシガシ指摘お願いしますm(__)m


__追伸__
タイトルに「Laravel/PHPの実装法」て書いてるけどLaravelのことしか書いてないやんてそろそろつっこまれそう
`~PHP will return(マーベル風)~`

#実務~現在まで

まず自分のことをざっくり。
実務に入ってから本記事投稿時点までで経験6ヶ月目のぺーぺーです。

携わった(または現在も携わっている)プロジェクトにて

- Laravel/PHP/jQuery
自身が初めて参画した案件。
既にキックオフからは1年以上経過しており、中身のややこしさに幾度も絶望を味わいつつ、お手伝い。
めちゃくちゃ勉強になる。。

- Laravel/LINEAPIでの独自API開発
ほとんどの実装を自分に任せてもらえた案件で、これもかなり勉強になりました。
APIってすげー！！！(語彙力)

- Smarty/PHPでの保守・改修案件
「なるほど！わからん。」を体感
しかし9割自分に任せてもらえたおかげでほとんど知らなかった生のPHPの書き方とかもちょこっと理解できた。(氷山の一角なのである)

※本記事は主に1つ目の案件で得た知見のまとめとなります。

#参考までに
自身が実務に入る際は、Laravelを用いた簡単なCRUD開発ができる程度。
「SSHとSSLって何が違うん?」て聞かれて
「えーーーっとですね.....
　最後がHかLかの違いです」となるレベル

フロントは最低限書けるくらい。(JavaScript頑張ろう。。)
CSSは大っ嫌いなのでコーディングできる人尊敬します

では早速 >>>

#自作ヘルパー関数
Laravelには複数のグローバルなヘルパ関数が数多く存在しますが、
どこでも使える〇〇な関数あったらいいのになーと思ったとき簡単に作れてしまいます。

__例：金額を引数に税込み価格を計算する関数__

`app/helper.php`を作成します。
(どのディレクトリでもいいです)

```helper.php
<?php

if (! function_exists('taxIncluded')) {
    function taxIncluded($price) {
        $tax = 10.0;
        return $price + floor($price * $tax / 100);
    }
}
```

>せせ、先輩が作ったやつほとんどパクってる。

__オートロードする__

`autoload.files`を下記の様に編集します。

```composer.json
"autoload": {
        "files": [
            "app/helpers.php"
        ]
    },
```

オートロードとは?
"https://laraweb.net/surrounding/1642/"

引用：
>PHPによるオートロード機能とはファイルを自動で読み込む仕組みのことです。
この機能を使うことでPHPファイルの冒頭に require を書きまくることはなくなりました。
オートロード機能を使用するには composer を使います。
composer を使用してライブラリをインストールするとvendor/autoload.phpというファイルが生成されます。
このファイルがオートロードの実体ファイルです。
このファイルを一度だけrequireすることで、vendor配下のライブラリをすべて自動的にロードしてくれます。
また、プロジェクトの直下にあるcomposer.jsonでオートロードするファイルを追加することもできます。

↑いつもお世話になってます。

__更新__

```
$ composer dump-autoload
```

あとは使いたいところで

```index.blade.php
--略
   <p>¥{{ number_format(taxIncluded($price)) }}(税込)</p>
--略
```

どこでも使えます。


#Eloquentモデルへのイベントをフックして何かする

__例：Usersテーブルに更新があり、emailカラムの値に変化があった際メールを通知する__

__イベントクラスを作成する__

```
$ php artisan make:event UserUpdated
```

__行いたい処理をイベントクラスに記述する__

```App\Events\UserUpdated.php

namespace App\Events;

use App\Models\User;

class UserUpdated
{
  use Dispatchable, InteractsWithSockets, SerializesModels;

  public function __construct(User $user)
   {
     if($user->wasChanged('email')) {
       $this->sendMail($user);
     }
   }

}
```


__モデルに`$dispatchesEventsプロパティ`を定義する__

```App\User.php
use App\Events\UserUpdated;

protected $dispatchesEvents = [
  'updated_at' => UserUpdated::class
];
```



こんな感じで簡単にフックできます。
ただイベントクラス内の処理で`Eloquent\Model`経由で更新やら何やらすると再帰するので要注意とのこと。

いやー便利だけど難しい。

`2021/04/20追記`
一部ロジック間違ってたので書き換えてます。
'updated_at' => UserUpdated::class
こことか。(updated_atの箇所emailにしてた)

__Note: Eloquentの複数モデル更新・削除を行う場合、影響を受けるモデルに対するsaved、updated、deletingモデルイベントは発行されません。その理由は複数モデル更新・削除を行う時、実際にモデルが取得されるわけではないからです。__

あくまでもモデルイベントが発行される際にのみ検知するので使いどころ見誤らない様にですね。


#自作artisanコマンドを作る

画面作るまでも無いけど〇〇な処理ササッとできやんかなーというあなたに朗報です

まずコマンド一覧確認

```
$ php artisan list
```

ご親切なことに僕の嫌いな英語で各コマンドの説明がずらーっと

自作コマンド作成！！

```
$ php artisan make:command SampleCommand
```

`app\Console\Command\SampleCommand`が出来ました。
さて、ここになんやかんや書いていきます。

__名前を変更__

```SampleCommand.php
    //ここを
    protected $signature = 'command:name';

    //こうじゃ
    protected $signature = 'email:send {user_id}';
```

{}で引数の指定ができます。
また「：」をつけることで一覧でグルーピングして表示されます。
(無くでもOK)
また、↑の場合は必須引数のため、引数抜きでコマンド実行すると怒られます

任意の引数の場合はこんな感じに。

```SampleCommand.php
    protected $signature = 'email:send {user_id?}';

    //指定されなかった場合のデフォルト値を指定
    protected $signature = 'email:send {user_id=1}';
```

引数に名前指定したり、オプションの指定もしたりできますが、
長くなるので詳しくはGoogle先生に尋ねてください。(投げやり)

__コマンドの説明__

```SampleCommand.php
    protected $description = 'メール送信する'
```

何をするコマンドか書いておきます。

__引数取得__

```SampleCommand.php
    public function handle()
    {
        $userId = $this->argument("user_id");
        $this->sendMail($userId);
    }
```


あとは処理書いていけばokです

#認可

忍者ではありません、認可です
なんじゃそりゃって感じですよね。

要約するとアクセス制限です！！(はじめからそう言うてよーと思いますね)

Laravelにはどうやら二種類、「ゲート」「ポリシー」なるものがあるみたいです。

引用
>ゲートとポリシーは、ルートとコントローラのようなものであると考えてください。ゲートはシンプルな、クロージャベースのアプローチを認可に対してとっています。一方のコントローラに似ているポリシーとは、特定のモデルやリソースに対するロジックをまとめたものです。最初にゲートを説明し、次にポリシーを確認しましょう。

>アプリケーション構築時にゲートだけを使用するか、それともポリシーだけを使用するかを決める必要はありません。ほとんどのアプリケーションでゲートとポリシーは混在して使われますが、それで正しいのです。管理者のダッシュボードのように、モデルやリソースとは関連しないアクションに対し、ゲートは主に適用されます。それに対し、ポリシーは特定のモデルやリソースに対するアクションを認可したい場合に、使用する必要があります。

参照：https://readouble.com/laravel/6.x/ja/authorization.html#via-blade-templates

初めて↑見た時は何を言うてるん??てなりましたね
(今でもなるのは内緒)
Gateは特定のモデルに関連しないアクションに対して、
Policyは特定のモデルに関連したアクション(作成 / 更新　/ 削除等)に対しての認可と解釈しています。


アクセス制限は前述したものなくても実現できます。

__例：「記事を投稿したユーザーのみ削除が可能」__

```post/show.blade.php
@if(Auth::user()->id === $post->user_id)
  <a href="{ route('post.destroy') }">
   削除
  </a>
@endif
```

こんな感じに。
(実際はコントローラー側での制限も必要になりますがいったん置いときましょう)
上記の場合は、「特定のモデル(Post)に対してアクション(閲覧)があった時削除を表示するか否か」って感じなのでPolicyが使えそうですね

###Gateから解説
おさらい：
「Gateは特定のモデルに関連しないアクション」に関しての認可です。

今回は「__管理者権限を持っている場合は管理画面にアクセスができる__」を実現してみます。

####Gateの登録

```App\Providers\AuthServiceProvider.php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    
    public function boot()
    {
        $this->registerPolicies();
        
        Gate::define('isAdmin',function($user){
           //trueかfalseを返します
           return $user->role === "admin";
        });
    }
}
```

第一引数は常にユーザーインスタンスになります。
上記の場合管理者でない場合はfalseが返されます。

```App\Http\Controller\ManageScreenController.php
public function index() {
   Gate::authorize('isAdmin');
   
   $notices = Notice::All();

   return view('notices.index',compact('notices'));
}
```

管理者権限を持たないユーザーが上記にアクセスした場合は403エラーになります。

###Policy
「Policyは特定のモデルに関連したアクション」でしたね。

前述の__「記事を投稿したユーザーのみ削除が可能」__を例にしましょう。

####Policyの作成

```
$ php artisan make:policy PostPolicy
```

```App\Policies\PostPolicies.php
<?php

namespace App\Policies;

use App\Models\User;
use App\Models\Post;
use Illuminate\Auth\Access\HandlesAuthorization;

class PostPolicy
{
    use HandlesAuthorization;

    public function destory(User $user, Post $post)
    {
        return $user->id = $post->user_id;
    }
}

```

####AuthServiceProvider.phpに登録します

```App\Providers\AuthServiceProvider.php
   //略
   protected $policies = [
    'App\Post' => 'App\Policies\PostPolicy'
   ];
```


__コントローラヘルパによる認可__

```App\Http\Controller\PostController

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Post;
use Illuminate\Http\Request;

public function destroy(Request $req, Post $post)
{
  this->authorize('destroy', $post);
}

```

認可されない場合は403エラーになります。

__Bladeテンプレートによる認可__

```post/show.blade.php
@can('destroy', $post)
  <a href="{ route('post.destroy') }">
   削除
  </a>
@endif
```

canを使ってこんな格好で制限できます。


上記でご紹介したのはごくごく一部ですので、もう少し詳しく知りたい方は

公式
https://readouble.com/laravel/6.x/ja/authorization.html#via-blade-templates

個人的にすごくわかりやすい
https://reffect.co.jp/laravel/laravel-gate-policy-understand#Policy

ここら参考にしてみてください。

#メンテナンスモード

Laravelでは簡単にメンテナンスモードにできる様です。

やり方はものすごく簡単

```
$ php artisan down
```

これをするだけ！

すると...
![スクリーンショット 2021-04-30 15.22.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/985819/f94f77fb-65e6-ec8f-a9e5-9cf2d85e243a.png)

これは
`vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/views/503.blade.php`
ここを見ている様です。

「いやいや兄ちゃん、こちとらこんな質素な画面やったら嫌やでっ」
「俺だけはせめて普通の画面みれるようにしたいで」
ていう声が聞こえてきました。
「ほなおじちゃん、IP制限してええ感じのページ作りましょうや！！」

##まずIP制限から。

__1.ミドルウェアの作成__

メンテナンスモードはミドルウェアで行ってます。
`vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php`
こいつ！！

------------------------------
※Laravel8系では同ディレクトリ内の`PreventRequestsDuringMaintenance.php`を継承してたり、コマンドのオプションとかもだいぶ変わってました。
>参照：
https://github.com/laravel/framework/blob/8.x/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php
>
https://readouble.com/laravel/8.x/ja/configuration.html

下記は7系を軸に話しているのでご注意。

------------------------------




さて、カスタマイズをするわけですが、vendor以下は編集しないのがルールのようなのでミドルウェア作ります。

```
$ php artisan make:middleware CheckForMaintenanceMode.php
```

`App\Http\Middleware\CheckForMaintenanceMode.php`
が作成されます。

```App\Http\Middleware\CheckForMaintenanceMode.php
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
use Symfony\Component\HttpKernel\Exception\HttpException;

class CheckForMaintenanceMode {

    protected $request;
    protected $app;

    public function __construct(Application $app, Request $request)
    {
        $this->app = $app;
        $this->request = $request;
    }

    public function handle($request, Closure $next)
    {
        //許可するIP
        $allow_ip = ['xxx'];
        if ($this->app->isDownForMaintenance() &&
            !in_array($this->request->getClientIp(), $allow_ip))
        {
            throw new HttpException(503);
        }

        return $next($request);
    }

}
```

こんな感じに編集。

__2.カーネルを登録__

```app\Http\Karnel.php
protected $middleware = [
        \App\Http\Middleware\CheckForMaintenanceMode::class,
    ];
```

__3.メンテナンスモード発動__

```
$ php artisan down
```

検証環境はDockerでしたので、webコンテナの内部IPが接続元IPを定義しておくと通常のページにアクセスできました！


##Viewをアレンジ

公式より引用：
`resources/views/errors/503.blade.phpを独自に定義することにより、メンテナンスモードのデフォルトテンプレートをカスタマイズできます。`

__作る__

```resources/views/errors/503.blade.php
<!DOCTYPE html>
<html>
<head>
    <title>メンテナンス中</title>

    <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">

</head>
<body>
<div class="container">
    <div class="content">
        <h1>メンテナンスしてるで</h1>
    </div>
</div>
</body>
</html>

```

アクセス！
![スクリーンショット 2021-04-30 17.49.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/985819/7c01cfc4-230e-ecf6-3553-6319ed5ea490.png)

なんということでしょう。さらに質素になりました

終了！！


今後随時更新予定
長くなってきたしそろそろ区切るかもしれません