- 投稿日:2021-03-22T21:43:14+09:00
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くださあああああああああああああい
- 投稿日:2021-03-22T13:14:06+09:00
いいねとリプライ
コメント機能
ルーティング
routes/web.php// ログイン状態 Route::group(['middleware' => 'auth'], function() { // 省略 // コメント関連 Route::resource('comments', 'CommentsController', ['only' => ['store']]); });Model
app/Comment.phppublic 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 --resourceapp/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、逆に存在しなければtrueController
php artisan make:controller FavoritesController --resourceapp/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(); } }
- 投稿日:2021-03-22T13:13:58+09:00
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)でも使用できる。
- 投稿日:2021-03-22T13:13:52+09:00
ログイン機能で詰まった箇所のメモ
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/ui2. ログイン機能作成
ターミナル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
- 投稿日:2021-03-22T13:13:46+09:00
ログイン機能とフォロー機能
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 --resourceRouting
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.phppublic 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.phppublic 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
Tweetapp/Tweet.phppublic 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(); }
Followerapp/Follower.phppublic 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_nameやapp/Http/Controllers/UsersController.phppublic 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.phppublic 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; }
- 投稿日:2021-03-22T13:13:38+09:00
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
Users2014_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'); } }
Tweets2020_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');
Comments2020_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'); } }
Favorites2020_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
Usersapp/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' ]; }
Tweetsapp/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' ]; }
Commentsapp/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' ]; }
Favoritesapp/Favorite.php<?php namespace App; use Illuminate\Database\Eloquent\Model; class Favorite extends Model { public $timestamps = false; }
Followersapp/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; }リレーションの親子関係
Usersapp/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'); }
Tweetsapp/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); } }
Commentsapp/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
- 投稿日:2021-03-22T13:00:06+09:00
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 methodscombining 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のときはいらないが、それ以外の時は基本いるものと考えたほうが良い。
- 投稿日:2021-03-22T09:05:38+09:00
一つのユーザーテーブルを複数のモデルとガードに分ける
背景
ロールを元にユーザーテーブルのガードを分けたかった。
実践的には、ガードを分けるよりも、一つのガードでログインした後に権限でアクセス制御したほうがいいと思う。あくまでも実験的なものとして覚え書きを残す。
動作環境
- 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.phpnamespace 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.phpnamespace 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', ],ファクトリの作成
UserFactoryのdefinition()をコピーして、StudentFactoryとTeacherFactoryを用意する。ロールの投入
マイグレーションして、
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を合わせること。参考資料
- https://readouble.com/laravel/8.x/ja/eloquent.html#anonymous-global-scopes
- https://readouble.com/laravel/8.x/ja/eloquent.html#events-using-closures
- https://spatie.be/docs/laravel-permission/v3/basic-usage/basic-usage
- https://spatie.be/docs/laravel-permission/v3/basic-usage/multiple-guards
- https://qiita.com/kd9951/items/c4e5526a6feb4437d1f0
- 投稿日:2021-03-22T09:03:39+09:00
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.phpuse 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 servecurl http://localhost:8000/example.mp | msgpack2json -dレスポンスマクロの作成
php artisan make:provider MessagePackResponseServiceProvider
app/Providers/MessagePackResponseServiceProvider.phpnamespace 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.phpreturn [ 'providers' => [ # ... App\Providers\MessagePackResponseServiceProvider::class, ], ];
routes/web.phpRoute::get('/example.mp', function () { return response()->mpac([ 'id' => 1, 'name' => 'John Doe', 'age' => 20, ]); });ターミナルで確認
curl http://localhost:8000/example.mp | msgpack2json -dModel を渡せるようにする
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' ); });資料
- 投稿日:2021-03-22T01:03:27+09:00
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!!
3. モデル作成
$ php artisan make:model Models/hogeHoge.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 HogeControllerHogeController.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>
formタグのactionでurlという機能を呼び出しているがこれはソースコードの下の方にある「追加」ボタンを押したあとに遷移する飛び先になる。
{{ csrf_field() }}を宣言することにより、POST, PUT, DELETEのリクエストをした際に認証済みユーザーのトークンと一致するか自動的にチェックしてくれる。
6. DBに入力した値が反映されていることを確認する
上記URLにアクセスし、任意の値を入力する。
その後、入力したフォームの内容がDBに登録されていることを確認できた!
http://127.0.0.1:8000/informationにアクセスした時にHogeControllerのcreateメソッドが呼ばれ、メソッド内のreturn view('create');でcreate.blade.phpを表示させている。その後、画面上のAddを押下したときにHogeControllerのaddメソッドが呼ばれ、DBに登録ができた。
参考サイト
- 投稿日:2021-03-22T01:03:27+09:00
入力したフォーム値を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!!
3. モデル作成
$ php artisan make:model Models/hogeHoge.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 HogeControllerHogeController.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>
formタグのactionでurlという機能を呼び出しているがこれはソースコードの下の方にある「追加」ボタンを押したあとに遷移する飛び先になる。
{{ csrf_field() }}を宣言することにより、POST, PUT, DELETEのリクエストをした際に認証済みユーザーのトークンと一致するか自動的にチェックしてくれる。
6. DBに入力した値が反映されていることを確認する
上記URLにアクセスし、任意の値を入力する。
その後、入力したフォームの内容がDBに登録されていることを確認できた!
http://127.0.0.1:8000/informationにアクセスした時にHogeControllerのcreateメソッドが呼ばれ、メソッド内のreturn view('create');でcreate.blade.phpを表示させている。その後、画面上のAddを押下したときにHogeControllerのaddメソッドが呼ばれ、DBに登録ができた。
参考サイト
- 投稿日:2021-03-22T00:09:21+09:00
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.phppublic 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)で送信された値を確認しようとしました。しかし...ここでエラーが発生!!
リダイレクトが繰り返し行われているとな、、、?
念のためデベロッパーツールのNetworkでも確認してみます。
やはりリダイレクトが起きている様子...!
ちなみに「status」の「302」はHTTPステータスコード「302リダイレクト」というものでその名の通りリダイレクト発生を示しています。
今回のケースではコントローラー内のindexアクションの
dd($search)で$searchの中身が表示され、処理が止まるはずなのですが、先ほどのエラー画面が表示されています。
このことからdd($search)まで処理が来ておらず、DailyReportRequest、$requestの箇所に原因があることがわかります。ここまでは特定できた、、、、、
だが、今回のエラーを自分なりに調べてみたが、解決策を見つけることができず、、、、自分の調べ方が悪いのかと、色々考えを巡らせましたが、どうにもこうにもいかない、、、
最終的に先輩社員に質問し、原因が特定でき、解決することができました。結論としては、Validationが掛かっていたためのリダイレクトでした!
どういうことかというと、、、
コントローラーの
store、updateアクションで設定していたDailyReportRequestをindexアクションの引数に渡していたことで、store、updateアクションの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.phppublic 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で送信された値の受け取りに成功!こうしたエラーは調べてもなかなかお目当てのものに辿り着かないため、しっかりとロジックを理解して解決を目指すことが重要だそうです。
いい勉強になりました、、、、
また、エラーを調べる時も、予め自己解決を目指す時間を設定し、それを超えてしまったら直ぐに質問するのが良いですね。
素直に現在の自分の実力と向き合うのが大切だと学びました。





