ポートフォリオ作成時メモ　part6 ~投稿画面の作成①~

## はじめに
こんにちは、未経験からエンジニア転職を目指しているものです。
オンラインスクールで本格的に学習して6ヶ月目に入りました。PHP,Laravelを学習しポートフォリオを作成中です。ポートフォリオ作成手順及び、エラー内容を中心に記録していこうと思います。今回は投稿画面の作成を行いました。本投稿は流れのメモが重点になっておりますので、削除した部分や追加した部分の詳細は割愛致します。

## ざっくりと投稿画面を作成
ワイヤーフレーム内容とは違いますが、まずは投稿画面が機能する状態まで作成してみました。
- localhost/posts/create
![posts:create.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3846051/948b20f7-dd53-ab32-b7f0-d70d9aafd5d7.png)

投稿結果が反映されるような画面を作成
- locallhost/posts
![posts.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3846051/a5d8bc50-c7e8-6eeb-c38e-7d6524dc33ac.png)![投稿結果反映画面.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3846051/7c0299ed-36d2-3cc7-8c16-c44ff7f23b70.png)

## エラーが発生
- 添付のエラーが発生
![スクリーンショット 2024-11-19 21.11.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3846051/ccbd4503-2916-f37e-1865-a4eadbafb8b2.png)


- エラーの原因
投稿テーブルにcreated_atカラムが存在していなかった。
このカラムはLaravelのlatest()メソッドがデフォルトで使用するため、created_atが無いとエラーになる。

## 対策内容
latest()の代わりに別の並び替え方法を使用
latest()メソッドはcreated_atをデフォルトで使用するが、別のカラムで並び替えを実施
1. コントローラーの修正 PostController.phpのindexメソッドを以下のように修正し解決
```php
public function index()
{
    $posts = Post::orderBy('id', 'desc')->get(); // id カラムで並び替え
    return view('posts.index', compact('posts'));
}
```

## エラー2
実際に投稿機能を使用した時、添付のエラーが発生
![スクリーンショット 2024-11-19 21.10.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3846051/a9494a68-e5cf-8cf1-7c1b-7da7059b3b45.png)

- エラーの原因
Posts テーブルに updated_at や created_at カラムが存在しない発生。
Post::create() メソッドは、デフォルトでタイムスタンプ (updated_at と created_at) を保存しようとするが、それらのカラムがテーブルにないため、このエラーが発生。

## 対策内容
タイムスタンプは今回は不要な為、モデルでタイムスタンプを無効化し解決
app/Models/Post.phpに以下を追加
```php
public $timestamps = false; // タイムスタンプを無効化
```


## 変更した部分
app/Http/Controllers/PostController.php

```php
  <?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class PostController extends Controller
{
    // 投稿一覧を表示
    public function index()
    {
        $posts = Post::orderBy('id', 'desc')->get(); // id カラムで並び替え
        return view('posts.index', compact('posts'));
    }


    // 投稿フォームを表示
    public function create()
    {
        return view('posts.create');
    }

    // 投稿を保存
    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required|string|max:255',
            'content' => 'required|string',
            'preferred_gender' => 'nullable|string',
            'preferred_group_size' => 'nullable|string',
        ]);

        Post::create([
            'title' => $request->title,
            'content' => $request->content,
            'preferred_gender' => $request->preferred_gender,
            'preferred_group_size' => $request->preferred_group_size,
            'user_id' => Auth::id(),
        ]);

        return redirect()->route('posts.index')->with('success', '投稿が完了しました！');
    }
}

```
app/Models/Post.php

```php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    // タイムスタンプを無効化
    public $timestamps = false;

    protected $fillable = [
        'title',
        'content',
        'preferred_gender',
        'preferred_group_size',
        'user_id',
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}
```
database/migrations/2024_11_19_072137_create_posts_table.php
```php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id(); // 自動的にインクリメントされるID
            $table->string('title'); // 投稿タイトル
            $table->text('content'); // 投稿内容
            $table->string('preferred_gender')->nullable(); // 希望性別（nullを許容）
            $table->string('preferred_group_size')->nullable(); // 希望人数（nullを許容）
            $table->foreignId('user_id')->constrained()->onDelete('cascade'); // 投稿者のユーザーID（外部キー制約付き）
            // $table->timestamps(); // created_at と updated_at のタイムスタンプ
        });
    }
    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts'); // posts テーブルを削除
    }
};
```

resources/views/posts/create.blade.php

```php
<x-app-layout>
    <div class="max-w-7xl mx-auto p-6">
        <h2 class="text-2xl font-bold mb-4">新規投稿</h2>
        <form method="POST" action="{{ route('posts.store') }}">
            @csrf
            <div class="mb-4">
                <label for="title" class="block text-sm font-medium text-gray-700">タイトル</label>
                <input type="text" id="title" name="title" required class="block w-full border-gray-300 rounded-md shadow-sm">
            </div>
            <div class="mb-4">
                <label for="content" class="block text-sm font-medium text-gray-700">内容</label>
                <textarea id="content" name="content" rows="4" required class="block w-full border-gray-300 rounded-md shadow-sm"></textarea>
            </div>
            <div class="mb-4">
                <label for="location" class="block text-sm font-medium text-gray-700">活動場所</label>
                <input type="text" id="location" name="location" required class="block w-full border-gray-300 rounded-md shadow-sm">
            </div>
            <div class="mb-4">
                <label for="preferred_gender" class="block text-sm font-medium text-gray-700">希望性別</label>
                <select id="preferred_gender" name="preferred_gender" class="block w-full border-gray-300 rounded-md shadow-sm">
                    <option value="">指定なし</option>
                    <option value="male">男</option>
                    <option value="female">女</option>
                </select>
            </div>
            <div class="mb-4">
                <label for="preferred_group_size" class="block text-sm font-medium text-gray-700">希望人数</label>
                <select id="preferred_group_size" name="preferred_group_size" class="block w-full border-gray-300 rounded-md shadow-sm">
                    <option value="">指定なし</option>
                    <option value="one">一人</option>
                    <option value="two">二人</option>
                    <option value="three">三人</option>
                </select>
            </div>
            <button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-md">投稿する</button>
        </form>
    </div>
</x-app-layout>
```

resources/views/posts/index.blade.php
```php
<x-app-layout>
    <div class="max-w-7xl mx-auto p-6">
        <h2 class="text-2xl font-bold mb-4">投稿一覧</h2>
        @foreach ($posts as $post)
            <div class="border-b py-4">
                <h3 class="text-lg font-semibold">{{ $post->title }}</h3>
                <p class="text-gray-600">{{ $post->content }}</p>
                <p class="text-gray-500">活動場所: {{ $post->location }}</p>
            </div>
        @endforeach
    </div>
</x-app-layout>
```

routes/web.php
```php
use App\Http\Controllers\ProfileController;
use App\Http\Controllers\AuthController; // AuthController をインポート
use App\Http\Controllers\PostController; // PostController　をインポート
use Illuminate\Support\Facades\Route;

// ホーム画面ルート
@@ -25,4 +26,12 @@
Route::post('/login', [AuthController::class, 'showLoginForm'])->name('login');
Route::post('/login', [AuthController::class, 'login']);

// 投稿関連のルート
Route::middleware(['auth'])->group(function () {
    Route::get('/posts', [PostController::class, 'index'])->name('posts.index');
    Route::get('/posts/create', [PostController::class, 'create'])->name('posts.create');
    Route::post('/posts', [PostController::class, 'store'])->name('posts.store');
});
require __DIR__.'/auth.php';
```


## 次回実施内容
- 投稿画面の作成②


最後までご覧いただき本当にありがとうございました！！