20210322のlaravelに関する記事は12件です。

LaravelのSeederを利用して、テストデータを生成する方法(1対多, 多対多)

テストデータを生成したい!!

テストデータを生成しておけば、リストをいちいち生成しないでいいので非常に楽になります。

Seederの書き方はパターンがあるので、今回は技術の棚卸しとして、Seederの書き方をアウトプットして整理しておきたいと思います。

「では、早速やっていきましょう!!」

1対多

1対多の場合はシンプルにリレーションを使って作成します。

リレーションでcreate()
$post->likes()->create([
    'user_id' => $users[array_rand($users)],
]);

これで$postの子モデルであるlikeモデルをcreateします。
いいね!機能の場合は、複数のいいねを生成したいので、下記のような例になります。

いいねをseederで作成する例
<?php

use Illuminate\Database\Seeder;
use App\Models\Like;

class LikeSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {

        $posts = App\Models\Post::all();
        $users = App\User::pluck('id')->all();
        /* $postsを繰り返し処理を実施する。ただし$usersは複数系で利用したいので、useでそのまま利用する。 */
        $posts->each(function ($post) use ($users) {
            /* rand($min, $max) で、一つの$postに対して10~1000個のいいねを生成する */
            for ($count = 0; $count < rand(10, 1000); $count++) {
                $post->likes()->create([
                    /* array_randで$usersの中からランダムで1つ取得する */
                    'user_id' => $users[array_rand($users)],
                ]);
            }
        });
    }
}

createMany()で生成する場合

コメントを生成する場合
<?php

use Illuminate\Database\Seeder;
use App\Models\Post;

class StrengthAppealTextSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        $post = Post::find(1);
        $post->comments()->createMany([
            [
                "text" => "これだとどれに紐づいてるかパッとみてわかるよねえ"
            ],
            [
                "text" => "みやすい場合もあるよねえ"
            ],
            [
                "text" => "これが投稿されるよ。"
            ],
        ]);
    }
}

多対多

多対多のSeederはどうすればよいでしょうか?
少し悩みどころですよね。
ここでは、例を紹介していきます

リレーションを活用する方法

リレーションを呼び出して、create()する方法があります。

$tableA->tableBs()->create([
  "text" => "aaaa"
]);

こんな感じでリレーションを活用して、紐ける方法があります。
少し具体的に見てみましょう。

具体例
/* Factoryを利用してランダムに生成 */
$As = TableA::all();

/* 複数得られた$Asに対して、それぞれTableBを紐付ける*/
foreach ($As as $A) {
   $A->questions()->saveMany(factory(TableB::class, 15)->create()); //createManyでもできます
}

多対多はsaveMany, createMnayで一括で作成することができます。生成する中身をfactoryで定義しておけばこれだけで作成できます。

中間テーブルをfactoryで生成する方法

多対多なので、中間テーブルにランダムにIDを紐づけて、多対多の情報を作成する方法です。
「Seederで複雑な処理はせずに,factoryで外部キーを設定して作成すれば簡単じゃない?」というやり方です。

複数タグの例:PostTagFactory.php
<?php

/** @var \Illuminate\Database\Eloquent\Factory $factory */

use App\Models\post_tag;
use App\Models\Post;
use Faker\Generator as Faker;

$factory->define(post_tag::class, function (Faker $faker) {
    /* 中間テーブルに紐付けるIDを取得する*/
    $postIDs  = App\Models\Post::pluck('id')->all();
    $tagIDs  = App\Models\Tag::pluck('id')->all();

    /* 中間テーブルを作成する*/
    return [
        'post_id' => $faker->randomElement($postIDs), // ランダムでIDを選択
        'tag_id' => $faker->randomElement($tagIDs)    // ランダムでIDを選択
    ];
});

あとはSeederを実行して、生成します。

PostTagSeeder.php
<?php

use Illuminate\Database\Seeder;
use App\Models\Post_tag;

class PostTagSeeder extends Seeder
{
    /**
     * Run the database seeds.
     *
     * @return void
     */
    public function run()
    {
        factory(Post_tag::class, 200)->create();
    }
}

最後に

いかがだったでしょうか
実例を交えたので、非常に参考になったのではないでしょうか?
内容が面白かった!!参考になった!!という方は

LGTMをお願い致します<_ _>

LGTMもらえるとルンルン♪になるんですよね
超励みになります
めっちゃ待ってます
LGTMくださあああああああああああああい

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

いいねとリプライ

コメント機能

ルーティング

routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {
 // 省略
 // コメント関連
 Route::resource('comments', 'CommentsController', ['only' => ['store']]);
});

Model

app/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

php artisan make:controller CommentsController --resource
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();
 }
}

いいね機能

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

ルーティング

routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {
 // 省略
 // いいね関連
 Route::resource('favorites', 'FavoritesController', ['only' => ['store', 'destroy']]);
});

Model

app/Favorite.php
<?php
namespace App;
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();
 }
}

いいねを押した際にツイートに対して既にいいね済みであればfalse、逆に存在しなければtrue

Controller

php artisan make:controller FavoritesController --resource
app/Http/Controllers/FavoritesController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\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();
 }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

str_random()が使用できなかった件

環境

Laravel 7.3.1

経緯

ログイン機能実装する上でstr_random(10)を使用。
作成後エラーが発生。

下記のブログを参考。
https://oversealife.work/2019/09/11/laravel-6-helper/

composer require laravel/helpers

ヘルパーのライブラリをインストール。

str_random(10)

補足

Str::random(10)

でも使用できる。

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

ログイン機能で詰まった箇所のメモ

Laravle6.x/7.x/8.xのログイン実装コマンドの違い

Laravel6 / Laravel7 / Laravel8 ログイン画面作成

1. laravel/uiをインストール
ターミナル
#Laravel6.x 
composer require laravel/ui:^1.0 --dev
#laravel7の場合
composer require laravel/ui:^2.4
#laravel8の場合
composer require laravel/ui

2. ログイン機能作成

ターミナル
php artisan ui vue --auth

参考

【ver6.x】https://laravel.com/docs/7.x/frontend#introduction
【ver7.x】https://laravel.com/docs/6.x/frontend#introduction
【ver8.x】https://laravel.com/docs/8.x/upgrade#updating-dependencies

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

ログイン機能とフォロー機能

Controllerを作成

ResourceController

Controllerファイルを生成する際--resourceと付けることでResourceに対応したControllerを自動的に生成。

HTTP動詞 URL アクション 役割
GET /tweets index 一覧表示
GET /tweets/create create 新規ツイート入力画面
POST /tweets store 新規ツイート投稿処理
GET /tweets/{tweet}/show show ツイート詳細画面
GET /tweets/{tweet}/edit edit ツイート編集画面
PUT/PATCH /tweets/{tweet} update ツイート編集処理
DELETE /tweets/{tweet} destory ツイート削除処理
TweetsConroller.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TweetsConroller extends Controller
{
 // 一覧表示
 public function index()
 {
 //
 }
 // 新規ツイート入力画面
 public function create()
 {
 //
 }
 // 新規ツイート投稿処理
 public function store(Request $request)
 {
 //
 }
 // ツイート詳細画面
 public function show($id)
 {
 //
 }
 // ツイート編集画面
 public function edit($id)
 {
 //
 }
 // ツイート編集処理
 public function update(Request $request, $id)
 {
 //
 }
 // ツイート削除処理
 public function destroy($id)
 {
 //
 }
}

UsersController

php artisan make:controller UsersController --resource

Routing

routes/web.php
<?php
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
 return view('welcome');
});
Auth::routes();
Route::get('/home', 'HomeController@index')->name('home');
// ログイン状態
Route::group(['middleware' => 'auth'], function() {
 // ユーザ関連
 Route::resource('users', 'UsersController');
});

このクロージャーの中にルートを設定することでログインした時にしかアクセス出来ないようにする。

Route::group(['middleware' => 'auth'], function() {
 //
});
Route::resource('users', 'UsersController');

ユーザ機能では一覧/詳細/編集/更新のみを使用するので第3引数にonlyと記述して使うアクションのみを設定。
php
Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'edit', 'update']]);

ユーザを取得

ユーザ一覧表示画面

Controller

app/Http/Controllers/UsersController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use App\Models\User;
use App\Models\Tweet;
use App\Models\Follower;
class UsersController extends Controller
{
 public function index(User $user)
 {
 $all_users = $user->getAllUsers(auth()->user()->id);
 return view('users.index', [
 'all_users' => $all_users
 ]);
 }
}
メソッドインジェクション

LaravelにはDI(依存性の注入)というのが内蔵されており、メソッドの引数にインジェクトしたいオブジェクトを書くだけで、そのインスタンスが使用できる。

public function index(User $user)
{
 $all_users = $user->getAllUsers(auth()->user()->id);
}

Model

app/Models/User.php
 public function getAllUsers(Int $user_id)
 {
 return $this->Where('id', '<>', $user_id)->paginate(5);
 }

View

resources/views/users/index.blade.php
@extends('layouts.app')
@section('content')
 <div class="container">
 <div class="row justify-content-center">
 <div class="col-md-8">
 @foreach ($all_users as $user)
 <div class="card">
 <div class="card-haeder p-3 w-100 d-flex">
 <img src="{{ $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>
 @endforeach
 </div>
 </div>
 <div class="my-4 d-flex justify-content-center">
 {{ $all_users->links() }}
 </div>
 </div>
