Webアプリ開発　記事の新規登録編

# 初めに
webアプリ開発ができたので、どんな感じで作成したのかをまとめたいと思います。
今回はマイページ編集についてです。

# 開発環境
macOS Sonoma 14.4.1
CentoOS Stream X_86_64
Apache/2.4.57
PHP 8.3.6 
mysql  Ver 8.0.36 
phpMyAdmin 5.2.1
composer version 2.7.2
Laravel Installer 5.7.1
Laravel Framework 11.0.5

# ソースコード

https://github.com/opatu07/portfolio

# 考えたこと
## 記事の新規登録
### 実装事例

![author.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/190d04f5-d074-edb8-4c85-804d36f90518.png)

権限があるユーザーには投稿一覧ボタンが出てきます。
押すと

![dashboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/ebe55bba-44f4-8849-c0d9-3ceda861da1b.png)

記事一覧が表示されます

![dashboardDropdown.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/510d9376-3c95-72f0-1302-db4e807ba75d.png)

ダッシュボードを押すと「新しい記事を作る」ボタンが出てきます。

![createArticle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/90cf1bd0-d952-0d00-4dba-3c5d0508eeb7.png)

新規登録画面でタイトル、スラッグ、サムネイル、要約、内容、カテゴリーを指定すると記事が新しくできます

### ビュー

```create.blade.php
<x-layout>
    <x-setting heading="新規投稿">
        <form method="POST" action="/admin/posts" enctype="multipart/form-data">
            @csrf

            <div class="py-2">
                <x-form.label name="タイトル"/>  
                    <input class="border border-gray-200 p-2 w-full rounded"
                        name="title"
                        id="title"
                    >
                <x-form.error name="title"/>
            </div>      
            <div class="py-2">
                <x-form.label name="スラッグ"/>  
                    <input class="border border-gray-200 p-2 w-full rounded"
                        name="slug"
                        id="slug"
                    >
                <x-form.error name="slug"/>
            </div>
            <div class="py-2">
                <x-form.label name="サムネイル"/>  
                    <input class="border border-gray-200 p-2 w-full rounded"
                        name="thumbnail"
                        id="thumbnail"
                        type="file"
                    >
                <x-form.error name="thumbnail"/>
            </div>
            <div class="py-2">
                <x-form.label name="要約"/>  
                    <textarea class="border border-gray-200 p-2 w-full rounded"
                        name="excerpt"
                        id="excerpt"
                    ></textarea>
                <x-form.error name="excerpt"/>
            </div>
            <div class="py-2">
                <x-form.label name="内容"/>  
                    <textarea class="border border-gray-200 p-2 w-full rounded"
                        name="body"
                        id="body"
                    ></textarea>
                <x-form.error name="body"/>
            </div>

            <x-form.field>
                <x-form.label name="カテゴリー"/>

                <select name="category_id" id="category_id" required>
                    @foreach (\App\Models\Category::all() as $category)
                        <option
                            value="{{ $category->id }}"
                            {{ old('category_id') == $category->id ? 'selected' : '' }}
                        >{{ ucwords($category->name) }}</option>
                    @endforeach
                </select>

                <x-form.error name="category"/>
            </x-form.field>

            <x-form.button>投稿</x-form.button>
        </form>
    </x-setting>
</x-layout>
```
記事の新規登録画面はフォームのラベルとエラーをコンポーネント化して左上のドロップダウンボタンもコンポーネントしました

### コントローラ
```AdminPostController.php
<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;


class AdminPostController extends Controller
{
    public function index()
    {
        return view('admin.posts.index', [
            'posts' => Post::paginate(7)
        ]);
    }

    public function create()
    {
        return view('admin.posts.create');
    }

    
    public function store()
    {
        Post::create(array_merge($this->validatePost(), [
            'user_id' => request()->user()->id,
            'thumbnail' => request()->file('thumbnail')->store('thumbnails') 
        ]));

        return redirect('/')->with('success', '記事を作成しました。');
    }

    protected function validatePost(?Post $post = null): array
    {
        $post ??= new Post();

        return request()->validate([
            'title' => 'required|max:20',
            'thumbnail' => $post->exists ? ['image'] : ['image'],
            'slug' => ['required', Rule::unique('posts', 'slug')->ignore($post), 'alpha_dash' , 'lowercase', 'max:10'],
            'excerpt' => 'required|max:40',
            'body' => 'required|max:500',
            'category_id' => ['required', Rule::exists('categories', 'id')]
        ]);

    }
}
```
indexメソッドは投稿記事一覧画面の表示に使います。7記事ごとにページネーションされます

createメソッドは新規登録画面の表示をしています。

storeメソッドではPostモデルでカラムの登録をします。
array_mergeメソッドでvalidatePostメソッドのバリデーションと連結させています

### ルート
```web.php
    Route::middleware('can:admin')->group(function () {
    Route::resource('admin/posts', AdminPostController::class)->except('show','destroy');
});
```
記事のcrudの処理のルートが共通化できそうだったのでグループ化しておきました。
showはホーム画面で作っているので除きました。
destroyはajax処理で実装したかったので別で作成しました。

### レスポンシブ対応
使われるスマホがiPhone8以上の画面サイズなのでiPhone8に対応させました。
![res_create_article.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3682713/d0b853bc-5296-ab30-221f-7ca07b398da5.gif)


# 改善していきたいこと
ビューで書いたcreate.blade.phpのactionのルートをベタ書きしているのでweb.phpのルートに置き換えられるのなら置き換えたいと思います。
カテゴリーを登録する画面がないので、データベースに直接登録するしか方法がありません。
記事を登録する際にカテゴリーも一緒に登録できるように変更していきたいと思います。
# 最後に
記事の新規登録画面ではあまり改善点が発見できなかったので、他の機能の修正や新規機能の追加に時間を当てたいと思います！