(この記事は作成中です。)
こんにちは。
ITエンジニアの濱辺(ハマベ)です。
今回は、ログイン&お気に入り動画登録機能を実装していきます。
↓第3回はこちら
LaravelでYoutubeのお気に入りCuration(まとめ)アプリを作る【第3回】
↓こちらの画面定義書のものを作っていきます。
Youtube-Curation 画面定義所 (googleスプレッドシート)
ログイン機能の実装
ログイン機能についても、Laravelに標準で備わっているファイルがあります。
基本機能は最初から用意されているんですね。
Route設定
下記3つのrouteを記述しましょう。
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login')->name('login.post');
Route::get('logout', 'Auth\LoginController@logout')->name('logout');
それぞれ、
- ログインページ表示
- ログインフォーム情報の送信
- ログアウト処理
のためのrouteです。
LoginController確認
Controllers/Auth/LoginController
を開いてみましょう。
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
//(中略)
use AuthenticatesUsers;
/**
* Where to redirect users after login.
*
* @var string
*/
protected $redirectTo = RouteServiceProvider::HOME;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest')->except('logout');
}
}
ログアウト以外のアクションは、guest(非ログイン)ユーザしか実行できない、といったことが記述されています。
上記記述のうち、推移先のページ情報だけ書き換えましょう。
protected $redirectTo = RouteServiceProvider::HOME;
↓ //変更
protected $redirectTo = '/';
ログインページの見た目実装
ログインページのviewを作成します。
view/auth/login.blade.php
を作成しましょう。
@extends('layouts.app')
@section('content')
<div class="center jumbotron bg-warning">
<div class="text-center text-white">
<h1>YouTubeまとめ × SNS</h1>
</div>
</div>
<div class="text-center">
<h3 class="login_title text-left d-inline-block mt-5">ログイン</h3>
</div>
<div class="row mt-5 mb-5">
<div class="col-sm-6 offset-sm-3">
{!! Form::open(['route' => 'login.post']) !!}
<div class="form-group">
{!! Form::label('email', 'メールアドレス') !!}
{!! Form::email('email', old('email'), ['class' => 'form-control']) !!}
</div>
<div class="form-group">
{!! Form::label('password', 'パスワード') !!}
{!! Form::password('password', ['class' => 'form-control']) !!}
</div>
{!! Form::submit('ログイン', ['class' => 'btn btn btn-primary mt-2']) !!}
{!! Form::close() !!}
<p class="mt-3">{!! link_to_route('signup', '新規ユーザ登録する?') !!}</p>
</div>
</div>
@endsection
これでログインができるようになりました。
ヘッダーからログインページへ推移できるようにする
ヘッダーを書き換えて、ログイン状態と非ログイン状態で表示分けできるようにしましょう。
<header class="mb-5">
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="/">YouTube-Curation</a>
<button type="button" class="navbar-toggler" data-toggle="collapse" data-target="#nav-bar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav-bar">
<ul class="navbar-nav mr-auto"></ul>
<ul class="navbar-nav">
@if (Auth::check())
<li class="nav-item">{!! link_to_route('logout', 'ログアウト', [], ['class' => 'nav-link']) !!}</li>
<li class="nav-item"><a href="" class="nav-link">マイページ</a></li>
@else
<li class="nav-item">{!! link_to_route('signup', '新規ユーザ登録', [], ['class' => 'nav-link']) !!}</li>
<li class="nav-item">{!! link_to_route('login', 'ログイン', [], ['class' => 'nav-link']) !!}</li>
@endif
</ul>
</div>
</nav>
</header>
@if (Auth::check())
から、@else
までの間がログイン状態での表示。
@else
から@endif
の間が非ログイン状態の表示を表しています。
Auth::check()
は「ユーザがログイン状態か?」を判断する関数です。
トップページにログインユーザ名を表示
トップページにログインしたユーザの名前を表示させましょう。
@extends('layouts.app')
@section('content')
<div class="center jumbotron bg-warning">
<div class="text-center text-white">
<h1>YouTubeまとめ × SNS</h1>
</div>
</div>
<div class="text-right">
@if(Auth::check())
{{ Auth::user()->name }}
@endif
</div>
@endsection
さて、一通り実装できたので、ログイン・ログアウトを実行してみましょう。
無事に動作したでしょうか。
できましたら、動画登録機能の実装をしていきましょう!
動画登録の実装
まずは、movie用のmysqlテーブルを作成していきます。
Moviesテーブルを作成
まず、Moviesテーブルを作成します。
下記のコマンドを実行しましょう。
# php artisan make:migration create_movies_table --create=movies
ファイルが作成されるので、中身up()の中身を書き換えましょう。
public function up()
{
Schema::create('movies', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->unsigned()->index();
$table->string('url');
$table->string('comment')->nullable();
$table->timestamps();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
});
}
5つのカラムを作るように記述しました。
- id:各動画に付ける連番
- user_id:動画を登録したユーザのID
- url: YouTube動画のURL
- comment: 動画に対するコメント (nullable = nullでも投稿できる)
- timestamps:動画登録日時・動画更新日時
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
は、紐づいているユーザIDを持つユーザが消去されたら、moviesテーブルの該当カラムも一緒に削除する、といった意味になります。
マイグレートを実行してみましょう。
# php artisan migrate
すると、下記のエラーが発生するかと思います。
PDOException::("SQLSTATE[HY000]: General error: 3780 Referencing column 'user_id' and referenced column 'id' in foreign key constraint 'movies_user_id_foreign' are incompatible.")
これは、usersテーブルのidと、moviesテーブルのidの型が異なることが原因で発生しています。
下記の部分を修正しましょう。
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
〜略
});
moviesモデルの作成
以下のコマンドにて、Movie モデルを作成しましょう。
# php artisan make:model Movie
生成されたMovie.php
に以下のように記述し、(’user_id’,’url’,’comment’)を登録できるようにしましょう。
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Movie extends Model
{
protected $fillable = ['user_id','url','comment'];
//多対一の関係
public function user()
{
return $this->belongsTo(User::class);
}
}
Userモデルに、Movieモデルとの関係を記述しましょう。(一対多、一人のuserに複数のmovieの関係)
~ 省略 ~
/**
* The attributes that should be cast to native types.
*
* @var array
*/
protected $casts = [
'email_verified_at' => 'datetime',
];
//追加(movieとの一対多の関係)
public function movies()
{
return $this->hasMany(Movie::class);
}
}
ここまで出来たら、一度tinkerを使って動画の登録を試してみましょう。
>>> use App\User
>>> $user = User::find(1)
=> App\User {#4104
id: 1,
name: "sample1",
email: "sample1@sample.com",
email_verified_at: null,
#password: "$2y$10$vHoB.o8.Gd.KGmkflb06MOVuRTnEU9KihfJ9himiOxjNCDaZ7d4oS",
#remember_token: null,
created_at: null,
updated_at: null,
}>>> use App\Movie
>>> $user->movies()->get();
=> Illuminate\Database\Eloquent\Collection {#xxx
all: [],
}
>>> $user->movies()->create([
... 'url' => 'Gn61Vq9v6GY',
... 'comment' => 'sample movie 1'])
=> App\Movie {#3324
url: "Gn61Vq9v6GY",
comment: "sample movie 1",
user_id: 1,
updated_at: "2021-10-12 07:51:46",
created_at: "2021-10-12 07:51:46",
id: 1,
}
>>> $user->movies
=> Illuminate\Database\Eloquent\Collection {#4252
all: [
App\Movie {#4249
id: 1,
user_id: 1,
url: "Gn61Vq9v6GY",
comment: "sample movie 1",
created_at: "2021-10-12 07:51:46",
updated_at: "2021-10-12 07:51:46",
},
],
}
MovieのRouter作成
Movie関連のRouterを記述していきます。
今回はresorce
を使用します。resourceは、主要7つのrouteを自動作成してくれます。
web.php
を下記のようにしましょう。
Route::get('/', 'UsersController@index'); //書き換え
Route::get('signup', 'Auth\RegisterController@showRegistrationForm')->name('signup');
Route::post('signup', 'Auth\RegisterController@register')->name('signup.post');
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login')->name('login.post');
Route::get('logout', 'Auth\LoginController@logout')->name('logout');
// 追記分
Route::resource('users', 'UsersController', ['only' => ['show']]);
//ログイン認証を通ったユーザのみ、アクセスできるroute
Route::group(['middleware' => 'auth'], function () {
Route::resource('movies', 'MoviesController', ['only' => ['create', 'store', 'destroy']]);
});
UsersController作成
UserControllerを作成し、トップページを表示するindexアクションを記述していきます。
# php artisan make:controller UsersController
app/Http/Controllers/UsersController.php
が作成される
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User; //追記
class UsersController extends Controller
{
public function index()
{
$users = User::orderBy('id','desc')->paginate(9);
return view('welcome', [
'users' => $users,
]);
}
}
'users' => $users,
は、usersという変数をviewに持っていく、という処理を表しています。
Viewの作成
Movies の一覧を表示する共通の View として、 users.blade.php を作成します。
usersフォルダを作成し、ファイルを作っていきましょう。
resources/views/users/users.blade.php
<h2 class="mt-5 mb-5">users</h2>
<div class="movies row mt-5 text-center">
@foreach ($users as $key => $user)
@php
$movie=$user->movies->last();
@endphp
@if($loop->iteration % 3 == 1 && $loop->iteration != 1)
</div>
<div class="row text-center mt-3">
@endif
<div class="col-lg-4 mb-5">
<div class="movie text-left d-inline-block">
@{{ $user->name }}
<div>
@if($movie)
<iframe width="290" height="163.125" src="{{ 'https://www.youtube.com/embed/'.$movie->url }}?controls=1&loop=1&playlist={{ $movie->url }}" frameborder="0"></iframe>
@else
<iframe width="290" height="163.125" src="https://www.youtube.com/embed/" frameborder="0"></iframe>
@endif
</div>
<p>
@if(isset($movie->comment))
{{ $movie->comment }}
@endif
</p>
</div>
</div>
@endforeach
</div>
{{ $users->links('pagination::bootstrap-4') }}
ここで少し、見た目を整えるためにCSSを適用しましょう。
下記のようにlinkを、app.blade.php
に追記します。
<head>
<meta charset="utf-8">
<title>YouTubeまとめ×SNS</title>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
//追加
<link rel="stylesheet" href="{{ asset('/css/styles.css') }}">
</head>
続いて、public/css/styles.css
を作成し、下記を記述します。
.movie{
width: 290px;
}
.movie > p{
height: 72px;
}
.button{
width: 290px;
}
これで、下記のような見た目となるはずです。
それぞれ、ユーザに登録した最新の動画のサムネが表示され、クリックすると、再生できるはずです。
ここまでで、「動画を保存できる」ようにはなりました。
次はwebページから動画を登録できるようにしていきます。
MoviesController作成
まずは、下記のコマンドでMoviesControllerを作成しましょう。
# php artisan make:controller MoviesController
app/Http/Controllers/MoviesController.php
が作成されるので、
createアクションを記述していきましょう。
createアクション作成
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\User;
use App\Movie;
class MoviesController extends Controller
{
public function create()
{
$user = \Auth::user();
$movies = $user->movies()->orderBy('id', 'desc')->paginate(9);
$data=[
'user' => $user,
'movies' => $movies,
];
return view('movies.create', $data);
}
}
今回は、$data
という変数に、$user
と$movies
を配列として渡して、$data
をViewに渡すようにしています。
Viewの作成
まず、headerから動画の新規登録ページへ飛べるようにしましょう。
@if (Auth::check())
<li class="nav-item">{!! link_to_route('logout', 'ログアウト', [], ['class' => 'nav-link']) !!}</li>
<li class="nav-item"><a href="" class="nav-link">マイページ</a></li>
<li class="nav-item">{!! link_to_route('movies.create','動画を登録する',['id'=>Auth::id()],['class'=>'nav-link']) !!}</li>
<!--↑追記-->
@else
次に、動画登録フォームを作成しましょう。
resources/views/movies/create.blade.php
を新規作成します。
@extends('layouts.app')
@section('content')
<div class="text-right">
{{ Auth::user()->name }}
</div>
<h2 class="mt-5">動画を登録する</h2>
{!! Form::open(['route'=>'movies.store']) !!}
<div class="form-group mt-5">
{!! Form::label('url','新規登録YouTube動画 "ID" を入力する',['class'=>'text-success']) !!}
<br>例)登録したいYouTube動画のURLが <span>https://www.youtube.com/watch?v=-bNMq1Nxn5o なら</span>
<div> "v="の直後にある "<span class="text-success">-bNMq1Nxn5o</span>" を入力</div>
{!! Form::text('url',null,['class'=>'form-control']) !!}
{!! Form::label('comment','登録動画へのコメント',['class'=> 'mt-3']) !!}
{!! Form::text('comment',null,['class'=>'form-control']) !!}
{!! Form::submit('新規登録する?',['class'=> 'button btn btn-primary mt-5 mb-5']) !!}
</div>
{!! Form::close() !!}
<h2 class="mt-5">あなたの登録済み動画</h2>
@include('movies.movies', ['movies' => $movies])
@endsection
下記の部分に注目してください。
<h2 class="mt-5">あなたの登録済み動画</h2>
@include('movies.movies', ['movies' => $movies])
これは、moviesフォルダのmovies.blade.phpを読み込む記述です。
こうして別ファイルに分けた方が、後々の変更に強くなるのです。
というわけで、movies/movies.blade.php
を作成しましょう。
<div class="movies row mt-5 text-center">
@foreach ($movies as $key => $movie)
@if($loop->iteration % 3 == 1 && $loop->iteration != 1)
</div>
<div class="row text-center mt-3">
@endif
<div class="col-lg-4 mb-5">
<div class="movie text-left d-inline-block">
<div>
@if($movie)
<iframe width="290" height="163.125" src="{{ 'https://www.youtube.com/embed/'.$movie->url }}?controls=1&loop=1&playlist={{ $movie->url }}" frameborder="0"></iframe>
@else
<iframe width="290" height="163.125" src="https://www.youtube.com/embed/" frameborder="0"></iframe>
@endif
</div>
<p>
@if(isset($movie->comment))
{{ $movie->comment }}
@endif
</p>
</div>
</div>
@endforeach
</div>
{{ $movies->links('pagination::bootstrap-4') }}
これで、ヘッダーの「動画を登録する」から遷移した画面がこうなるはずです。