@endsection

フォロー関連の処理

フォロー/フォロー解除/フォローされているか

Controller

フォローとフォロー解除はresourceのどのアクションにも該当しないので、独自に追加。

app/Http/Controllers/UsersController.php
 // フォロー
 public function follow(User $user)
 {
 $follower = auth()->user();
 // フォローしているか
 $is_following = $follower->isFollowing($user->id);
 if(!$is_following) {
 // フォローしていなければフォローする
 $follower->follow($user->id);
 return back();
 }
 }
 // フォロー解除
 public function unfollow(User $user)
 {
 $follower = auth()->user();
 // フォローしているか
 $is_following = $follower->isFollowing($user->id);
 if($is_following) {
 // フォローしていればフォローを解除する
 $follower->unfollow($user->id);
 return back();
 }
 }

Model

app/User.php
 // フォローする
 public function follow(Int $user_id)
 {
 return $this->follows()->attach($user_id);
 }
 // フォロー解除する
 public function unfollow(Int $user_id)
 {
 return $this->follows()->detach($user_id);
 }
 // フォローしているか
 public function isFollowing(Int $user_id)
 {
 return (boolean) $this->follows()->where('followed_id', $user_id)->first(['id']);
 }
 // フォローされているか
 public function isFollowed(Int $user_id)
 {
 return (boolean) $this->followers()->where('following_id', $user_id)->first(['id']);
 }

フォローされているかの判定

resources/views/users/index.blade.php
@if (auth()->user()->isFollowed($user->id))

フォローしているかの判定

resources/views/users/index.blade.php
@if (auth()->user()->isFollowing($user->id))

Routing

routes/web.php
// ログイン状態
Route::group(['middleware' => 'auth'], function() {
 // ユーザ関連
 Route::resource('users', 'UsersController', ['only' => ['index', 'show', 'edit', 'update']]);
 // フォロー/フォロー解除を追加
 Route::post('users/{user}/follow', 'UsersController@follow')->name('follow');
 Route::delete('users/{user}/unfollow', 'UsersController@unfollow')->name('unfollow');
});

ユーザ詳細画面

  • プロフィール
  • プロフィールが自身だった時に編集ボタンを追加
  • プロフィールが自身以外のユーザだった時にフォロー/フォロー解除/フォローされているかの判定を追加
  • 総ツイート数/フォロー数/フォロワー数の表示
  • ユーザがツイートしたタイムラインの表示

Controller

app/Http/Controllers/UsersController.php
 public function show(User $user, Tweet $tweet, Follower $follower)
 {
 $login_user = auth()->user();
 $is_following = $login_user->isFollowing($user->id);
 $is_followed = $login_user->isFollowed($user->id);
 $timelines = $tweet->getUserTimeLine($user->id);
 $tweet_count = $tweet->getTweetCount($user->id);
 $follow_count = $follower->getFollowCount($user->id);
 $follower_count = $follower->getFollowerCount($user->id);
 return view('users.show', [
 'user' => $user,
 'is_following' => $is_following,
 'is_followed' => $is_followed,
 'timelines' => $timelines,
 'tweet_count' => $tweet_count,
 'follow_count' => $follow_count,
 'follower_count' => $follower_count
 ]);
 }

Model

Tweet
app/Tweet.php
 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();
 }
Follower
app/Follower.php
 public function getFollowCount($user_id)
 {
 return $this->where('following_id', $user_id)->count();
 }
 public function getFollowerCount($user_id)
 {
 return $this->where('followed_id', $user_id)->count();
 }

ユーザ編集

Controller

$requestで取得したデータをValidator::makeを使ってバリデーションをかける。
Rule::unique('users')->ignore($user->id)の部分はユニークに設定しているscreen_nameemailを自身のIDの時だけ無効にする設定。

app/Http/Controllers/UsersController.php
 public function edit(User $user)
 {
 return view('users.edit', ['user' => $user]);
 }
 public function update(Request $request, User $user)
 {
 $data = $request->all();
 $validator = Validator::make($data, [
 'screen_name' => ['required', 'string', 'max:50', Rule::unique('users')->ignore($user->id)],
 'name' => ['required', 'string', 'max:255'],
 'profile_image' => ['file', 'image', 'mimes:jpeg,png,jpg', 'max:2048'],
 'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($user->id)]
 ]);
 $validator->validate();
 $user->updateProfile($data);
 return redirect('users/'.$user->id);
 }
Model

$paramsの中に画像があれば処理を分けています。
$file_name = $params['profile_image']->store('public/profile_image/');
とすることで画像ファイルが/storage/app/public/profile_image/に保存される。

