20191002のlaravelに関する記事は6件です。

【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第6回コメントといいね機能)

Laravelで始めるTwitter風(Twitterクローン)のSNSツール開発チュートリアル

概要

スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。

前回

いよいよ最後となった第6回はコメントといいね機能を実装していきます?‍?

前提

  • PHPをある程度理解している
  • Homesteadをインストールしている
  • MVC構造をある程度理解している

環境

  • Mac
  • Homestead
  • Laravel 5.8

コメント機能

それではツイートに対してコメントを付けれる機能を実装していきます。

ルーティング

コメント機能を実装していくにあたりルーティングの定義をします。
コメント機能では保存のみの実装になるのでstoreのみ指定します。

routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {

    // 省略

    // コメント関連
    Route::resource('comments', 'CommentsController', ['only' => ['store']]);
});

Model

Controllerからバリデーションが通ってきたという前提で、データを保存していきます。
引数にはコメントしたユーザのユーザIDとなる$user_idとコメントのデータ$dataを設定します。

app/Models/Comment.php
    public function commentStore(Int $user_id, Array $data)
    {
        $this->user_id = $user_id;
        $this->tweet_id = $data['tweet_id'];
        $this->text = $data['text'];
        $this->save();

        return;
    }

Controller

では例によってCommentsController.phpファイルを--resourceで作成しましょう。

php artisan make:controller CommentsController --resource

ファイルを生成したら以下の内容で書いていきます。

基本的に【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第4回ツイートのCRUD機能)でやったツイートの保存と同じ流れなので説明は省きます

app/Http/Controllers/CommentsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\Comment;

class CommentsController extends Controller
{
    public function store(Request $request, Comment $comment)
    {
        $user = auth()->user();
        $data = $request->all();
        $validator = Validator::make($data, [
            'tweet_id' =>['required', 'integer'],
            'text'     => ['required', 'string', 'max:140']
        ]);

        $validator->validate();
        $comment->commentStore($user->id, $data);

        return back();
    }
}

View

早速Viewを作っていきたいのですが、コメントはツイートのViewを使用します。
ツイートの詳細画面からしか投稿できないという仕様なのでtweets/show.blade.phpに組み込んでいきます。

<!-- ここから下を変更してください -->の部分を上書きしてください。

resources/views/tweets/show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center mb-5">
        <div class="col-md-8 mb-3">
            <div class="card">
                <div class="card-haeder p-3 w-100 d-flex">
                    <img src="{{ asset('storage/profile_image/' .$tweet->user->profile_image) }}" class="rounded-circle" width="50" height="50">
                    <div class="ml-2 d-flex flex-column">
                        <p class="mb-0">{{ $tweet->user->name }}</p>
                        <a href="{{ url('users/' .$tweet->user->id) }}" class="text-secondary">{{ $tweet->user->screen_name }}</a>
                    </div>
                    <div class="d-flex justify-content-end flex-grow-1">
                        <p class="mb-0 text-secondary">{{ $tweet->created_at->format('Y-m-d H:i') }}</p>
                    </div>
                </div>
                <div class="card-body">
                    {!! nl2br(e($tweet->text)) !!}
                </div>
                <div class="card-footer py-1 d-flex justify-content-end bg-white">
                    @if ($tweet->user->id === Auth::user()->id)
                        <div class="dropdown mr-3 d-flex align-items-center">
                            <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                <i class="fas fa-ellipsis-v fa-fw"></i>
                            </a>
                            <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                                <form method="POST" action="{{ url('tweets/' .$tweet->id) }}" class="mb-0">
                                    @csrf
                                    @method('DELETE')

                                    <a href="{{ url('tweets/' .$tweet->id .'/edit') }}" class="dropdown-item">編集</a>
                                    <button type="submit" class="dropdown-item del-btn">削除</button>
                                </form>
                            </div>
                        </div>
                    @endif
                    <div class="mr-3 d-flex align-items-center">
                        <a href="{{ url('tweets/' .$tweet->id) }}"><i class="far fa-comment fa-fw"></i></a>
                        <p class="mb-0 text-secondary">{{ count($tweet->comments) }}</p>
                    </div>
                    <div class="d-flex align-items-center">
                        <button type="" class="btn p-0 border-0 text-primary"><i class="far fa-heart fa-fw"></i></button>
                        <p class="mb-0 text-secondary">{{ count($tweet->favorites) }}</p>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- ここから下を変更してください -->
    <div class="row justify-content-center">
        <div class="col-md-8 mb-3">
            <ul class="list-group">
                @forelse ($comments as $comment)
                    <li class="list-group-item">
                        <div class="py-3 w-100 d-flex">
                            <img src="{{ asset('storage/profile_image/' .$comment->user->profile_image) }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column">
                                <p class="mb-0">{{ $comment->user->name }}</p>
                                <a href="{{ url('users/' .$comment->user->id) }}" class="text-secondary">{{ $comment->user->screen_name }}</a>
                            </div>
                            <div class="d-flex justify-content-end flex-grow-1">
                                <p class="mb-0 text-secondary">{{ $comment->created_at->format('Y-m-d H:i') }}</p>
                            </div>
                        </div>
                        <div class="py-3">
                            {!! nl2br(e($comment->text)) !!}
                        </div>
                    </li>
                @empty
                    <li class="list-group-item">
                        <p class="mb-0 text-secondary">コメントはまだありません</p>
                    </li>
                @endforelse
                <li class="list-group-item">
                    <div class="py-3">
                        <form method="POST" action="{{ route('comments.store') }}">
                            @csrf

                            <div class="form-group row mb-0">
                                <div class="col-md-12 p-3 w-100 d-flex">
                                    <img src="{{ asset('storage/profile_image/' .$user->profile_image) }}" class="rounded-circle" width="50" height="50">
                                    <div class="ml-2 d-flex flex-column">
                                        <p class="mb-0">{{ $user->name }}</p>
                                        <a href="{{ url('users/' .$user->id) }}" class="text-secondary">{{ $user->screen_name }}</a>
                                    </div>
                                </div>
                                <div class="col-md-12">
                                    <input type="hidden" name="tweet_id" value="{{ $tweet->id }}">
                                    <textarea class="form-control @error('text') is-invalid @enderror" name="text" required autocomplete="text" rows="4">{{ old('text') }}</textarea>

                                    @error('text')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row mb-0">
                                <div class="col-md-12 text-right">
                                    <p class="mb-4 text-danger">140文字以内</p>
                                    <button type="submit" class="btn btn-primary">
                                        ツイートする
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</div>
@endsection

