laravel学んで2か月で自サービスを開発した話 Part6


皆さんこんばんわ（この記事を投稿したのは真昼間です）

今回も引き続き、プロフィール画面の続きを開発していきます。
![68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f323238313934362f30396630376266302d323930352d643666652d366366632d613837386465373638643834.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/0e6465c6-e1b2-6329-74d7-3f2965a07b49.jpeg)

今までの開発記録はこちらへ
<a href="https://qiita.com/k-hayack884/items/e571138e8bf7f35f7502">胡蝶蘭を捨てるくらいならワイが欲しいので、サービス開発する編</a>
<a href="https://qiita.com/k-hayack884/items/720c7a6300139317deac">公式ドキュメントの言う通り、パッケージをインストールされたら、Inertia.jsが導入されて？？？になった編</a>
<a href="https://qiita.com/k-hayack884/items/dccb9d479bd162144559">マルチログインを作ってみた編</a>
<a href="https://qiita.com/k-hayack884/items/0354c0ddecee58272e46">デザインをtailwindcssに丸投げする編</a>
<a href="https://qiita.com/k-hayack884/items/e4c691041373a97afd97">デザイナーに怒られないために、画像をリサイズする編</a>

今回投稿すること
- s3に画像を保存する
- ユーザーの退会機能

# AWSを使う
開発以前からAWSによるインフラ開発を勉強していたのだが、いきなりは不安だったので今回はHerokuにデプロイをしようと思ったが、
### Herokuではストレージの中身が一定時間で削除されることが判明
ということで、結局AWSに触れることに・・・
## AWSストレージサービスのS3を使います。
**まずはAWSのアカウントを作りましょう！**
リージョンは特にこだわらなければ東京を選びましょう。
※登録にはクレジットカードが必要です。

![pose_kyosyu_boy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/e326ea8a-c33e-73ba-06cb-1e2cb599f9a3.png)
### 先生、未成年の場合どうやってクレジットカードを用意すればいいんですか。
![job_teacher_man.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/0ed487c8-0505-0953-8cf3-55d2cfe0c071.png)
#### お母さんやお父さんにお願いしようね
![gorogoro_neet.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/6fdcd789-3d3a-8bbe-dabc-50e1e3c99805.png)
#### 先生、クレジットカードの審査が落ちるんだけどどう知ればいいですか？
![job_teacher_man.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/668daee4-c4f9-9f0b-014e-6c2095e4adb0.png)
#### 知らん
![スクリーンショット (1655)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/0e0be26a-10af-b2dc-5507-e5436594b7fe.jpeg)
アカウント名をクリックして、セキュリティ認証情報を押します。
![FireShot Capture 003 - IAM Management Console - console.aws.amazon.com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/3a7ebacf-d725-16ec-f9db-686411e51006.png)
新しいアクセスキーの作成をクリックして、アクセスキーとシークレットアクセスキーを入手します
**シークレットアクセスキーはここ以外では見ることができないので、別のファイルに控えるか、ダウンロードしましょう。**

アクセスキーとシークレットアクセスキーを入手したら、上の検索フォームから**s3**と入力しましょう。
S3は無料枠もありますが、基本的には**お金がかかります**
~~学習用にAWSをいじって放置した結果、私は月に４０００円取られました~~
<a href="https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all">無料枠についてはこちらを見てね</a>
### S3の使い方
1. まずはバケットを作成します
1. バケット名を入力する
1. リージョンは特にこだわってなければ東京で
1. オブジェクト所有者のACLは有効に
1. パブリックアクセスをすべてブロックをオフに変更
にします。

バケットが完成すれば、バケットをクリックして、**アクセス許可**のタブをクリック
するとバケットポリシーの欄があるので
```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "(初期設定でIDがある)",
            "Effect": "Allow",
            "Principal": "*",　//変更
            "Action": "s3:*",　//変更
            "Resource": "arn:aws:s3:::(バケット名)/*"　//変更
        }
    ]
}
```
で保存
AWS側の設定はこれで大丈夫だと思う。
## laravel側の動作