app/User.php
 public function updateProfile(Array $params)
 {
 if (isset($params['profile_image'])) {
 $file_name = $params['profile_image']->store('public/profile_image/');
 $this::where('id', $this->id)
 ->update([
 'screen_name' => $params['screen_name'],
 'name' => $params['name'],
 'profile_image' => basename($file_name),
 'email' => $params['email'],
 ]);
 } else {
 $this::where('id', $this->id)
 ->update([
 'screen_name' => $params['screen_name'],
 'name' => $params['name'],
 'email' => $params['email'],
 ]);
 }
 return;
 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Twitterクローン DB設計

環境

  • Windows
  • Homestead
  • Laravel 7.3.1

要件

  • ログインユーザのみ利用可能
  • ツイート投稿機能
  • リプライといいね機能
  • フォロー機能

DB設計

  • usersテーブル
    • ユーザ管理テーブル
  • tweetsテーブル
    • ユーザ毎のツイート管理テーブル
  • commentsテーブル
    • コメント管理テーブル
  • favoritesテーブル
    • いいね管理テーブル
  • followersテーブル
    • フォロー関係管理テーブル

Migrationファイルの作成

make:model 〇〇 -m とすることでModelとMigrationを同時作成。

php artisan make:model Tweet -m
php artisan make:model Comment -m
php artisan make:model Favorite -m
php artisan make:model Models/Follower -m

Users

2014_10_12_000000_create_users_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateUsersTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create('users', function (Blueprint $table) {
 $table->increments('id');
 $table->string('screen_name')->unique()->null()->comment('アカウント名');
 $table->string('name')->null()->comment('ユーザ名');
 $table->string('profile_image')->nullable()->comment('プロフィール画像');
 $table->string('email')->unique();
 $table->timestamp('email_verified_at')->nullable();
 $table->string('password');
 $table->rememberToken();
 $table->timestamps();
 });
 }
 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::dropIfExists('users');
 }
}

Tweets

2020_10_24_2358_create_tweets_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateTweetsTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create('tweets', function (Blueprint $table) {
 $table->increments('id');
 $table->unsignedInteger('user_id')->comment('ユーザID');
 $table->string('text')->comment('本文');
 $table->softDeletes();
 $table->timestamps();
 $table->index('id');
 $table->index('user_id');
 $table->index('text');
 $table->foreign('user_id')
 ->references('id')
 ->on('users')
 ->onDelete('cascade')
 ->onUpdate('cascade');
 });
 }
 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::dropIfExists('tweets');
 }
}

以下でUsersテーブルと外部キー接続を宣言。

2020_10_24_2358_create_tweets_table.php
 $table->foreign('user_id')
 ->references('id')
 ->on('users')
 ->onDelete('cascade')
 ->onUpdate('cascade');

Comments

2020_10_24_2358_create_comments_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateCommentsTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create('comments', function (Blueprint $table) {
 $table->increments('id');
 $table->unsignedInteger('user_id')->comment('ユーザID');
 $table->unsignedInteger('tweet_id')->comment('ツイートID');
 $table->string('text')->comment('本文');
 $table->softDeletes();
 $table->timestamps();
 $table->index('id');
 $table->index('user_id');
 $table->index('tweet_id');
 $table->foreign('user_id')
 ->references('id')
 ->on('users')
 ->onDelete('cascade')
 ->onUpdate('cascade');
 $table->foreign('tweet_id')
 ->references('id')
 ->on('tweets')
 ->onDelete('cascade')
 ->onUpdate('cascade');
 });
 }
 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::dropIfExists('comments');
 }
}

Favorites

2020_10_24_2358_create_favorites_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFavoritesTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create('favorites', function (Blueprint $table) {
 $table->increments('id');
 $table->unsignedInteger('user_id')->comment('ユーザID');
 $table->unsignedInteger('tweet_id')->comment('ツイートID');
 $table->index('id');
 $table->index('user_id');
 $table->index('tweet_id');
 $table->unique([
 'user_id',
 'tweet_id'
 ]);
 $table->foreign('user_id')
 ->references('id')
 ->on('users')
 ->onDelete('cascade')
 ->onUpdate('cascade');
 $table->foreign('tweet_id')
 ->references('id')
 ->on('tweets')
 ->onDelete('cascade')
 ->onUpdate('cascade');
 });
 }
 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::dropIfExists('favorites');
 }
}

Followersテーブル

自分がフォローしているユーザのツイートをTLに表示する際は
自分がfollowing_idで相手がfollowed_idとなる。