これでコメントが付いていないときはコメントはまだありません。と表示されて、
ログインしてるユーザがコメント出来る様になっていると思います。

スクリーンショット 2019-10-02 22.46.55.png

投稿すると以下のようにちゃんとコメントが投稿されているのが確認できていると思います。

スクリーンショット 2019-10-02 22.48.31.png

これでコメント機能の実装は終わりです。

いいね機能

これでとりあえず最後の機能となります。こういうのも欲しいみたいな意見は聞かない

いいね機能では一人につき1いいね。
まだいいねを付けていない投稿に対してはいいねを保存。
逆に既にいいねを付けているツイートに対して再度いいねを押すと削除するという仕様にします。

ルーティング

さてお決まりのルーティングを設定していきましょう。
いいね機能では保存のstoreと削除のdestroyを設定しておきます。

routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {

    // 省略

    // いいね関連
    Route::resource('favorites', 'FavoritesController', ['only' => ['store', 'destroy']]);
});

Model

app/Models/Favorite.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Favorite extends Model
{
    public $timestamps = false;

    // いいねしているかどうかの判定処理
    public function isFavorite(Int $user_id, Int $tweet_id) 
    {
        return (boolean) $this->where('user_id', $user_id)->where('tweet_id', $tweet_id)->first();
    }

    public function storeFavorite(Int $user_id, Int $tweet_id)
    {
        $this->user_id = $user_id;
        $this->tweet_id = $tweet_id;
        $this->save();

        return;
    }

    public function destroyFavorite(Int $favorite_id)
    {
        return $this->where('id', $favorite_id)->delete();
    }
}

isFavoriteという見慣れないメソッドがありますね。
これはいいねを押した際にツイートに対して既にいいね済みであればfalse、逆に存在しなければtrue
これで正しいデータが飛んできたかどうかを判定しています。

Controller

またまたFavoritesController.phpファイルを--resourceで作成しましょう。

php artisan make:controller FavoritesController --resource

いいね機能の場合、見ている全ツイートに対していいね済かの判定があり同じViewファイルにstoredestroyactionが異なったformが混在した形で渡されます。
そのためログインしているユーザがツイートに対していいねをしているかの判定はフロントで行い、その判定の分岐でstoredestroyどちらかのデータがControllerに渡されます。

app/Http/Controllers/FavoritesController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Favorite;

class FavoritesController extends Controller
{
    public function store(Request $request, Favorite $favorite)
    {
        $user = auth()->user();
        $tweet_id = $request->tweet_id;
        $is_favorite = $favorite->isFavorite($user->id, $tweet_id);

        if(!$is_favorite) {
            $favorite->storeFavorite($user->id, $tweet_id);
            return back();
        }
        return back();
    }

    public function destroy(Favorite $favorite)
    {
        $user_id = $favorite->user_id;
        $tweet_id = $favorite->tweet_id;
        $favorite_id = $favorite->id;
        $is_favorite = $favorite->isFavorite($user_id, $tweet_id);

        if($is_favorite) {
            $favorite->destroyFavorite($favorite_id);
            return back();
        }
        return back();
    }
}

View

いいね機能もコメント機能同様にViewが存在しないので、こちらもtweetsに書いていきます。
コメントとは違ってツイート一覧画面とツイート詳細画面さらにユーザ詳細画面3つあるので、一気に3つやっていきます。

全て<!-- ここから --><!-- ここまで -->の間の部分を上書きしてください。

ユーザ詳細画面(users/show.blade.php)

先ほど実装したコメントのリンクもついでに設定しておきます。

