PROGRAMMING

【シンプル解説】Laravel9でゴミ箱付きToDoリストアプリを作る

ごm今回はToDoリストを作ります。

リストページの他にゴミ箱ページを使って、リスト→ゴミ箱へ、ゴミ箱→リストへを移動させるようにします。

サーバーとの通信はAjaxを使い、非同期で行う感じです。

ソースコード(Github)

2つの方法用意してます。Laravelのインストールからやる場合は、「一から作成」、完成版を見たい方は「完成版」をクローンしてみてください。

一から作成

git clone <https://github.com/sho55/todo-app-laravel9.git>

完成版

git clone -b created_demo https://github.com/sho55/todo-app-laravel9.git

開発環境

Laravel 9.*

MySQL8.0

PHP8.1

jQuery(Ajax) 3.6

dockerファイルとdocker-compose.yml作成

ファイル構成は以下の通り。

docker-config
├── mysql
│   ├── data //データの保管用
│   └── my.cnf
├── nginx
│   ├── Dockerfile
│   └── default.conf
└── php
    ├── Dockerfile
    └── php.ini

順に解説していきます。

PHP

docker-config/php/php.ini

[Date]
date.timezone = "Asia/Tokyo"
[xdebug]
xdebug.remote_enable = On
xdebug.remote_port = 9001
xdebug.remote_autostart = On
xdebug.remote_host = 192.168.99.1
xdebug.profiler_output_dir = "/tmp"
xdebug.max_nesting_level= 1000
xdebug.idekey = "PHPSTORM"

docker-config/php/Dockerfile

FROM php:8.1-fpm
WORKDIR /var/www
ADD . /var/www
RUN chown -R www-data:www-data /var/www

COPY php.ini /usr/local/etc/php/

# install composer
RUN cd /usr/bin && curl -s <http://getcomposer.org/installer> | php && ln -s /usr/bin/composer.phar /usr/bin/composer