2020_10_24_2358_create_followers_table.php
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateFollowersTable extends Migration
{
 /**
 * Run the migrations.
 *
 * @return void
 */
 public function up()
 {
 Schema::create('followers', function (Blueprint $table) {
 $table->unsignedInteger('following_id')->comment('フォローしているユーザID');
 $table->unsignedInteger('followed_id')->comment('フォローされているユーザID');
 $table->index('following_id');
 $table->index('followed_id');
 $table->unique([
 'following_id',
 'followed_id'
 ]);
 });
 }
 /**
 * Reverse the migrations.
 *
 * @return void
 */
 public function down()
 {
 Schema::dropIfExists('followers');
 }
}

Model

Users

app/User
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
 use Notifiable;
 /**
 * The attributes that are mass assignable.
 *
 * @var array
 */
 protected $fillable = [
 'screen_name',
 'name',
 'profile_image',
 'email',
 'password'
 ];
}

Tweets

app/Tweet.php
<?php
namespace App;
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'
 ];
}

Comments

app/Comment.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\softDeletes;
class Comment extends Model
{
 use SoftDeletes;
 /**
 * The attributes that are mass assignable.
 *
 * @var array
 */
 protected $fillable = [
 'text'
 ];
}

Favorites

app/Favorite.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Favorite extends Model
{
 public $timestamps = false;
}

Followers

app/Follower.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Follower extends Model
{
 protected $primaryKey = [
 'following_id',
 'followed_id'
 ];
 protected $fillable = [
 'following_id',
 'followed_id'
 ];
 public $timestamps = false;
 public $incrementing = false;
}

リレーションの親子関係

Users

app/User.php
 //追記
 public function followers()
 {
 return $this->belongsToMany(self::class, 'followers', 'followed_id', 'following_id');
 }
 public function follows()
 {
 return $this->belongsToMany(self::class, 'followers', 'following_id', 'followed_id');
 }

Tweets

app/Tweet.php
<?php
namespace App;
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);
 }
}

Comments

app/Comment.php
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\softDeletes;
class Comment extends Model
{
 use SoftDeletes;
 /**
 * The attributes that are mass assignable.
 *
 * @var array
 */
 protected $fillable = [
 'text'
 ];
 public function user()
 {
 return $this->belongsTo(User::class);
 }
}

Migration

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

Laravelのクエリビルダ(Eloquent)のまとめ

LaravelでのSQLの書き方がごっちゃになってきたので、まとめる。

Raw

$users = DB::select('select * from users')  

insert, update, delete でも同様

クエリビルダ

$email = DB::table('email')
   ->join('telephone',  'email.id', '=', 'telephone.id') //combining methods
   ->where('vip', true) //constraing methods
   ->orderBy('last_name','desc') //modifiyng methods
   ->get() //ending methods

combining methods

  • join
  • union

constraining methods

  • select
  • where
  • orWhere
  • whereBetween
  • whereIn
  • whereNull
  • whereExists
  • distinct

modyfing methods

  • orderBy
  • groupBy and having
  • skip and take
  • latest/oldest
  • inRandomOrder

conditional methods

  • when
  • unless

ending methods

  • get
  • first
  • find
  • value
  • count
  • min/max
  • sum/avg
  • all(ただしこれは、Eloquentのときだけ)

まとめ

特にgetを忘れてエラーを出しやすい。
findやallのときはいらないが、それ以外の時は基本いるものと考えたほうが良い。

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

一つのユーザーテーブルを複数のモデルとガードに分ける

背景

ロールを元にユーザーテーブルのガードを分けたかった。

実践的には、ガードを分けるよりも、一つのガードでログインした後に権限でアクセス制御したほうがいいと思う。あくまでも実験的なものとして覚え書きを残す。

動作環境

  • Mac OS X 10.15.7
  • Laravel 8.33.1
  • Laravel Permission 4.0.0

プロジェクトの作成

Laravel-Permission の導入

composer require spatie/laravel-permission
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider"

モデルの作成

php artisan make:model Student -f
php artisan make:model Teacher -f

app/Models/Student.php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Spatie\Permission\Traits\HasRoles;

class Student extends Model
{
    use HasFactory;
    use HasRoles;

    protected $table = 'users';

    protected $fillable = ['name', 'email', 'password'];

    protected $hidden = ['password', 'remember_token'];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    protected static function booted(): void
    {
        static::creating(function ($student) {
            $student->assignRole('student');
        });

        static::addGlobalScope('role', function (Builder $builder) {
            $builder->role('student');
        });
    }
}

app/Models/Teacher.php

namespace App\Models;

use Spatie\Permission\Traits\HasRoles;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Teacher extends Model
{
    use HasFactory;
    use HasRoles;

    protected $table = 'users';

    protected $fillable = ['name', 'email', 'password'];

    protected $hidden = ['password', 'remember_token'];

    protected $casts = [
        'email_verified_at' => 'datetime',
    ];

    protected static function booted(): void
    {
        static::creating(function ($teacher) {
            $teacher->assignRole('teacher');
        });

        static::addGlobalScope('role', function (Builder $builder) {
            $builder->role('teacher');
        });
    }
}