resources/views/users/show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8 mb-3">
            <div class="card">
                <div class="d-inline-flex">
                    <div class="p-3 d-flex flex-column">
                        <img src="{{ asset('storage/profile_image/' .$user->profile_image) }}" class="rounded-circle" width="100" height="100">
                        <div class="mt-3 d-flex flex-column">
                            <h4 class="mb-0 font-weight-bold">{{ $user->name }}</h4>
                            <span class="text-secondary">{{ $user->screen_name }}</span>
                        </div>
                    </div>
                    <div class="p-3 d-flex flex-column justify-content-between">
                        <div class="d-flex">
                            <div>
                                @if ($user->id === Auth::user()->id)
                                    <a href="{{ url('users/' .$user->id .'/edit') }}" class="btn btn-primary">プロフィールを編集する</a>
                                @else
                                    @if ($is_following)
                                        <form action="{{ route('unfollow', ['id' => $user->id]) }}" method="POST" class="mb-2">
                                            {{ csrf_field() }}
                                            {{ method_field('DELETE') }}

                                            <button type="submit" class="btn btn-danger">フォロー解除</button>
                                        </form>
                                    @else
                                        <form action="{{ route('follow', ['id' => $user->id]) }}" method="POST" class="mb-2">
                                            {{ csrf_field() }}

                                            <button type="submit" class="btn btn-primary">フォローする</button>
                                        </form>
                                    @endif

                                    @if ($is_followed)
                                        <span class="mt-2 px-1 bg-secondary text-light">フォローされています</span>
                                    @endif
                                @endif
                            </div>
                        </div>
                        <div class="d-flex justify-content-end">
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">ツイート数</p>
                                <span>{{ $tweet_count }}</span>
                            </div>
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">フォロー数</p>
                                <span>{{ $follow_count }}</span>
                            </div>
                            <div class="p-2 d-flex flex-column align-items-center">
                                <p class="font-weight-bold">フォロワー数</p>
                                <span>{{ $follower_count }}</span>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        @if (isset($timelines))
            @foreach ($timelines as $timeline)
                <div class="col-md-8 mb-3">
                    <div class="card">
                        <div class="card-haeder p-3 w-100 d-flex">
                            <img src="{{ asset('storage/profile_image/' .$user->profile_image) }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column flex-grow-1">
                                <p class="mb-0">{{ $timeline->user->name }}</p>
                                <a href="{{ url('users/' .$timeline->user->id) }}" class="text-secondary">{{ $timeline->user->screen_name }}</a>
                            </div>
                            <div class="d-flex justify-content-end flex-grow-1">
                                <p class="mb-0 text-secondary">{{ $timeline->created_at->format('Y-m-d H:i') }}</p>
                            </div>
                        </div>
                        <div class="card-body">
                            {{ $timeline->text }}
                        </div>
                        <div class="card-footer py-1 d-flex justify-content-end bg-white">
                            @if ($timeline->user->id === Auth::user()->id)
                                <div class="dropdown mr-3 d-flex align-items-center">
                                    <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                        <i class="fas fa-ellipsis-v fa-fw"></i>
                                    </a>
                                    <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                                        <form method="POST" action="{{ url('tweets/' .$timeline->id) }}" class="mb-0">
                                            @csrf
                                            @method('DELETE')

                                            <a href="{{ url('tweets/' .$timeline->id .'/edit') }}" class="dropdown-item">編集</a>
                                            <button type="submit" class="dropdown-item del-btn">削除</button>
                                        </form>
                                    </div>
                                </div>
                            @endif

                            <!-- ここから -->
                            <div class="mr-3 d-flex align-items-center">
                                <a href="{{ url('tweets/' .$timeline->id) }}"><i class="far fa-comment fa-fw"></i></a>
                                <p class="mb-0 text-secondary">{{ count($timeline->comments) }}</p>
                            </div>
                            <div class="d-flex align-items-center">
                                @if (!in_array(Auth::user()->id, array_column($timeline->favorites->toArray(), 'user_id'), TRUE))
                                    <form method="POST" action="{{ url('favorites/') }}" class="mb-0">
                                        @csrf

                                        <input type="hidden" name="tweet_id" value="{{ $timeline->id }}">
                                        <button type="submit" class="btn p-0 border-0 text-primary"><i class="far fa-heart fa-fw"></i></button>
                                    </form>
                                @else
                                    <form method="POST"action="{{ url('favorites/' .array_column($timeline->favorites->toArray(), 'id', 'user_id')[Auth::user()->id]) }}" class="mb-0">
                                        @csrf
                                        @method('DELETE')

                                        <button type="submit" class="btn p-0 border-0 text-danger"><i class="fas fa-heart fa-fw"></i></button>
                                    </form>
                                @endif
                                <p class="mb-0 text-secondary">{{ count($timeline->favorites) }}</p>
                            </div>
                            <!-- ここまで -->

                        </div>
                    </div>
                </div>
            @endforeach
        @endif
    </div>
    <div class="my-4 d-flex justify-content-center">
        {{ $timelines->links() }}
    </div>
</div>
@endsection

ツイート一覧画面(tweets/index.blade.php)

resources/views/tweets/index.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8 mb-3 text-right">
            <a href="{{ url('users') }}">ユーザ一覧 <i class="fas fa-users" class="fa-fw"></i> </a>
        </div>
        @if (isset($timelines))
            @foreach ($timelines as $timeline)
                <div class="col-md-8 mb-3">
                    <div class="card">
                        <div class="card-haeder p-3 w-100 d-flex">
                            <img src="{{ asset('storage/profile_image/' .$timeline->user->profile_image) }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column">
                                <p class="mb-0">{{ $timeline->user->name }}</p>
                                <a href="{{ url('users/' .$timeline->user->id) }}" class="text-secondary">{{ $timeline->user->screen_name }}</a>
                            </div>
                            <div class="d-flex justify-content-end flex-grow-1">
                                <p class="mb-0 text-secondary">{{ $timeline->created_at->format('Y-m-d H:i') }}</p>
                            </div>
                        </div>
                        <div class="card-body">
                            {!! nl2br(e($timeline->text)) !!}
                        </div>
                        <div class="card-footer py-1 d-flex justify-content-end bg-white">
                            @if ($timeline->user->id === Auth::user()->id)
                                <div class="dropdown mr-3 d-flex align-items-center">
                                    <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                        <i class="fas fa-ellipsis-v fa-fw"></i>
                                    </a>
                                    <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                                        <form method="POST" action="{{ url('tweets/' .$timeline->id) }}" class="mb-0">
                                            @csrf
                                            @method('DELETE')

                                            <a href="{{ url('tweets/' .$timeline->id .'/edit') }}" class="dropdown-item">編集</a>
                                            <button type="submit" class="dropdown-item del-btn">削除</button>
                                        </form>
                                    </div>
                                </div>
                            @endif
                            <div class="mr-3 d-flex align-items-center">
                                <a href="{{ url('tweets/' .$timeline->id) }}"><i class="far fa-comment fa-fw"></i></a>
                                <p class="mb-0 text-secondary">{{ count($timeline->comments) }}</p>
                            </div>

                            <!-- ここから -->
                            <div class="d-flex align-items-center">
                                @if (!in_array($user->id, array_column($timeline->favorites->toArray(), 'user_id'), TRUE))
                                    <form method="POST" action="{{ url('favorites/') }}" class="mb-0">
                                        @csrf

                                        <input type="hidden" name="tweet_id" value="{{ $timeline->id }}">
                                        <button type="submit" class="btn p-0 border-0 text-primary"><i class="far fa-heart fa-fw"></i></button>
                                    </form>
                                @else
                                    <form method="POST" action="{{ url('favorites/' .array_column($timeline->favorites->toArray(), 'id', 'user_id')[$user->id]) }}" class="mb-0">
                                        @csrf
                                        @method('DELETE')

                                        <button type="submit" class="btn p-0 border-0 text-danger"><i class="fas fa-heart fa-fw"></i></button>
                                    </form>
                                @endif
                                <p class="mb-0 text-secondary">{{ count($timeline->favorites) }}</p>
                            </div>
                            <!-- ここまで -->

                        </div>
                    </div>
                </div>
            @endforeach
        @endif
    </div>
    <div class="my-4 d-flex justify-content-center">
        {{ $timelines->links() }}
    </div>