# install packages
RUN apt-get update && \\
  apt-get -y install --no-install-recommends npm libzip-dev libicu-dev libonig-dev libmcrypt-dev git unzip vim mariadb-client curl gnupg openssl && \\
    apt-get clean && \\
    rm -rf /var/lib/apt/lists/* && \\
  docker-php-ext-install intl pdo_mysql zip bcmath mbstring mysqli

# install stable node and latest npm
RUN curl -sL <https://deb.nodesource.com/setup_10.x> | bash
RUN apt-get install -y nodejs
# RUN apt-get install -y npm
RUN npm install -g n
RUN n stable
RUN npm update -g npm

docker-config/php/php.ini

[Date]
date.timezone = "Asia/Tokyo"

[xdebug]
xdebug.remote_enable = On
xdebug.remote_port = 9001
xdebug.remote_autostart = On
xdebug.remote_host = 192.168.99.1
xdebug.profiler_output_dir = "/tmp"
xdebug.max_nesting_level= 1000
xdebug.idekey = "PHPSTORM"
[mysqlnd]
mysqlnd.collect_memory_statistics = on

Nginx

docker-config/nginx/default.conf

server {
    listen 80;

    root /var/www/public;
    index index.php index.html;
    allow all;

    access_log /var/log/nginx/ssl-access.log;
    error_log  /var/log/nginx/ssl-error.log;

    location / { 
        root /var/www/public;
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \\.php$ {
        try_files $uri =404;
        
        fastcgi_split_path_info ^(.+\\.php)(/.+)$;
        fastcgi_pass web:9000;
        fastcgi_index index.php;

        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;

        # CORS start
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
        add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
        add_header Access-Control-Allow-Credentials true;
        # CORS end
    }   
}

docker-config/nginx/Dockerfile

FROM alpine:3.6

RUN apk update && \\
    apk add --no-cache nginx
RUN mkdir -p /run/nginx

# フォアグラウンドでnginx実行
CMD nginx -g "daemon off;"

MySQL

docker-config/mysql/my.cnf

# MySQLサーバーへの設定
[mysqld]
# 文字コード/照合順序の設定
character-set-server = utf8mb4
collation-server = utf8mb4_bin

# タイムゾーンの設定
default-time-zone = 'Asia/Tokyo'
log_timestamps = SYSTEM

# デフォルト認証プラグインの設定
default-authentication-plugin = mysql_native_password

# エラーログの設定
# log-error = /var/log/mysql/mysql-error.log

# スロークエリログの設定
slow_query_log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log
long_query_time = 5.0
log_queries_not_using_indexes = 0

# 実行ログの設定
general_log = 1
general_log_file = /var/log/mysql/mysql-query.log

# mysqlオプションの設定
[mysql]
# 文字コードの設定
default-character-set = utf8mb4

# mysqlクライアントツールの設定
[client]
# 文字コードの設定
default-character-set = utf8mb4

docker-compose.yml

version: '3' 

services:
    
  web: 
    build: ./docker-config/php
    volumes:
      - ./src/:/var/www/
    working_dir: /var/www/
    depends_on:
      - mysql

  nginx:
    image: nginx
    build: ./docker-config/nginx
    ports:
      - "81:80"
    volumes:
      - ./src/:/var/www
      - ./docker-config/nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - web

  mysql:
    image: mysql:8.0
    ports:
      - 3316:3306
    environment:
      MYSQL_DATABASE: mainsys1
      MYSQL_ROOT_USER: root
      MYSQL_ROOT_PASSWORD: root
      MYSQL_USER: my_user
      MYSQL_PASSWORD: my_user
      TZ: 'Asia/Tokyo'
      
    volumes:
      - ./docker-config/mysql/data:/var/lib/mysql
      - ./docker-config/mysql/my.cnf:/etc/mysql/conf.d/my.cnf

    depends_on:
      - mysql-volume

  mysql-volume:
    image: busybox
    volumes:
      - ./docker-config/mysql:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin/phpmyadmin
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=mysql
      - PMA_USER=root
      - PMA_PASSWORD=root
    links:
      - mysql
    ports:
      - 8081:80
    volumes:
      - /sessions

Dockerの立ち上げ

Dockerを立ち上げていきます。 「-d」はデーモンを使うということで、裏側で動かすために必要です。

# docker-compose up -d 
//コンテナに入る # docker exec -it php9-laravel8-mysql8_web_1 bash

Laravelインストール

今回はLaravel9(2022年5月時点で最新)を導入します。

今インストールしたらversion9の指定をしてなくてもいいんですが、一応。

composer create-project laravel/laravel:^9.0 src
cd src

環境変数を修正します。 回はDBのところだけでOKです。

.env

APP_NAME=ToDoApp
APP_ENV=local
APP_KEY=
APP_DEBUG=true
APP_URL=http://localhost

LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=mainsys1
DB_USERNAME=root
DB_PASSWORD=root

BROADCAST_DRIVER=log
CACHE_DRIVER=file
FILESYSTEM_DISK=local
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120

MEMCACHED_HOST=127.0.0.1

REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379

MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"

AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false

PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1

MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

マイグレーションファイル作成

DBのテーブル、カラムを用意します。

php artisan make:migration create_posts_table --create=posts

ファイル構成

create_posts_table


create_posts_table

public function up() { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('text'); $table->smallInteger('complete_flag'); $table->softDeletes($column = 'deleted_at', $precision = 0); $table->timestamps(); }); }

マイグレーションしていきます。

php artisan migrate

修正ファイル

config/app.php

'timezone' => 'UTC',
↓
'timezone' => 'Asia/Tokyo',

コントローラーとモデル作成

コントローラーとモデルを作っていきます。

今回はPostという名前にしております。

php artisan make:controller PostController --model=Post

モデルの修正

論理削除を行うので、SoftDeletesをトレイトしております。

Post.php


<?php

namespace App\\Models;

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

class Post extends Model
{
    use HasFactory;
    use SoftDeletes;

    protected $table = 'posts';

    protected $fillable = [
        'text',
'complete_flag' ]; }

パッケージをインストール

今回用意するのは以下の通りです。

・Laravel UI : Bootstrapを追加するため

・Bootstrap : デザインを簡単に作るフレームワーク

・jQuery : サーバーと非同期通信をするAjaxの元

・FontAwesome : ゴミ箱マークなどのアイコンを使う

順に入れていきます。

Laravel UI

//Laravel UIパッケージ
$ composer require laravel/ui

BootStrap

$ php artisan ui bootstrap --auth

jQuery

$ npm install jquery --save-dev

fontawesome

$ npm install @fortawesome/fontawesome-free



npmのリストを確認する

//確認
/var/www# npm ls
www@ /var/www
+-- @fortawesome/fontawesome-free@6.1.1
+-- @popperjs/core@2.11.5
+-- autoprefixer@10.4.5
+-- axios@0.25.0
+-- bootstrap@5.1.3
+-- jquery@3.6.0 //追加されている
+-- laravel-mix@6.0.43
+-- lodash@4.17.21
+-- postcss@8.4.13
+-- resolve-url-loader@5.0.0
+-- sass-loader@11.1.1
`-- sass@1.51.0

jQueryをLaravelで使えるようにする

このままでは使えないので、以下を追加しましょう。

resources/js/bootstrap.js

~
window.$ = window.jQuery = require('jquery')

Font awesomeも使えるようにする

以下を追加します。

resources/sass/app.scss

// Font Awesome
@import '~@fortawesome/fontawesome-free/scss/fontawesome';
@import '~@fortawesome/fontawesome-free/scss/regular';
@import '~@fortawesome/fontawesome-free/scss/solid';
@import '~@fortawesome/fontawesome-free/scss/brands';

npmをコンパイル

以下を叩くことで、jQueryやFont awesomeが正常に使えるようになります。

npm run dev

※開発をする時は、いちいちコマンドを叩くのが面倒なので、以下を使って自動でコンパイル処理をしていくのが効率的です。

npm run watch

npmでのWarnig対応

僕がコンパイルした時に以下の状態になりました。

1 WARNING in child compilations (Use 'stats.children: true' resp. '--stats-children' for more details)

というものが出まして、しらべたところ以下の対処をするみたいです。

/webpack.mix.js

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css')

~
// 以下を追加
mix.webpackConfig({
    stats: {
        children: true,
    },
});

追加してもう一度コンパイルをしてみます。

そうすると別のワーニングが出てきました。

WARNING in ./resources/sass/app.scss (./node_modules/css-loader/dist/cjs.js??ruleSet.rules[5].use!./node_modules/postcss-loader/dist/cjs.js??ruleSet.rules[5].use!./node_modules/resolve-url-loader/index.js??ruleSet.rules[5].use!./node_modules/sass-loader/dist/cjs.js??ruleSet.rules[5].use!./resources/sass/app.scss) Module Warning (from ./node_modules/postcss-loader/dist/cjs.js): Warning (2423:3) autoprefixer: Replace color-adjust to print-color-adjust. The color-adjust shorthand is currently deprecated.

どうやら、autoprefixerというものが必要らしい。

なので以下を叩きます。

npm install autoprefixer@10.4.5 --save-exact

参考 : https://exerror.com/autoprefixer-replace-color-adjust-to-print-color-adjust-the-color-adjust-shorthand-is-currently-deprecated/

その後、再度コンパイルします。

$ npm run dev

┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┬──────────┐
│                                                                                                                                   File │ Size     │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┼──────────┤
│                                                                                                                             /js/app.js │ 2.23 MiB │
│                                                                                                                            css/app.css │ 202 KiB  │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┴──────────┘
    
  Child mini-css-extract-plugin 
~~ webpack compiled successfully

これでOKな様子ですが、皆さんはどうでしょうか?

Viewの作成

画面を作っていきましょう。

今回はリストとゴミ箱の2つのみです。

resources/views/posts/index.blade.php

@extends('layouts.app')
@section('content')

<style>

    input[type="button"],
    .trash-area,
    .body-area{
        cursor: pointer;
    }
    .trash-area:hover{
        opacity: 0.5;
    }
</style>
    <div class="container">
        <div class="row justify-content-center">
            <div class="col-12 col-md-6">
                <label for="task-input">New Task</label>
                <div class="form-group d-flex mb-4">
                    {{-- @csrf --}}
                    <input type="text" name="task" id="task-input" class=" form-control mr-3">
                    <input type="button" value="追加" onClick="createTask()" class="btn btn-outline-primary mx-3">
                </div>
                <table class="w-100 table table-hover">
                    <thead>
                        <tr>
                            <th><i class="fas fa-check-square"></i></th>
                            <th>タスク</th>
                            <th>日付</th>
                            <th class="float-end">ゴミ箱へ</th>
                        </tr>
                    </thead>
                    <tbody class="tr_lists">

                    @foreach ($posts as $post)
                        <tr id="tr_{{$post->id}}" class="@if($post->complete_flag == 1) bg-success  @endif">
                            <td><input type="checkbox" name="task-done" id="checkbox_{{$post->id}}" onChange="checkChange( {{$post->id}} )" @checked($post->complete_flag == 1) ></td>
                            <td class="w-50"><label for="checkbox_{{$post->id}}"><span class="body-area">{{$post->text}}</span></label></td>
                            <td><span class="date-area">{{$post->create_time}}</span></td>
                            <td><span class="trash-area float-end" onClick="goToTrash({{$post->id}})"><i class="fas fa-trash fa-2xl"></i></span></td>
                        </tr>
                    @endforeach

                    </tbody>
                </table>
            </div>
        </div>
    </div>

<script>
    function createTask() {

        const task = $("#task-input").val();

        console.log(task);

        $.ajaxSetup({
            headers: {
                'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
            }
        });

       $.ajax({
        type: 'post',
        data: {
                'task' : task
             },
        datatype: 'json',
            url: '/create'
            })
            .done(function(data){
                console.log(data.post);

                $("#task-input").val('');
                // $('tbody.tr_lists').empty();
                for (let i = 0; i < data.post.length; i++) {
                    const element = data.post[i];
                    console.log(element);
                    var el = '';
                    el+= '<tr id="tr_'+element.id+'" class="">';
                    el+= '<td><input type="checkbox" name="task-done" id="checkbox_'+element.id+'" onChange="checkChange('+element.id+')"></td>';
                    el+= '<td class="w-50"><label for="checkbox_'+element.id+'"><span class="body-area">'+element.text+'</span></label></td>';
                    el+= '<td><span class="date-area">'+element.create_time+'</span></td>';
                    el+= '<td><span class="trash-area float-end" onClick="goToTrash('+element.id+')"><i class="fas fa-trash fa-2xl"></i></span></td>';
                    el+= '</tr>';
                    $('tbody.tr_lists').prepend(el);
                }
                })
            .fail(function(data){
                console.log(data);
                alert("error!");
            });
    }

function goToTrash(id) {

    console.log(id);

    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    $.ajax({
    type: 'post',
    data: {
            'id' : id
            },
    datatype: 'json',
        url: '/softdelete'
        })
        .done(function(data){
            //json = JSON.parse(data);
            console.log(data);
            $('#tr_'+id).remove();
        })
        .fail(function(data){
            console.log(data);
            alert("error!");
        });
    }

    function checkChange(id) {
    
    var is_checked = $('#checkbox_'+id).prop("checked");
    
    $.ajaxSetup({
    headers: {
    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
    });
    
    $.ajax({
    type: 'post',
    data: {
    'id' : id,
    'is_checked' : is_checked
    },
    datatype: 'json',
    url: '/check/change'
    })
    .done(function(data){
    //json = JSON.parse(data);
        console.log(data);
        if(data == 1){
            $('#tr_'+id).addClass('bg-success');
        }else{
            $('#tr_'+id).removeClass('bg-success');
        }
    })
    .fail(function(data){
    console.log(data);
    alert("error!");
    });
    }
</script>
@endsection

createTask()では通信で戻ってきたデータをDOM操作で一番上に表示するという仕様にしてます。

次にゴミ箱の画面が次の通り。

resources/views/posts/trash.blade.php

@extends('layouts.app')
@section('content')

<style>
    .trash-area,
    .body-area {
        cursor: pointer;
    }

    .trash-area:hover {
        opacity: 0.5;
    }
</style>
<div class="container">
    <div class="row justify-content-center">
        <div class="col-12 col-md-6">
            <label for="task-input"><p>ゴミ箱<i class="fas fa-trash"></i></p></label>
            @if( count($posts) > 0)
            <table class="w-100 table table-hover">
                <thead>
                    <tr>
                        <th>タスク</th>
                        <th>日付</th>
                        <th class="float-end">元に戻す</th>
                    </tr>
                </thead>
                <tbody>
                        @foreach ($posts as $post)
                        <tr id="tr_{{$post->id}}">
                        
                            <td class="w-50"><span class="body-area">{{$post->text}}</span></td>
                            <td><span class="date-area">{{$post->create_time}}</span></td>
                            <td><span class="trash-area float-end" onClick="restore({{$post->id}})"><i class="fas fa-undo fa-2xl"></i></span>
                            </td>
                        
                        </tr>
                        @endforeach                  
                </tbody>
            </table>
            <form action="/delete" method="post" onSubmit="return emptyTrash()">
                @csrf
                <button class="btn btn-outline-danger" type="submit">ゴミ箱を空にする</button>
            </form>
            @else
            <p>データはありません。</p>
            @endif
            
        </div>
    </div>
</div>

<script>
// $(function(){
function restore(id) {

    console.log(id);

    $.ajaxSetup({
        headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    }
    });

    $.ajax({
    type: 'post',
    data: {
    'id' : id
    },
    datatype: 'json',
    url: '/restore'
    })
    .done(function(data){ 
    console.log(data);
    $('#tr_'+id).remove();
    })
    .fail(function(data){ 
    console.log(data);
    alert("error!");
    });
}

function emptyTrash() {
    if(window.confirm('本当に実行しますか?')) {
        return true;
    } else {
        return false;
    }
}
// });
</script>
@endsection

emptyTrash()はダイアログをつけて、削除の確認を行ってます。

全体にconsole.logでデータのテストをしておりますので、適宜削除してOKです。

 

コントローラーの編集

リストとゴミ箱で分けても構いませんが、今回は1つに表示から削除まで入れております。

以下の通りです。

app/Http/Controllers/PostController.php

<?php

namespace App\\Http\\Controllers;

use App\\Models\\Post;

use Illuminate\\Http\\Request;
use Illuminate\\Support\\Str;
use Illuminate\\Support\\Facades\\Log;
use Illuminate\\Support\\Facades\\DB;

class PostController extends Controller
{
    /**
     * Display a listing of the resource.
     *
     * @return \\Illuminate\\Http\\Response
     */
    public function index()
    {
        $posts = $this->_getDisplayLists();
        return view('posts.index',['posts'=> $posts]);
    }

    /**
     * creating a new resource.
     *
     * @return \\Illuminate\\Http\\Response
     */
    public function create(Request $request)
    {
        $result = Post::create([
            'text' => $request->task
        ]);

        $post = DB::table('posts')
        ->selectRaw('id,text,complete_flag,DATE_FORMAT(created_at, "%Y-%m-%d %H:%i") AS create_time')
        ->where('id', $result->id)->get();

        return response()->json( ['post' => $post]);
    }

    /**
     * change the specified resource.
     *
     * @return \\Illuminate\\Http\\Response
     */
    public function checkedChange(Request $request)
    {
        $is_checked = $request->is_checked === 'true' ? 1 : 0;
        // Log::debug($is_checked);

        $posts = DB::table('posts')
            ->where('id',$request->id)
            ->update(['complete_flag' => $is_checked]);

        return $is_checked; 
    }

    /**
     * Display a listing of the trash resource.
     *
     * @return \\Illuminate\\Http\\Response
     */
    public function trash()
    {
        $posts =  $this->_getTrashLists();
        return view('posts.trash', ['posts' => $posts]);

    }
    /**
     * SoftDelete the specified resource from storage.
     *
     * @param  \\App\\Models\\Post  $post
     * @return \\Illuminate\\Http\\Response
     */
    public function goToTrash(Request $request)
    {
        $result = Post::destroy($request->id);

        return $result;
    }

    /**
     * Back to store the specified resource from storage.
     *
     * @param  \\App\\Models\\Post  $post
     * @return \\Illuminate\\Http\\Response
     */
    public function restore(Request $request)
    {
        $result = Post::where('id',$request->id)->withTrashed()->restore();
        

        return redirect()->route('post.trash');
    }

    /**
     * Remove the specified resource from storage.
     *
     * @return \\Illuminate\\Http\\Response
     */
    public function delete()
    {
        $result = DB::table('posts')->whereNotNull('deleted_at')->delete();
        $posts = $this->_getTrashLists();

        return redirect()->route('post.trash', ['post' => $posts]);
    }

    // SQL
    private function _getDisplayLists()
    {
        $result = DB::table('posts')
            ->selectRaw('id,text,complete_flag,DATE_FORMAT(created_at, "%Y-%m-%d %H:%i") AS create_time')
            ->whereNull('deleted_at')
            ->orderByRaw('created_at DESC')
            ->get();
        return $result;
    }

    private function _getTrashLists()
    {
        $result = DB::table('posts')
        ->selectRaw('id,text,complete_flag,DATE_FORMAT(created_at, "%Y-%m-%d %H:%i") AS create_time')
        ->whereNotNull('deleted_at')
        ->orderByRaw('created_at DESC')
        ->get();
        return $result;
    }
}

下2つのprivate関数はよく使うものをまとめております。

論理削除がポイントなので、リストを表示する時は->whereNull('deleted_at')を使っており、

ゴミ箱は->whereNotNull('deleted_at') で表示してます。

Log:debugはテスト用に出力しているものなので、消しちゃって構いません。

 

 

ルーティングを作成

ルーティングのところに、Laravel UIを入れた時に入っている、LoginやRegisterなどが入ってますが、全て消してOKです。

今回使うのは以下通りです。

use App\\Http\\Controllers\\PostController;

Route::get('/', [PostController::class, 'index'])->name('post.index');
Route::get('/trash', [PostController::class, 'trash'])->name('post.trash');

Route::post('/create', [PostController::class, 'create']);
Route::post('/check/change', [PostController::class, 'checkedChange']);
Route::post('/softdelete', [PostController::class, 'goToTrash']);
Route::post('/restore', [PostController::class, 'restore']);
Route::post('/delete', [PostController::class, 'delete']);

get通信になっているのがリストページとゴミ箱ページでして、post通信になっているのが非同期処理で行う部分です。

use ~ PostController;と宣言するのが必須です。