User に書いてそれぞれ継承でもいいと思う。

ガードの設定

プロパイダーの設定を auth.providers に追記する。

'students' => [
    'driver' => 'eloquent',
    'model' => App\Models\Student::class,
],
'teachers' => [
    'driver' => 'eloquent',
    'model' => App\Models\Teacher::class,
],

ガードの設定を auth.guards に追記する。

'student' => [
    'driver' => 'session',
    'provider' => 'students',
],
'teacher' => [
    'driver' => 'session',
    'provider' => 'teachers',
],

ファクトリの作成

UserFactorydefinition() をコピーして、StudentFactoryTeacherFactory を用意する。

ロールの投入

マイグレーションして、Role を用意する。

php artisan migrate
php artisan permission:create-role student student
php artisan permission:create-role teacher teacher

動作確認

Tinker で動作確認する。

php artisan tinker

Student, Teacher を作成して、それぞれ別々に取得できるのを確かめる。

Student::factory(3)->create();
Teacher::factory(3)->create();
Student::all();
Teacher::all();

補足:ガードを追加したくない場合

Laravel-Permission はガードに紐づいているプロバイダーの model を元にロールを絞り込む。
ガードを新たに追加しなくても、モデルに public function guardName()protected $guard_name を明示すると特定のガードを流用できる。

この方法を選んだ場合、「ロールの投入」でも第二引数の guard_name を合わせること。

参考資料

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

Laravel と MessagePack で遊ぶ

動作環境

  • Mac OS X 10.15.7
  • Laravel 7.x|8.x
  • rybakit/msgpack 0.8
  • msgpack-tools 0.6

プロジェクトの作成

composer require rybakit/msgpack

レスポンスの作成

routes/web.php

use MessagePack\Packer;

Route::get('/example.mp', function () {
    $value = (new Packer())->pack([
        'id' => 1,
        'name' => 'John Doe',
        'age' => 18,
    ]);
    return response($value)->header('Content-Type', 'application/x-msgpack');
});

ターミナルで確認

brew install msgpack-tools
php artisan serve
curl http://localhost:8000/example.mp | msgpack2json -d

レスポンスマクロの作成

php artisan make:provider MessagePackResponseServiceProvider

app/Providers/MessagePackResponseServiceProvider.php

namespace App\Providers;

use MessagePack\Packer;
use Illuminate\Support\ServiceProvider;
use Illuminate\Support\Facades\Response;

class MessagePackResponseServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Response::macro('mpac', function ($value) {
            return response((new Packer())->pack($value))->header(
                'Content-Type',
                'application/x-msgpack'
            );
        });
    }
}

config/auth.php

return [
    'providers' => [
        # ...
        App\Providers\MessagePackResponseServiceProvider::class,
    ],
];

routes/web.php

Route::get('/example.mp', function () {
    return response()->mpac([
        'id' => 1,
        'name' => 'John Doe',
        'age' => 20,
    ]);
});

ターミナルで確認

curl http://localhost:8000/example.mp | msgpack2json -d

Model を渡せるようにする

toArray() があれば変換しておくと Eloquent Model もそのまま渡せる。

Response::macro('mpac', function ($value) {
    if (method_exists($value, 'toArray')) {
        $value = $value->toArray();
    }
    return response((new Packer())->pack($value))->header(
        'Content-Type',
        'application/x-msgpack'
    );
});

資料

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

Laravelで入力したフォーム値をDBに登録する

簡単な入力フォームを作り、DBに登録までを行う

1. ルーティング設定

web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/information', 'HogeController@create');

Route::post('/add', 'HogeController@add');

?>

2. テーブルの準備

$ php artisan make:migration create_hoge_table

テーブルを作成後、database/migrations配下にあるcreate_hoge_tableを確認する。

create_hoge_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateHogeTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('hoge', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->char('title', 255);
            $table->char('body', 255);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('hoge');
    }
}

その後、artisanコマンドで下記を実行

$ php artisan migrate

下記画像のように、DBにテーブルが登録されていればOK!!

スクリーンショット 2021-03-21 22.35.03.png

3. モデル作成

$ php artisan make:model Models/hoge
Hoge.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Hoge extends Model
{
    protected $table = 'hoge';
    protected $fillable = ['title', 'body'];
    protected $dates =  ['created_at', 'updated_at'];
}

$fillableとは?

$fillable(代入可能)に配列を設定した上でprotectedすれば、配列内のカラムのみcreate()やupdate() 、fill()が可能になる。

4. Controller

artisanコマンドでController.phpを作成する。

$ php artisan make:controller HogeController
HogeController.php
<?php