</div>
@endsection

ツイート詳細画面(tweets/show.blade.php)

resources/views/tweets/show.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center mb-5">
        <div class="col-md-8 mb-3">
            <div class="card">
                <div class="card-haeder p-3 w-100 d-flex">
                    <img src="{{ asset('storage/profile_image/' .$tweet->user->profile_image) }}" class="rounded-circle" width="50" height="50">
                    <div class="ml-2 d-flex flex-column">
                        <p class="mb-0">{{ $tweet->user->name }}</p>
                        <a href="{{ url('users/' .$tweet->user->id) }}" class="text-secondary">{{ $tweet->user->screen_name }}</a>
                    </div>
                    <div class="d-flex justify-content-end flex-grow-1">
                        <p class="mb-0 text-secondary">{{ $tweet->created_at->format('Y-m-d H:i') }}</p>
                    </div>
                </div>
                <div class="card-body">
                    {!! nl2br(e($tweet->text)) !!}
                </div>
                <div class="card-footer py-1 d-flex justify-content-end bg-white">
                    @if ($tweet->user->id === Auth::user()->id)
                        <div class="dropdown mr-3 d-flex align-items-center">
                            <a href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
                                <i class="fas fa-ellipsis-v fa-fw"></i>
                            </a>
                            <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
                                <form method="POST" action="{{ url('tweets/' .$tweet->id) }}" class="mb-0">
                                    @csrf
                                    @method('DELETE')

                                    <a href="{{ url('tweets/' .$tweet->id .'/edit') }}" class="dropdown-item">編集</a>
                                    <button type="submit" class="dropdown-item del-btn">削除</button>
                                </form>
                            </div>
                        </div>
                    @endif
                    <div class="mr-3 d-flex align-items-center">
                        <a href="{{ url('tweets/' .$tweet->id) }}"><i class="far fa-comment fa-fw"></i></a>
                        <p class="mb-0 text-secondary">{{ count($tweet->comments) }}</p>
                    </div>

                    <!-- ここから -->
                    <div class="d-flex align-items-center">
                        @if (!in_array($user->id, array_column($tweet->favorites->toArray(), 'user_id'), TRUE))
                            <form method="POST" action="{{ url('favorites/') }}" class="mb-0">
                                @csrf

                                <input type="hidden" name="tweet_id" value="{{ $tweet->id }}">
                                <button type="submit" class="btn p-0 border-0 text-primary"><i class="far fa-heart fa-fw"></i></button>
                            </form>
                        @else
                            <form method="POST" action="{{ url('favorites/' .array_column($tweet->favorites->toArray(), 'id', 'user_id')[$user->id]) }}" class="mb-0">
                                @csrf
                                @method('DELETE')

                                <button type="submit" class="btn p-0 border-0 text-danger"><i class="fas fa-heart fa-fw"></i></button>
                            </form>
                        @endif
                        <p class="mb-0 text-secondary">{{ count($tweet->favorites) }}</p>
                    </div>
                    <!-- ここまで -->

                </div>
            </div>
        </div>
    </div>

    <div class="row justify-content-center">
        <div class="col-md-8 mb-3">
            <ul class="list-group">
                @forelse ($comments as $comment)
                    <li class="list-group-item">
                        <div class="py-3 w-100 d-flex">
                            <img src="{{ asset('storage/profile_image/' .$comment->user->profile_image) }}" class="rounded-circle" width="50" height="50">
                            <div class="ml-2 d-flex flex-column">
                                <p class="mb-0">{{ $comment->user->name }}</p>
                                <a href="{{ url('users/' .$comment->user->id) }}" class="text-secondary">{{ $comment->user->screen_name }}</a>
                            </div>
                            <div class="d-flex justify-content-end flex-grow-1">
                                <p class="mb-0 text-secondary">{{ $comment->created_at->format('Y-m-d H:i') }}</p>
                            </div>
                        </div>
                        <div class="py-3">
                            {!! nl2br(e($comment->text)) !!}
                        </div>
                    </li>
                @empty
                    <li class="list-group-item">
                        <p class="mb-0 text-secondary">コメントはまだありません</p>
                    </li>
                @endforelse
                <li class="list-group-item">
                    <div class="py-3">
                        <form method="POST" action="{{ route('comments.store') }}">
                            @csrf

                            <div class="form-group row mb-0">
                                <div class="col-md-12 p-3 w-100 d-flex">
                                    <img src="{{ asset('storage/profile_image/' .$user->profile_image) }}" class="rounded-circle" width="50" height="50">
                                    <div class="ml-2 d-flex flex-column">
                                        <p class="mb-0">{{ $user->name }}</p>
                                        <a href="{{ url('users/' .$user->id) }}" class="text-secondary">{{ $user->screen_name }}</a>
                                    </div>
                                </div>
                                <div class="col-md-12">
                                    <input type="hidden" name="tweet_id" value="{{ $tweet->id }}">
                                    <textarea class="form-control @error('text') is-invalid @enderror" name="text" required autocomplete="text" rows="4">{{ old('text') }}</textarea>

                                    @error('text')
                                        <span class="invalid-feedback" role="alert">
                                            <strong>{{ $message }}</strong>
                                        </span>
                                    @enderror
                                </div>
                            </div>

                            <div class="form-group row mb-0">
                                <div class="col-md-12 text-right">
                                    <p class="mb-4 text-danger">140文字以内</p>
                                    <button type="submit" class="btn btn-primary">
                                        ツイートする
                                    </button>
                                </div>
                            </div>
                        </form>
                    </div>
                </li>
            </ul>
        </div>
    </div>
</div>
@endsection

これで各画面でいいねを押してみる

ユーザ詳細画面

スクリーンショット 2019-10-02 23.37.20.png