laravelでs3にアップロードするには紐づけないといけないので、aws-sdk-phpをインストールする
```
composer require aws/aws-sdk-php
```
ファイルを操作するパッケージである、flysystem-aws-s3-v3もインストール。
```
composer require league/flysystem-aws-s3-v3": "1.0.29
```
インストールに成功すると、config/filesystems.phpが出現するので、
```filesystems.php
'disks' => [
//略
        's3' => [
            'driver' => 's3',
            'key' => env('AWS_ACCESS_KEY_ID'),
            'secret' => env('AWS_SECRET_ACCESS_KEY'),
            'region' => env('AWS_DEFAULT_REGION'),
            'bucket' => env('AWS_BUCKET'),
            'url' => env('AWS_URL'),
            'endpoint' => env('AWS_ENDPOINT'),
            'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', false),
        ],
```
を入力する。
今度はenvファイルの設定
```.env
AWS_ACCESS_KEY_ID=(アクセスキー)
AWS_SECRET_ACCESS_KEY=(シークレットアクセスキー)
AWS_DEFAULT_REGION=ap-northeast-1　（東京の場合）
AWS_BUCKET=（バケット名）
AWS_URL=https://s3-（リージョン）.amazonaws.com/（バケット名）/
```
に設定する。

**これで準備は終了です**

## s3の使い方
```ImageService.php
class ImageService
{
    public static function upload($imageFile, $folderName)
    {
        $fileName=uniqid(rand().'_'); //ランダムな画像名を生成
            $extension=$imageFile->extension(); //拡張子を判別する
            $fileNameToStore=$fileName.'.'.$extension;
            
        $resizedImage=InterventionImage::make($imageFile)->resize(400, 400)->encode();
        // Storage::put('public/'.$folderName.'/'.$fileNameToStore, $resizedImage);
        Storage::disk('s3')->put($folderName.'/'.$fileNameToStore,$resizedImage, 'public'); //追加
        
        return $fileNameToStore;
    }
}
```
前回はstorageフォルダに保存していましたが、この部分を書き換えます
![komaru.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/6bfd913e-62cb-418d-75e4-4f54c8bc244c.png)
### こんなに大掛かりな準備をしたのにコードはこれだけ？
ということできょとんとしている彼をアップロードします
![FireShot Capture 006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/b16f964c-4e94-61d5-97ae-72f7ebe6835b.png)
**ちゃんとS3に画像が入っていますよね**
画像の名前の部分を押してみると、
![スクリーンショット (1657)_LI.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281946/82141abe-272d-3be9-ddc9-c83909123b05.jpeg)
**本当に連携できている・・・**

ということで、S3とlaravelを連携させて画像をアップロードすることができました！

## 退会機能
退会の処理をすることは、ユーザーテーブルからユーザーを削除するわけではない。
ECサイトなら、ユーザーが出した商品など、ユーザーテーブルと商品テーブルがつながっていることが多いだろう。
そのため、外部キーを使ってリレーションをするはずなので、先に退会ユーザーが出している商品を削除してから、ユーザーを削除するという流れになる。
しかし、商品テーブルを削除した後、何らかの不具合で、ユーザーを削除できていなかった場合、商品テーブルは削除されただけになる。
この場合は、一見問題なさそうに見えるかもしれないが、**在庫やお金**をやり取りするサイトではそれが消されたのに処理がされていないことになり、クレームにつながる。
例えば、AがBに金を振り込むとAの残高が減って、Bの残高が増える
しかし、Aが振り込んだ後にシステムが止まれば、Aはただお金を損しただけであって、Bにはお金が増えない。

なので、Aの残高を減らす、Bの残高を増やすという工程をひとまとめにして、全部できたら処理をする、途中でエラーが発生すれば、処理を止めて実行前に戻す**トランザクション**の処理を書く必要がある。
長くなりましたが、コードを書きます。

```ProfileController.php
 public function destroy(Request $request,$id)
    {
//略   
        try {
            DB::transaction(function () use ($id) {
                Product::select('id', ) 
                ->where('user_id', Auth::id())->delete();
                //先にそのユーザーが出品した商品を商品テーブルから削除（のちに実装します）
                User::select('id', 'name')
                ->where('id', Auth::id())->delete();
            }, 2); //試行する回数
        } catch (Throwable $e) {
            Log::error($e);
            throw $e;
        }
        Auth::guard('users')->logout();　//退会すれば、ログアウト処理がいります
    }
```
これでＯＫ