namespace App\Http\Controllers;

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

class HogeController extends Controller
{
    public function create() {
        return view('create');
      }

    public function add(Request $request) 
    { 
        $post = new Hoge();
        $post->title = $request->title;
        $post->body = $request->body;
        $post->save();

        return redirect('/information');
     }

}

5. viewの作成

create.blade.php
<head>
  <meta charset="utf-8">
</head>

<form method="post" action="{{ url('/add') }}">
    {{ csrf_field() }}
    <p>
      <input type="text" name="title" placeholder="名前を入力してください">
    </p>
    <p>
      <textarea name="body" placeholder="生年月日を入力してください"></textarea>
    </p>
    <p>
      <input type="submit" value="Add">
    </p>
  </form>

上記のソースだと下記の画像のような、画面が表示される。
スクリーンショット 2021-03-21 23.49.25.png

  • formタグのactionでurlという機能を呼び出しているがこれはソースコードの下の方にある「追加」ボタンを押したあとに遷移する飛び先になる。

  • {{ csrf_field() }}を宣言することにより、POST, PUT, DELETEのリクエストをした際に認証済みユーザーのトークンと一致するか自動的にチェックしてくれる。

6. DBに入力した値が反映されていることを確認する

上記URLにアクセスし、任意の値を入力する。

スクリーンショット 2021-03-22 0.01.52.png

その後、入力したフォームの内容がDBに登録されていることを確認できた!
スクリーンショット 2021-03-22 0.03.31.png

http://127.0.0.1:8000/informationにアクセスした時にHogeControllerのcreateメソッドが呼ばれ、メソッド内のreturn view('create');でcreate.blade.phpを表示させている。

その後、画面上のAddを押下したときにHogeControllerのaddメソッドが呼ばれ、DBに登録ができた。

参考サイト

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

入力したフォーム値をDBに登録する

簡単な入力フォームを作り、DBに登録までを行う

1. ルーティング設定

web.php
<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('/information', 'HogeController@create');

Route::post('/add', 'HogeController@add');

?>

2. テーブルの準備

$ php artisan make:migration create_hoge_table

テーブルを作成後、database/migrations配下にあるcreate_hoge_tableを確認する。

create_hoge_table.php
<?php

use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateHogeTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('hoge', function (Blueprint $table) {
            $table->bigIncrements('id');
            $table->char('title', 255);
            $table->char('body', 255);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('hoge');
    }
}

その後、artisanコマンドで下記を実行

$ php artisan migrate

下記画像のように、DBにテーブルが登録されていればOK!!

スクリーンショット 2021-03-21 22.35.03.png

3. モデル作成

$ php artisan make:model Models/hoge
Hoge.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Hoge extends Model
{
    protected $table = 'hoge';
    protected $fillable = ['title', 'body'];
    protected $dates =  ['created_at', 'updated_at'];
}

$fillableとは?

$fillable(代入可能)に配列を設定した上でprotectedすれば、配列内のカラムのみcreate()やupdate() 、fill()が可能になる。

4. Controller

artisanコマンドでController.phpを作成する。

$ php artisan make:controller HogeController
HogeController.php
<?php

namespace App\Http\Controllers;

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

class HogeController extends Controller
{
    public function create() {
        return view('create');
      }

    public function add(Request $request) 
    { 
        $post = new Hoge();
        $post->title = $request->title;
        $post->body = $request->body;
        $post->save();

        return redirect('/information');
     }

}

5. viewの作成

create.blade.php
<head>
  <meta charset="utf-8">
</head>

<form method="post" action="{{ url('/add') }}">
    {{ csrf_field() }}
    <p>
      <input type="text" name="title" placeholder="名前を入力してください">
    </p>
    <p>
      <textarea name="body" placeholder="生年月日を入力してください"></textarea>
    </p>
    <p>
      <input type="submit" value="Add">
    </p>
  </form>

上記のソースだと下記の画像のような、画面が表示される。
スクリーンショット 2021-03-21 23.49.25.png

  • formタグのactionでurlという機能を呼び出しているがこれはソースコードの下の方にある「追加」ボタンを押したあとに遷移する飛び先になる。

  • {{ csrf_field() }}を宣言することにより、POST, PUT, DELETEのリクエストをした際に認証済みユーザーのトークンと一致するか自動的にチェックしてくれる。

6. DBに入力した値が反映されていることを確認する

上記URLにアクセスし、任意の値を入力する。

スクリーンショット 2021-03-22 0.01.52.png

その後、入力したフォームの内容がDBに登録されていることを確認できた!
スクリーンショット 2021-03-22 0.03.31.png

http://127.0.0.1:8000/informationにアクセスした時にHogeControllerのcreateメソッドが呼ばれ、メソッド内のreturn view('create');でcreate.blade.phpを表示させている。