ツイート詳細画面では逆にいいねを外してみる

ツイート詳細画面

スクリーンショット 2019-10-02 23.34.08.png

一覧画面ではもう一度いいねをしてみて動いてるか確認する。

ツイート一覧画面

スクリーンショット 2019-10-02 23.33.23.png

完成!!?‍??‍??‍?

Congratulations!

これで【全6回】Laravel5.8でTwitterっぽいSNSツールを作るは終了です。
なんとなくLaravelの取っ付きやすさは伝わりましたか?
これを機にLaravelを使いたくなったという方がいれば嬉しいです!!?‍?
お疲れ様でした!!

絶対どこがで間違いがあるはずなので、都度修正していきます。
訂正箇所があれば教えていただければ幸いです?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第5回ツイートのCRUD機能 編集と削除)

Laravelで始めるTwitter風(Twitterクローン)のSNSツール開発チュートリアル

概要

スクールとかの課題だったりLaravelを初めてみたいけど何を作ろうって迷ってる人向けによくあるTwitter風のWEBサイトを作ってみます。

前回

第4回ツイートのCRUD機能の続きを追加していきます。
今回はツイートの編集(Update)と削除(Delete)をやっていきます。

前提

  • PHPをある程度理解している
  • Homesteadをインストールしている
  • MVC構造をある程度理解している

環境

  • Mac
  • Homestead
  • Laravel 5.8

リダイレクト先

ついでに現在ログインすると/homeに飛ぶようになっているのでこちらも/tweetsにリダイレクトするように設定しましょう(忘れてた)

  • 対象のファイル名
    • LoginController.php
    • RegisterController.php
    • ResetPasswordController.php
    • VerificationController.php
    • RedirectIfAuthenticated.php

上記のファイルから/homeの部分を全て/tweetsに変更してください。

そうするとログイン後も/tweetsにリダイレクトするようになります!

Update(ツイート編集機能)

ではツイート内容の編集機能を実装していきましょう。

Update(ツイート編集画面)

では次はCRUDのUpdateをやっていきましょう!
まずはツイート投稿と同じく編集画面から作っていきます。

Model

$user_id$tweet_idに値に一致するツイートを取得します。

app/Models/Tweet.php
    public function getEditTweet(Int $user_id, Int $tweet_id)
    {
        return $this->where('user_id', $user_id)->where('id', $tweet_id)->first();
    }

Controller

編集するツイートを先ほどのgetEditTweet$tweet_idを渡してその結果をViewに渡す処理をします。

app/Http/Controllers/TweetsController.php
    public function edit(Tweet $tweet)
    {
        $user = auth()->user();
        $tweets = $tweet->getEditTweet($user->id, $tweet->id);

        if (!isset($tweets)) {
            return redirect('tweets');
        }

        return view('tweets.edit', [
            'user'   => $user,
            'tweets' => $tweets
        ]);
    }

View

resources/views/tweets/edit.blade.php
@extends('layouts.app')

@section('content')
<div class="container">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Update</div>

                <div class="card-body">
                    <form method="POST" action="{{ route('tweets.update', ['tweets' => $tweets]) }}">
                        @csrf
                        @method('PUT')

                        <div class="form-group row mb-0">
                            <div class="col-md-12 p-3 w-100 d-flex">
                                <img src="{{ asset('storage/profile_image/' .$user->profile_image) }}" class="rounded-circle" width="50" height="50">
                                <div class="ml-2 d-flex flex-column">
                                    <p class="mb-0">{{ $user->name }}</p>
                                    <a href="{{ url('users/' .$user->id) }}" class="text-secondary">{{ $user->screen_name }}</a>
                                </div>
                            </div>
                            <div class="col-md-12">
                                <textarea class="form-control @error('text') is-invalid @enderror" name="text" required autocomplete="text" rows="4">{{ old('text') ? : $tweets->text }}</textarea>

                                @error('text')
                                    <span class="invalid-feedback" role="alert">
                                        <strong>{{ $message }}</strong>
                                    </span>
                                @enderror
                            </div>
                        </div>

                        <div class="form-group row mb-0">
                            <div class="col-md-12 text-right">
                                <p class="mb-4 text-danger">140文字以内</p>
                                <button type="submit" class="btn btn-primary">
                                    ツイートする
                                </button>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
        </div>
    </div>
</div>
@endsection

ツイート編集画面

ツイート一覧ページから自身のツイートのみ表示されるボタン「・・・←これを縦にしたアイコン 笑」を押すと編集ボタンが表示されるのでそれを押すとこのページに行けます!

スクリーンショット 2019-09-19 2.05.48.png

Update(ツイート編集機能)

では先ほどの編集画面から実際に編集した内容で更新する処理を書いていきましょう!

Model

この辺はただ上書きしているだけなので、特に触れません。

もしわからない人がいれば第3回のUser情報を更新するときと流れは一緒なのでもう一度見てみてください。

app/Models/Tweet.php
    public function tweetUpdate(Int $tweet_id, Array $data)
    {
        $this->id = $tweet_id;
        $this->text = $data['text'];
        $this->update();

        return;
    }

Controller

こちらも先ほど作成したstoreとほとんど同じなので説明は省きます。

app/Http/Controllers/TweetsController.php
    public function update(Request $request, Tweet $tweet)
    {
        $data = $request->all();
        $validator = Validator::make($data, [
            'text' => ['required', 'string', 'max:140']
        ]);

        $validator->validate();
        $tweet->tweetUpdate($tweet->id, $data);

        return redirect('tweets');
    }

これで編集ができるようになっていると思います!

Delete(ツイート削除)

次はツイートの削除を行ってみようと思います?‍?

Delete(ツイート削除機能)

Model

$user_id$tweet_idに一致したツイートを削除します。

app/Models/Tweet.php
    public function tweetDestroy(Int $user_id, Int $tweet_id)
    {
        return $this->where('user_id', $user_id)->where('id', $tweet_id)->delete();
    }

Controller

$user_id$tweet_idを先ほど作成したtweetDestroy()メソッドに渡しています。