## 画像を表示する
view側の設定もしていきましょう　edit.blade.phpはadminのものを使いまわして調整
```show.blade.php
<x-app-layout>
    <x-slot name="header">
        <h2 class="font-semibold text-xl text-gray-800 leading-tight">
            ユーザー情報確認
        </h2>
    </x-slot>
    <div class="py-12">
        <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
            <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    <section class="text-gray-600 body-font">
                        <div class="container mx-auto flex px-5 py-24 md:flex-row flex-col items-center">
                            <div class="lg:max-w-lg lg:w-full md:w-1/2 w-5/6 mb-10 md:mb-0">
                                <x-user-icon :filename="$userProfile->img" />
                            </div>
                            <div
                                class="lg:flex-grow md:w-1/2 lg:pl-24 md:pl-16 flex flex-col md:items-start md:text-left items-center text-center">
                                <h1 class="title-font sm:text-4xl text-3xl mb-4 font-medium text-gray-900">ユーザー情報</h1>
                                <ul>
                                    <li class="mb-8 leading-relaxed">ユーザー名: {{ $userProfile->name }}</li>

                                    @if ($userProfile->id === auth()->user()->id)
                                        <li class="mb-8 leading-relaxed">メールアドレス: {{ $userProfile->email }}</li>
                                    @endif
                                    <li class="mb-8 leading-relaxed">出身地: {{ $userProfile->prefecture }}</li>
                                    <li class="mb-8 leading-relaxed">自己紹介: {{ $userProfile->comment }}</li>
                                    <ul>
                                        <div class="flex flex-col md:flex-row">
                                            @if ($userProfile->id === auth()->user()->id)
                                                <button type="button"
                                                    onclick="location.href='{{ route('user.profiles.edit', ['profile' => $userProfile->id]) }}'"
                                                    class=" mx-auto text-white bg-indigo-500 border-0 py-2 px-8 focus:outline-none hover:bg-gray-600 rounded text-lg mx-4 mb-4">編集</button>
                                            @endif
                                            <button type="button"
                                                onclick="location.href='{{ route('user.dashboard') }}'"
                                                class=" mx-auto text-white bg-gray-500 border-0 py-2 px-8 focus:outline-none hover:bg-gray-600 rounded text-lg mx-4 mb-20 md:mb-4">戻る</button>
                                            @if ($userProfile->id === auth()->user()->id)
                                                <form id="delete_{{ $userProfile->id }}" method="post"
                                                    action="{{ route('user.profiles.destroy', ['profile' => $userProfile->id]) }}">
                                                    @csrf
                                                    <a href="#" data-id="{{ $userProfile->id }}"
                                                        onclick="deletePost(this)"
                                                        class=" text-white bg-red-500 border-0 py-2 px-20 text-center focus:outline-none hover:bg-red-600 rounded text-lg mx-4 mt-8 md:hidden">
                                                        削除</a>
                                                </form>
                                            @endif
                                        </div>
                                    </ul>
                                </ul>
                            </div>
                        </div>
                        @if ($userProfile->id === auth()->user()->id)
                            <div class="flex justify-end">
                                <form id="delete_{{ $userProfile->id }}" method="post"
                                    action="{{ route('user.profiles.destroy', ['profile' => $userProfile->id]) }}">
                                    @csrf
                                    <a href="#" data-id="{{ $userProfile->id }}" onclick="deletePost(this)"
                                        class=" text-white bg-red-500 border-0 py-2 px-8 mx-auto focus:outline-none hover:bg-red-600 rounded text-lg mx-4 mt-10 hidden md:block">
                                        削除</a>
                                </form>
                            </div>
                        @endif
                    </section>
                </div>
            </div>
        </div>
    </div>
    <script src="{{ mix('/js/app.js') }}"></script>
    <script>
        'use strict';

        function deletePost(e) {
            if (confirm('本当に退会してもよろしいですか？')) {
                document.getElementById('delete_' + e.dataset.id).submit();
            }
        }
    </script>
</x-app-layout>

```
特に画像の表示に使う部分が```<x-user-icon :filename="$userProfile->img" />```の部分で、
いちいち、bladeごとに画像表示のコードを書くのは面倒くさいので、コンポーネントを挿入しています。

コンポーネントについてはこちら
<a href="https://biz.addisteria.com/laravel_breeze_component-ex/#toc3">Laravel Breeze：Componentの使い方を図入りで分かりやすく説明しました</a>

ざっくりいうと、コントローラーから取得したimgのテキストをコンポーネントファイルに渡します。
コンポーネントの中身はresources/views/componentsにあるので、新しくuser-icon.blade.phpを新規作成
```user-icon.blade.php
<div>
    @if(empty($filename))
    <img src="{{asset('images/no_image.png')}}">
    @else
    <img src="{{asset('https://(バケット名).s3.ap-northeast-1.amazonaws.com/profiles/'.$filename)}}">
    @endif
</div>
```
初期設定や、ユーザーが画像を設定しない場合はnoimageの画像を使います。
これでS3にある画像ファイルを読み込んでくれます。

## 終わりに
これでプロフィール画面の表示や退会機能を作りました。
AWSの機能はいろいろあるのでいじってみたいですね~~適当にいじくったら月4000円も取られました~~
いよいよ、商品部分の作成ですね!