その後、画面上のAddを押下したときにHogeControllerのaddメソッドが呼ばれ、DBに登録ができた。

参考サイト

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

Laravelで検索機能実装時に「リダイレクトが繰り返し行われました。」と表示された件

検索機能作成時に「リダイレクトが繰り返し行われました。」とエラーが発生!

自分なりに調べてみましたが、核心を得た答えに辿り着くことができず、なかなかに苦労したので備忘録として残します。

まずは検索フォームのコードです。

Formファサードを利用しているので以下のコードになります。

index.blade.php
{!! Form::open(['route' => 'report.index', 'method' => 'GET']) !!}
  {!! Form::text('search', '', ['class' => 'form-control', 'type' => 'search', 'placeholder' => '----年--月']) !!}
  {!! Form::button('<i class="fa fa-search"></i>', ['class' => 'btn btn-icon', 'type' => 'submit']) !!}
{!! Form::close() !!}

検索フォームに入力された値をindexアクションへGET送信します。
送信された値はRequest $requestで受け取ります。

続いてはコントローラー内のindexアクションの記述になります。

DailyReportController.php
public function index(DailyReportRequest $request)
{
    $search = $request->input('search');
    dd($search)
    return view('user.daily_report.index', compact('dailyReports'));
}

formから送信された値を$requestで受け取ります。

ここではLaravelに既存で搭載されているサービスプロバイダという機能を使って、自動でRequestクラスから$requestインスタンスを作成してくれています。

詳しくは以下の記事を参考にしてください。
hoge(Request $request)ってなんだ???

$requestに送信された値をinputメソッドで受け取り、変数$searchへ代入。
dd($search)で送信された値を確認しようとしました。

しかし...ここでエラーが発生!!

スクリーンショット 2021-03-21 23.17.08.png

リダイレクトが繰り返し行われているとな、、、?

念のためデベロッパーツールのNetworkでも確認してみます。
スクリーンショット 2021-03-21 23.18.26.png

やはりリダイレクトが起きている様子...!

ちなみに「status」の「302」はHTTPステータスコード「302リダイレクト」というものでその名の通りリダイレクト発生を示しています。

今回のケースではコントローラー内のindexアクションのdd($search)$searchの中身が表示され、処理が止まるはずなのですが、先ほどのエラー画面が表示されています。
このことからdd($search)まで処理が来ておらず、DailyReportRequest$requestの箇所に原因があることがわかります。

ここまでは特定できた、、、、、
だが、今回のエラーを自分なりに調べてみたが、解決策を見つけることができず、、、、

自分の調べ方が悪いのかと、色々考えを巡らせましたが、どうにもこうにもいかない、、、
最終的に先輩社員に質問し、原因が特定でき、解決することができました。

結論としては、Validationが掛かっていたためのリダイレクトでした!

どういうことかというと、、、

コントローラーのstoreupdateアクションで設定していたDailyReportRequestindexアクションの引数に渡していたことで、storeupdateアクションのvalidationが掛かってしまっていました。

DailyReportRequest.php
~省略~
    public function rules()
    {
        return [
            'reporting_time' => 'required|before_or_equal:today',
            'title' => 'required|max:255',
            'contents' => 'required|max:1000',
        ];
    }

    public function messages()
    {
        return [
            'required' => '入力必須の項目です。',
            'reporting_time.before_or_equal' => '今日以前の日付を入力してください。',
            'max' => ':max 文字以内で入力してください。',
        ];
    }

このvalidationが掛かっていると、検索フォームから送信した値にもvalidationチェックを行ってしまいます。
当然、送信された値には上記のアクションでvalidationが失敗する値が送信されてきます。

validationは入力値が設定した条件を満たしていない場合、入力画面にリダイレクトします。

そのため、入力フォームから値を送信した際に、無限リダイレクトが発生し、「リダイレクトが繰り返し行われました。」のようなエラーが発生していました。

というわけでindexアクションを修正します。

DailyReportController.php
public function index(Request $request)
{
    $search = $request->input('search');
    dd($search)
    return view('user.daily_report.index', compact('dailyReports'));
}

Request, $requestでよかったんだ、、、、

しっかりuse宣言で利用するRequestクラスの記述を忘れずに、、、

use Illuminate\Http\Request;

こちらで無事に$searchで送信された値の受け取りに成功!

こうしたエラーは調べてもなかなかお目当てのものに辿り着かないため、しっかりとロジックを理解して解決を目指すことが重要だそうです。

いい勉強になりました、、、、

また、エラーを調べる時も、予め自己解決を目指す時間を設定し、それを超えてしまったら直ぐに質問するのが良いですね。
素直に現在の自分の実力と向き合うのが大切だと学びました。

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