app/Http/Controllers/TweetsController.php
    public function destroy(Tweet $tweet)
    {
        $user = auth()->user();
        $tweet->tweetDestroy($user->id, $tweet->id);

        return back();
    }

これで削除機能を実装できましたので、先ほどの編集画面に遷移する方法と同様に削除するボタンがあるので試してみてください。

振り返り(ModelとControllerファイルの全体)

今回第4,5回とModelとControllerの行き来が多かったのでとりあえず全体も載せておきます!
ここまで問題なく動いたよって人はスルーしてください。

Model

app/Models/Tweet.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\softDeletes;

class Tweet extends Model
{
    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
        'text'
    ];

    public function user()
    {
        return $this->belongsTo(User::class);
    }

    public function favorites()
    {
        return $this->hasMany(Favorite::class);
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }

    public function getUserTimeLine(Int $user_id)
    {
        return $this->where('user_id', $user_id)->orderBy('created_at', 'DESC')->paginate(50);
    }

    public function getTweetCount(Int $user_id)
    {
        return $this->where('user_id', $user_id)->count();
    }

    // 詳細画面
    public function getTweet(Int $tweet_id)
    {
        return $this->with('user')->where('id', $tweet_id)->first();
    }

    // 一覧画面
    public function getTimeLines(Int $user_id, Array $follow_ids)
    {
        $follow_ids[] = $user_id;
        return $this->whereIn('user_id', $follow_ids)->orderBy('created_at', 'DESC')->paginate(50);
    }

    public function tweetStore(Int $user_id, Array $data)
    {
        $this->user_id = $user_id;
        $this->text = $data['text'];
        $this->save();

        return;
    }

    public function getEditTweet(Int $user_id, Int $tweet_id)
    {
        return $this->where('user_id', $user_id)->where('id', $tweet_id)->first();
    }

    public function tweetUpdate(Int $tweet_id, Array $data)
    {
        $this->id = $tweet_id;
        $this->text = $data['text'];
        $this->update();

        return;
    }

    public function tweetDestroy(Int $user_id, Int $tweet_id)
    {
        return $this->where('user_id', $user_id)->where('id', $tweet_id)->delete();
    }
}

Controller

app/Http/Controllers/TweetsController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\Tweet;
use App\Models\Comment;
use App\Models\Follower;

class TweetsController extends Controller
{
    public function index(Tweet $tweet, Follower $follower)
    {
        $user = auth()->user();
        $follow_ids = $follower->followingIds($user->id);
        $following_ids = $follow_ids->pluck('followed_id')->toArray();

        $timelines = $tweet->getTimelines($user->id, $following_ids);

        return view('tweets.index', [
            'user'      => $user,
            'timelines' => $timelines
        ]);
    }

    public function create()
    {
        $user = auth()->user();

        return view('tweets.create', [
            'user' => $user
        ]);
    }

    public function store(Request $request, Tweet $tweet)
    {
        $user = auth()->user();
        $data = $request->all();

        $validator = Validator::make($data, [
            'text' => ['required', 'string', 'max:140']
        ]);
        $validator->validate();

        $tweet->tweetStore($user->id, $data);

        return redirect('tweets');
    }

    public function show(Tweet $tweet, Comment $comment)
    {
        $user = auth()->user();
        $tweet = $tweet->getTweet($tweet->id);
        $comments = $comment->getComments($tweet->id);

        return view('tweets.show', [
            'user'     => $user,
            'tweet'    => $tweet,
            'comments' => $comments
        ]);
    }

    public function edit(Tweet $tweet)
    {
        $user = auth()->user();
        $tweets = $tweet->getEditTweet($user->id, $tweet->id);

        if (!isset($tweets)) {
            return redirect('tweets');
        }

        return view('tweets.edit', [
            'user'   => $user,
            'tweets' => $tweets
        ]);
    }

    public function update(Request $request, Tweet $tweet)
    {
        $data = $request->all();
        $validator = Validator::make($data, [
            'text' => ['required', 'string', 'max:140']
        ]);

        $validator->validate();
        $tweet->tweetUpdate($tweet->id, $data);

        return redirect('tweets');
    }

    public function destroy(Tweet $tweet)
    {
        $user = auth()->user();
        $tweet->tweetDestroy($user->id, $tweet->id);

        return back();
    }
}

以上です。

次回 -> 【全6回】Laravel5.8でTwitterっぽいSNSツールを作る(第6回コメントといいね機能)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで実装したAPIがOpenAPIで記述された仕様に準拠しているかテストする

はじめに

APIを開発する上で、多くの場合、仕様書も作成するかと思いますが、どのように作成しているでしょうか?
この記事では、OpenAPI形式で記述されたAPI仕様があって、その仕様にAPIの実装が準拠しているかテストする方法を紹介します。

tl;dr

OpenAPI v3とは(Swagger v2との違い)

詳しくは後述の参考リンクを参照してほしいのですが、誤解を恐れずに要点をまとめると以下のようになります。

  • APIを定義する仕様として、2010年にSwagger 1.0がリリースされる
  • Swaggerは、Open API Initiativeに寄贈され、2017-07-26にOpen API 3.0がリリースされる
  • Open API 3.0Swagger 2.0から大幅な変更が加えられており、互換性はない

参考リンク

OpenAPI v3に対応した検証ライブラリは多くない(2019年10月1日現在)

Swagger v2に対応したPHP製の検証ライブラリはいくつかあります。
しかし、いずれも、OpenAPI v3には未対応です。

WakeOnWeb/swagger

OpenAPI v3 support · Issue #16 · WakeOnWeb/swaggerはいまだにOpenのままです。

nabbar/SwaggerValidator-PHP

README.mdを見ると、swagger / openapi version 3.0 (release >= 2.0)とあるので、v2.0以上だったらOpen API 3.0に対応しているのかと思いきや、最新のバージョンは1.3.2です。
対応予定だけ書いて力尽きたようです。

OpenAPI v3形式のファイルをJSON Schemaに変換すれば、検証はできる

laravel-petstore-apiの最初のコミットでもこの方法を使っているのですが、OpenAPI v3形式のファイルをJSON Schemaに変換し、RequestおよびResponseの検証はできます。

ポイントは以下のとおりです。

2019年9月 thephpleague/openapi-psr7-validatorがリリースされる

この記事を書いている途中で気付いてしまったのですが、thephpleague/openapi-psr7-validatorという何とも期待の持てるライブラリがあったので、試してみました。
結果、期待どおり動いたので、それまでのJSON Schemaに変換してから検証する実装、関連ライブラリを全部捨ててthephpleague/openapi-psr7-validatorで実装し直しました。

実装

実装は下記のようになりました。

<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\TestResponse;
use Nyholm\Psr7\Factory\Psr17Factory;
use OpenAPIValidation\PSR7\Exception\ValidationFailed;
use OpenAPIValidation\PSR7\OperationAddress;
use OpenAPIValidation\PSR7\ValidatorBuilder;
use Symfony\Bridge\PsrHttpMessage\Factory\PsrHttpFactory;
use Tests\TestCase;

/**
 * Trait AssertResponseCompliantForSwaggerApiSpec
 * @package Tests\Feature
 *
 * @mixin TestCase
 */
trait OpenApiSpecAssertions
{
    /**
     * @param TestResponse $testResponse
     * @param string $method
     * @param string $path
     */
    protected function assertResponseCompliantForOpenApiSpec(
        TestResponse $testResponse,
        string $method,
        string $path
    ) {
        $validator = (new ValidatorBuilder())
            ->fromYamlFile(__DIR__. '/../ApiSpec/petstore-expanded.yaml')
            ->getResponseValidator()
        ;

        $operation = new OperationAddress($path, strtolower($method)) ;

        $psr17Factory = new Psr17Factory();
        $psrHttpFactory = new PsrHttpFactory($psr17Factory, $psr17Factory, $psr17Factory, $psr17Factory);
        $psrResponse = $psrHttpFactory->createResponse($testResponse->baseResponse);

        try {
            $validator->validate($operation, $psrResponse);
        } catch (ValidationFailed $validationFailed) {
            self::fail($validationFailed->getPrevious()->getMessage());
        }
    }

    /**
     * @param array $requestBody
     * @param string $method
     * @param string $uri
     */
    protected function assertRequestCompliantForOpenApiSpec(
        array $requestBody,
        string $method,
        string $uri
    ) {
        $validator = (new ValidatorBuilder())
            ->fromYamlFile(__DIR__. '/../ApiSpec/petstore-expanded.yaml')
            ->getRequestValidator()
        ;

        $psr17Factory = new Psr17Factory();
        $json = json_encode($requestBody, JSON_UNESCAPED_UNICODE);
        $stream = $psr17Factory->createStream($json);
        $stream->rewind();
        $request = $psr17Factory->createRequest(strtolower($method), $uri)
            ->withBody($stream)
            ->withHeader('Content-Type', 'application/json')
        ;

        try {
            $validator->validate($request);
        } catch (ValidationFailed $validationFailed) {
            self::fail($validationFailed->getPrevious()->getMessage());
        }
    }
}

ポイント

  • tests/ApiSpec/petstore-expanded.yamlOpenAPI v3形式のファイルを配置
  • OpenAPIValidation\PSR7\ValidatorBuilderfromYamlFilevalidatorを生成
  • PSR-7に準拠したRequest/Responseでなければいけないので、変換する必要がある
  • PSR-7形式への変換については、The PSR-7 Bridge (Symfony Docs)を参照
  • 仕様に違反した場合は、OpenAPIValidation\PSR7\Exception\ValidationFailedthrowされる
  • 今回はテストを失敗させるようにした

API仕様に違反すると

API仕様を変更して、テストを失敗させてみます。

任意項目だったNewPetおよびPettagというpropertyを必須(required)にして、テストを実行します。

openapi: "3.0.0"
## 中略 ##

components:
  schemas:
    Pet:
      allOf:
        - $ref: '#/components/schemas/NewPet'
        - type: object
          required:
          - id
          properties:
            id:
              type: integer
              format: int64

    NewPet:
      type: object
      required:
        - name
        - tag ## この行を追加
      properties:
        name:
          type: string
        tag:
          type: string

Keyword validation failed: Required property 'tag' must be present in the object

というメッセージとともに、テストが失敗します。

$ vendor/bin/phpunit
PHPUnit 8.3.5 by Sebastian Bergmann and contributors.

..FFF...                                                            8 / 8 (100%)

Time: 405 ms, Memory: 24.00 MB

There were 3 failures:

1) Tests\Feature\Pet\IndexTest::indexSuccess
Keyword validation failed: Required property 'tag' must be present in the object

/path/to/laravel-petstore-api/tests/Feature/OpenApiSpecAssertions.php:45
/path/to/laravel-petstore-api/tests/Feature/Pet/IndexTest.php:23

2) Tests\Feature\Pet\ShowTest::showSuccess
Keyword validation failed: Required property 'tag' must be present in the object

/path/to/laravel-petstore-api/tests/Feature/OpenApiSpecAssertions.php:45
/path/to/laravel-petstore-api/tests/Feature/Pet/ShowTest.php:24

3) Tests\Feature\Pet\StoreTest::storeSuccess
Keyword validation failed: Required property 'tag' must be present in the object

/path/to/laravel-petstore-api/tests/Feature/OpenApiSpecAssertions.php:76
/path/to/laravel-petstore-api/tests/Feature/OpenApiSpecAssertions.php:98
/path/to/laravel-petstore-api/tests/Feature/Pet/StoreTest.php:23

FAILURES!
Tests: 8, Assertions: 13, Failures: 3.

おわりに

API仕様に準拠しているか、実装が変わるたびに検証されていないと、しだいにAPI仕様が陳腐化し、結果、「今動いている実装が正しい」ということになりがちです。
もちろん、動いているAPIから仕様書を自動生成するというアプローチもありますが、以下のような問題があると思います。

  • API仕様を変更するのにPHPを触らなければいけない
  • API仕様をPHPのアノテーションで定義する形式だと、検査されずに陳腐化する可能性がある

なので、先にAPI仕様をOpenAPI v3形式で作成してから、クライアントチームと合意をとりながら開発を進めていくのが良いのかなと個人的には考えています。

この記事が、日々、多くのAPIを実装、保守しているサーバーサイドエンジニアの助けになれば幸いです。
ではでは。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel:Trait内で毎回呼び出す処理を実装できた

Traitってなんだ?はこちらの記事を参照。
https://qiita.com/mokkos/items/53d1d4cbf57bd6ceba5e

Trait に共通処理を実装している場合、常に呼び出したい処理ってありますよね。
公式ドキュメントに載っていなかったのでまとめました。

まず、laravel は Model の基盤を Eloquent というクラスで定義しています。
それにより、Model内で boot() を書けば・・・あら不思議。Eloquentが呼び出されたとき、自動的に関数が呼び出されます。

class Fugafuga extends Model
{
  public static function boot()
  {
   // __construct() が呼び出された後に実行されます
  }
}

深掘りして、Model.php をみてみると・・・・

/**
  * Boot all of the bootable traits on the model.
  *
  * @return void
  */
protected static function bootTraits()
{
    $class = static::class;

    foreach (class_uses_recursive($class) as $trait) {
        if (method_exists($class, $method = 'boot'.class_basename($trait))) {
            forward_static_call([$class, $method]);
        }
    }
}

が実装されていました。
つまり、Traitでも boot+(Trait名)って書くと勝手に初期に呼び出してくれることになります。

Trait Hogehoge
{
  public static function bootHogehoge()
  {
  }
}

このように実装することで、Hogehoge 内の関数が呼び出される直前に bootHogehoge() が呼び出されました!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravelで複数のデータベースを跨ぐテストを実施する際のトランザクションの設定

背景

LaravelのPHPUnitで

use DatabaseTransactions;

を使用すると1つのデータベースへのトランザクションしか受け付けてくれない。
複数のデータベースを跨ぐトランザクションの設定を実施するには

connectionsToTransact

を使用する。

具体例

use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;

class HogeTest extends TestCase
{
    use DatabaseTransactions;

    protected $connectionsToTransact = ['mysql1', 'mysql2'];

参考

Multiple databases and testing

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel6でtymon/jwt-auth

パッケージインストールでエラー

composer require tymon/jwt-authで以下のようにエラーが発生しました。

Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - Installation request for tymon/jwt-auth ^0.5.12 -> satisfiable by tymon/jwt-auth[0.5.12].
    - Conclusion: remove laravel/framework v6.1.0
    - Conclusion: don't install laravel/framework v6.1.0
    - tymon/jwt-auth 0.5.12 requires illuminate/http ~5.0 -> satisfiable by illuminate/http[5.0.x-dev, 5.1.x-dev, 5.2.x-dev, 5.3.x-dev, 5.4.x-dev, 5.5.x-dev, 5.6.x-dev, 5.7.17, 5.7.18, 5.7.19, 5.7.x-dev, 5.8.x-dev, v5.0.0, v5.0.22, v5.0.25, v5.0.26, v5.0.28, v5.0.33, v5.0.4, v5.1.1, v5.1.13, v5.1.16, v5.1.2, v5.1.20, v5.1.22, v5.1.25, v5.1.28, v5.1.30, v5.1.31, v5.1.41, v5.1.6, v5.1.8, v5.2.0, v5.2.19, v5.2.21, v5.2.24, v5.2.25, v5.2.26, v5.2.27, v5.2.28, v5.2.31, v5.2.32, v5.2.37, v5.2.43, v5.2.45, v5.2.6, v5.2.7, v5.3.0, v5.3.16, v5.3.23, v5.3.4, v5.4.0, v5.4.13, v5.4.17, v5.4.19, v5.4.27, v5.4.36, v5.4.9, v5.5.0, v5.5.16, v5.5.17, v5.5.2, v5.5.28, v5.5.33, v5.5.34, v5.5.35, v5.5.36, v5.5.37, v5.5.39, v5.5.40, v5.5.41, v5.5.43, v5.5.44, v5.6.0, v5.6.1, v5.6.10, v5.6.11, v5.6.12, v5.6.13, v5.6.14, v5.6.15, v5.6.16, v5.6.17, v5.6.19, v5.6.2, v5.6.20, v5.6.21, v5.6.22, v5.6.23, v5.6.24, v5.6.25, v5.6.26, v5.6.27, v5.6.28, v5.6.29, v5.6.3, v5.6.30, v5.6.31, v5.6.32, v5.6.33, v5.6.34, v5.6.35, v5.6.36, v5.6.37, v5.6.38, v5.6.39, v5.6.4, v5.6.5, v5.6.6, v5.6.7, v5.6.8, v5.6.9, v5.7.0, v5.7.1, v5.7.10, v5.7.11, v5.7.15, v5.7.2, v5.7.20, v5.7.21, v5.7.22, v5.7.23, v5.7.26, v5.7.27, v5.7.28, v5.7.3, v5.7.4, v5.7.5, v5.7.6, v5.7.7, v5.7.8, v5.7.9, v5.8.0, v5.8.11, v5.8.12, v5.8.14, v5.8.15, v5.8.17, v5.8.18, v5.8.19, v5.8.2, v5.8.20, v5.8.22, v5.8.24, v5.8.27, v5.8.28, v5.8.29, v5.8.3, v5.8.30, v5.8.31, v5.8.32, v5.8.33, v5.8.34, v5.8.35, v5.8.4, v5.8.8, v5.8.9].
    - don't install illuminate/http 5.0.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.1.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.2.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.3.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.4.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.5.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.6.x-dev|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.7.17|don't install laravel/framework v6.1.0
    - don't install illuminate/http 5.7.18|don't install laravel/framework v6.1.0
..........

解決

composer require tymon/jwt-auth ^1.0.0

参考

https://github.com/tymondesigns/jwt-auth/issues/1869

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む