Laravel Collectiveでのhiddenの扱い

hiddenはinput type=”hidden”で普通に書く

/resources/views/comments/index.blade.php
/resources/views/comments/replies/index.blade.php

Route

Route::group(['middleware'=>'admin'], function(){
	...//
	Route::resource('admin/comments', 'PostCommentsController');
	Route::resource('admin/comments/replies', 'CommentRepliesController');
});

$ php artisan make:model Comment -m
$ php artisan make:model CommentReply -m

comment migration file

Schema::create('comments', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('post_id')->unsigned()->index();;
            $table->integer('is_active')->default(0);
            $table->string('author');
            $table->string('email');
            $table->text('body');
            $table->timestamps();

            $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade');
        });

comment replies migration file

Schema::create('comment_replies', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('comment_id')->unsigned()->index();
            $table->integer('is_active')->default(0);
            $table->string('author');
            $table->string('email');
            $table->text('body');
            $table->timestamps();

            $table->foreign('comment_id')->references('id')->on('comments')->onDelete('cascade');
        });

Model: Post.php

public function comments(){
        return $this->hasMany('App\Post');
    }

Comment.php

protected $fillable = [
		'post_id',
		'author',
		'email',
		'body',
		'is_active'
	];

    public function replies(){
    	return $this->hasMany('App\CommentReply');
    }

CommentReply.php

protected $fillable = [
		'comment_id',
		'author',
		'email',
		'body',
		'is_active'
	];

	public function comment(){
		return $this->belongsTo('App\Comment');
	}

$ php artisan make:controller –resource PostCommentController
$ php artisan make:controller –resource CommentRepliesController

admin.blade.php

<li>
                        <a href="#"><i class="fa fa-wrench fa-fw"></i> Posts<span class="fa arrow"></span></a>
                        <ul class="nav nav-second-level">
                            <li>
                                <a href="{{route('admin.posts.index')}}">All Posts</a>
                            </li>

                            <li>
                                <a href="{{route('admin.posts.create')}}">Create Post</a>
                            </li>

                            <li>
                                <a href="{{route('admin.comments')}}">All Comments</a>
                            </li>

                        </ul>
                        <!-- /.nav-second-level -->
                    </li>

PostCommentsController.php

public function index(){
    	return view('admin.comments.index');
    }

route

Route::get('/post/{id}', ['as'=>'home.post','uses'=>'AdminPostsController@post']);

post.blade.php

<h1>{{$post->title}}</h1>

                <!-- Author -->
                <p class="lead">
                    by <a href="#">{{$post->user->name}}</a>
                </p>
                <hr>
               <!-- Date/Time -->
                <p><span class="glyphicon glyphicon-time"></span> Posted {{$post->created_at->diffForhumans()}}</p>
                <hr>
                <!-- Preview Image -->
                <img class="img-responsive" src="{{$post->photo ? '/' . $post->photo->file : ''}}" alt="">
                <hr>
                <!-- Post Content -->
                <p>{{$post->body}}</p>
                <hr>

 <h4>Leave a Comment:</h4>
                    
                    {!! Form::open(['method'=>'POST', 'action'=>'PostCommentsController@store']) !!}
				        {{ csrf_field()}}
				 
				        <div class="form-group">
				            {!! Form::label('body', 'Body') !!}
				            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
				        </div>				        
				        <div class="form-group">
				            {!! Form::submit('Submit Comment', ['class'=>'btn btn-primary']) !!}
				        </div>
				    {!! Form::close() !!}                   
                </div>

post.blade.php

 {!! Form::open(['method'=>'POST', 'action'=>'PostCommentsController@store']) !!}
				        {{ csrf_field()}}

				        <input type="hidden" name="post_id" value="{{$post->id}}">
				 
				        <div class="form-group">
				            {!! Form::label('body', 'Body') !!}
				            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
				        </div>				        
				        <div class="form-group">
				            {!! Form::submit('Submit Comment', ['class'=>'btn btn-primary']) !!}
				        </div>
				    {!! Form::close() !!} 

@if(Sessin::has('comment_message'))
						{{session('comment_message')}}
                @endif

PostCommentsController.php

use Illuminate\Support\Facades\Session;
use Illuminate\Support\Facades\Auth;
use App\User;
use App\Photo;
use App\Comment;

public function store(Request $request){

    	$user = Auth::user();

    	$data = [
    		'post_id' => $request->post_id,
    		'author' => $user->name,
    		'email' => $user->email,
    		'body' => $request->body
    	];
    	Comment::create($data);
    	$request->session()->flash('comment_message','Your message has been submitted and is waiting moderation' );

    	return redirect()->back();
    }

hiddenはinput type=”hidden”で書くという事は、フォームの確認ページがある場合、そこは全てcollectiveは使わずに書く、ということになりますね。

Laravelでdropzoneの使い方

– htmlべだ書と同様、ヘッダにdropzoneのcss、フッタにjsを読み込ませればいい。
– dropzoneはfileのnameがglobalで’file’なので、controllerで受け取るときは、$request->file(‘file’);と書く。

/resources/views/media/index.blade.php

@if($photos)
		<table class="table table-striped">
			<thead>
			<tr>
			  <th>Id</th>
			  <th>Name</th>
			  <th>Created date</th>
			</tr>
			</thead>
			<tbody>
				@foreach($photos as $photo)
				<tr>
				  <td>{{$photo->id}}</td>
				  <td><img height="50" src="/{{$photo->file}}"></td>
				  <td>{{$photo->created_at ? $photo->created_at : 'no date'}}</td>
				</tr>
				@endforeach			
			</tbody>
		</table>
		@endif

$ php artisan make:controller AdminMediasController

route.php

Route::group(['middleware'=>'admin'], function(){
	Route::resource('admin/users', 'AdminUsersController');
	Route::resource('admin/posts', 'AdminPostsController');	
	Route::resource('admin/categories', 'AdminCategoriesController');	
	Route::resource('admin/media', 'AdminMediasController');
});

layouts/admin.blade.php

<li>
                                <a href="{{route('admin.medias.index')}}">All Media</a>
                            </li>
s
                            <li>
                                <a href="{{route('admin.medias.create')}}">Upload Media</a>
                            </li>

AdminMediasController.php

 public function index(){
    	$photos = Photo::all();
    	return view('admin.media.index', compact('photos'));
    }
 public function create(){
    	return view('admin.media.create');
    }

https://www.dropzonejs.com/

create.blade.php

@extends('layouts.admin')

@section('styles')
	<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.css">
@stop

@section('content')

		<h1>Upload Media</h1>

		{!! Form::open(['method'=>'POST', 'action'=>'AdminMediasController@store','class'=>'dropzone', 'files'=>true]) !!}
        {{ csrf_field()}}
      
        <div class="form-group">
            {!! Form::submit('Create Post', ['class'=>'btn btn-primary']) !!}
        </div>
    {!! Form::close() !!}
@stop

@section('scripts')
	<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.5.1/min/dropzone.min.js"></script>
@stop

AdminMediasController.php

public function store(Request $request){
    	$file = $request->file('file');
    	$name = time(). $file->getClientOriginalName();
    	$file->move('images',  $name);
    	Photo::create(['file'=>$name]);
    }
public function destroy($id){
    	$photo = Photo::findOrFail($id);
    	unlink(public_path(). '/'. $photo->file);

    	$photo->delete();
    	return redirect('/admin/media');
    }

index.blade.php

<tbody>
				@foreach($photos as $photo)
				<tr>
				  <td>{{$photo->id}}</td>
				  <td><img height="50" src="/{{$photo->file}}"></td>
				  <td>{{$photo->created_at ? $photo->created_at : 'no date'}}</td>
				  <td>
				  	{!! Form::open(['method'=>'DELETE', 'action'=>['AdminMediasController@destroy', $photo->id]]) !!}
			        {{ csrf_field()}}
			        
			        <div class="form-group">
			            {!! Form::submit('Delete', ['class'=>'btn btn-danger']) !!}
			        </div>
			        {!! Form::close() !!}

				  </td>
				</tr>
				@endforeach			
			</tbody>

対象ページのみcssやjsをインクルードさせたい時は、ヘッダとフッタのsectionに追加で書き込めば良い😺

Laravel index, create, update, delete

index, create, update, deleteの順番に作ります。
最初にindexで表示項目のデータをDBに入れておく。

$ php artisan make:controller –resource AdminCategoriesController

Route::group(['middleware'=>'admin'], function(){
	Route::resource('admin/users', 'AdminUsersController');
	Route::resource('admin/posts', 'AdminPostsController');	
	Route::resource('admin/categories', 'AdminCategoriesController');	
});

AdminCategoriesController.php

public function index()
    {
        $categories = Category::all();
        return view('admin.categories.index', compact('categories'));
    }

layouts/admin.blade.php

<li>
                                <a href="{{route('admin.categories.index')}}">All Categories</a>
                            </li>

                            <li>
                                <a href="{{route('admin.categories.create')}}">Create Category</a>
                            </li>

categories/index.blade.php

<h1>Categories</h1>	

	<div class="col-sm-6">
		{!! Form::open(['method'=>'POST', 'action'=>'AdminCategoriesController@store']) !!}
        {{ csrf_field()}}
	        <div class="form-group">
	            {!! Form::label('name', 'Name') !!}
	            {!! Form::text('name', null, ['class'=>'form-control']) !!}
	        </div>
	        <div class="form-group">
	            {!! Form::submit('Create Category', ['class'=>'btn btn-primary']) !!}
	        </div>
	    {!! Form::close() !!}

	</div>

	<div class="col-sm-6">
		
		@if($categories)
		<table class="table table-striped">
			<thead>
			<tr>
			  <th>Id</th>
			  <th>Category</th>
			  <th>Created date</th>
			</tr>
			</thead>
			<tbody>
				@foreach($categories as $category)
				<tr>
				  <td>{{$category->id}}</td>
				  <td>{{$category->name}}</td>
				  <td>{{$category->created_at ? $category->created_at->diffForhumans() : 'no date'}}</td>
				</tr>
				@endforeach			
			</tbody>
		</table>
		@endif
	</div>

AdminCategoriesController.php

public function store(Request $request)
    {
        //
            Category::create($request->all());

            return redirect('/admin/categories');
    }

public function edit($id)
    {
        //
        $category = Category::findOrFail($id);
        return view('admin.categories.edit', compact('category'));
    }

view: index.blade.php

<tr>
				  <td>{{$category->id}}</td>
				  <td><a href="{{route('admin.categories.edit', $category->id)}}">{{$category->name}}</a></td>
				  <td>{{$category->created_at ? $category->created_at->diffForhumans() : 'no date'}}</td>
				</tr>

AdminCategoriesController.php

public function destroy($id)
    {
        Category::findOrFail($id)->delete();
        return view('/admin/categories');
    }

Keep going!
気分転換に馬喰町のロボットコーヒーでも行くかな☕

Laravel hasManyで親レコード削除と同時に子レコード削除の書き方

migration時に$table->foreign(‘user_id’)->references(‘id’)->on(‘users’)->onDelete(‘cascade’);と書く

AdminPostsController.php

public function edit($id)
    {
        //
        $post = Post::findOrFail($id);
        $categories = Category::lists('name', 'id')->all();
        return view('admin.posts.edit', compact('post','categories'));
    }

View: edit.blade.php

<h1>Edit Post</h1>
	{!! Form::Model($post, ['method'=>'PATCH', 'action'=>['AdminPostsController@update', $post->id], 'files'=>true]) !!}
        {{ csrf_field()}}
 
        <div class="form-group">
            {!! Form::label('title', 'Title') !!}
            {!! Form::text('title', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('category_id', 'Category') !!}
            {!! Form::select('category_id', $categories, null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('photo_id', 'Photo') !!}
            {!! Form::file('photo_id', null,['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('body', 'Description') !!}
            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
        </div>


        
        <div class="form-group">
            {!! Form::submit('Create Post', ['class'=>'btn btn-primary']) !!}
        </div>
    {!! Form::close() !!}

    </div>

    <div class="row">

    @include('includes.form_error')

	</div>

index.blade.php

<tr>
			  <td>{{$post->id}}</td>
			  <td><img height="50" src="{{$post->photo ? '/' . $post->photo->file : 'https://placehold.it/50x50'}}"></td>
			  <td><a href="{{route('admin.posts.edit', $post->id)}}">{{$post->user->name}}</a></td>
			  <td>{{$post->category ? $post->category->name : 'Uncategorized'}}</td>
			  <td>{{$post->title}}</td>
			  <td>{{$post->body}}</td>
			  <td>{{$post->created_at->diffForHumans()}}</td>
			  <td>{{$post->updated_at->diffForHumans()}}</td>
			</tr>

AdminPhotosController.php

public function update(Request $request, $id)
    {
        //
        $input = $request->all();

        if($file = $request['photo_id']){
            $name = time(). $file->getClientOriginalName();
            $file->move('images', $name);
            $photo = Photo::create(['file'=>$name]);

            $input['photo_id'] = $photo->id;
        }

        Auth::user()->posts()->whereId($id)->first()->update($input);

        return redirect('/admin/posts');
    }

### helper
laravel helper string functions
https://laravel.com/docs/master/helpers

string limit
https://laravel.com/docs/master/helpers#method-str-limit

 <td>{{str_limit($post->body, 10)}}</td>

### delete
edit.blade.php

{!! Form::open(['method'=>'DELETE', 'action'=>['AdminPostsController@destroy', $post->id]]) !!}
        {{ csrf_field()}}
        
        <div class="form-group">
            {!! Form::submit('Delete Post', ['class'=>'btn btn-danger col-sm-6']) !!}
        </div>
        {!! Form::close() !!}

AdminPostsController.php

public function destroy($id)
    {
        //
        $post = Post::findOrFail($id);
        unlink(public_path() . '/' .  $post->photo->file);
        $post->delete();

        return redirect('/admin/posts');
    }

### ユーザ削除するとpostsもdelete
Post migration file

Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned()->index();
            $table->integer('category_id')->unsigned()->index();
            $table->integer('photo_id')->unsigned()->index();
            $table->string('title');
            $table->text('body');
            $table->timestamps();

            $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
        });

$ php artisan migrate:refresh

insert into roles (name) values (‘Administrator’);
insert into roles (name) values (‘Author’);
insert into roles (name) values (‘Subscriber’);

insert into categories (name) values (‘PHP’);
insert into categories (name) values (‘Laravel’);
insert into categories (name) values (‘JavaScript’);
insert into categories (name) values (‘Photoshop’);

update posts set user_id=10 where id=1;
update users set role_id=1 where id=1;

update posts set user_id=2 where id=2;

AdminUserController.php

public function destroy($id)
    {
        //
        $user = User::findOrFail($id);

        if($user->photo !== NULL){
            unlink(public_path() ."/". $user->photo->file);
        }
        $user->delete();

        Session::flash('deleted_user', 'The user has benn deleted');
        return redirect('/admin/users');
    }

edit.blade.php

<div class="col-sm-6">
		<img src="{{$post->photo ? $post->photo->file : 'https://placehold.it/400x400'}}" alt="" class="img-responsive">

	</div>

実際のアプリケーションでは、ユーザはsoftdeleteとして、更にユーザを削除しても、hasManyの子レコードは残したいケースが多いように思いますが、一緒に削除も可能です。

helperはstr_limit以外にも使えそうなものは使いたい。

git private repositoryへpushでInvalid username or password.

### vagrant環境(ローカル)
$ git init
$ git status
$ git add .
$ git commit -m “application to the github”

### リポジトリの作成
– プライベートリポジトリ作成
$ git remote add origin https://github.com/${username}/${repositoryName}.git

### git push
$ git push -u origin master
Username for ‘https://github.com’: ${username}
Password for ‘https://${username}@github.com’

remote: Invalid username or password.

パスワードを正しく入力してもエラーが出ます。

### 解決方法
Github右上のハンバーガーメニュー -> “Settings” -> right menu “Developer Settings” -> Personal access tokens -> generate token

Full control of private repositoriesにチェックを入れて、tokenを発行
発行されたトークンをパスワードに入力すれば、pushできます。
Username for ‘https://github.com’: ${username}
Password for ‘https://${username}@github.com’ : ${generated token}

github 公式
https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line

username, passwordは間違ってないのだから、エラーメッセージに”Invalid username or password”って出すの辞めて欲しい。反面教師にしよう。

Laravel belongsToのフォームの書き方

formはlaravel collectiveを使用。make:model ${modelName} -mでDBを作って、データを挿入し、select文に対してControllerのcreateメソッドからデータを渡す

View: create.blade.php

{!! Form::open(['method'=>'POST', 'action'=>'AdminPostsController@store', 'files'=>true]) !!}
        {{ csrf_field()}}
 
        <div class="form-group">
            {!! Form::label('title', 'Title') !!}
            {!! Form::text('title', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('category_id', 'Category') !!}
            {!! Form::select('category_id', array(''=>'options'), null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('photo_id', 'Photo') !!}
            {!! Form::file('photo_id', null,['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('body', 'Description') !!}
            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
        </div>

        
        <div class="form-group">
            {!! Form::submit('Create Post', ['class'=>'btn btn-primary']) !!}
        </div>
    {!! Form::close() !!}

$ php artisan make:request PostsCreateRequest

Requests/PostCreateRequest.php

public function rules()
    {
        return [
            'category_id' => 'required',
            'photo_id' => 'required',
            'title' => 'required',
            'body' => 'required'
        ];
    }

AdminPostsController.php

use App\Http\Requests\PostsCreateRequest;
use Illuminate\Support\Facades\Auth;
public function store(PostsCreateRequest $request)
    {
        //
        $input = $request->all();
        $user = Auth::user();
        if($file = $request['photo_id']){
            $name = time(). $file->getClientOriginalName();
            $file->move('images', $name);
            $photo = Photo::create(['file'=>$name]);

            $input['photo_id'] = $photo->id;
        }
        $user->posts()->create($input);
        return redirect('/admin/posts');
    }

View: posts/index.blade.php

<tr>
			  <td>{{$post->id}}</td>
			  <td>{{$post->user->name}}</td>
			  <td>{{$post->category_id}}</td>
			  <td><img height="50" src="{{$post->photo ? '/' . $post->photo->file : 'https://placehold.it/50x50'}}"></td>
			  <td>{{$post->title}}</td>
			  <td>{{$post->body}}</td>
			  <td>{{$post->created_at->diffForHumans()}}</td>
			  <td>{{$post->updated_at->diffForHumans()}}</td>
			</tr>

$ php artisan make:model Category -m

Model: Category.php

class Category extends Model
{
    //
    protected $fillable = ['name'];
}

Post.php

public function category(){
    	return $this->belongsTo('App\Category');
    }

categories migration files

Schema::create('categories', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->timestamps();
        });

$ php artisan migrate

insert into categories (name) values (‘PHP’);
insert into categories (name) values (‘Laravel’);
insert into categories (name) values (‘JavaScript’);
insert into categories (name) values (‘Photoshop’);

/posts/index.blade.php

<td>{{$post->category ? $post->category->name : 'Uncategorized'}}</td>

AdminPostsController

use App\Category;
public function create()
    {
        //
        $categories = Category::lists('name', 'id')->all();
        return view('admin.posts.create', compact['categories']);
    }

View: posts/create.blade.php

<div class="form-group">
            {!! Form::label('title', 'Title') !!}
            {!! Form::text('title', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('category_id', 'Category') !!}
            {!! Form::select('category_id', array(''=>'Choose Options') + $categories, null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('photo_id', 'Photo') !!}
            {!! Form::file('photo_id', null,['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('body', 'Description') !!}
            {!! Form::textarea('body', null, ['class'=>'form-control', 'rows'=>3]) !!}
        </div>

DBにuncategorizedという値を入れずに、view側で$post->category ? $post->category->name : ‘Uncategorized’と書く方法は面白い、isnt it?

Laravel OneToMany(hasMany)の 作り込みの流れと注意点

– ログイン系のアプリケーションでは、まずUserを作り、一通りmodel, view, controller, middlewareが出来てから、OneToManyの機能を作り込んでいく
– 主要機能や機能的な優先度から開発するのではなく、データベースのテーブル構造に沿って、開発をしていく
– 先にDBにデータを入れながらコーディングしていく

$ php artisan make:controller –resource AdminPostsController

AdminPostsController.php

public function index()
    {
        //
        return view('admin.posts.index');
    }

/layouts/admin.blade.php

<li>
                                <a href="{{route('admin.posts.index')}}">All Posts</a>
                            </li>

                            <li>
                                <a href="{{route('admin.posts.create')}}">Create Post</a>
                            </li>

$ php artisan make:model Post -m

posts migration file

Schema::create('posts', function (Blueprint $table) {
            $table->increments('id');
            $table->integer('user_id')->unsigned()->index();
            $table->integer('category_id')->unsigned()->index();
            $table->integer('photo_id')->unsigned()->index();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });

$ php artisan migrate

Model:Post.php

protected $fillable = [
    	'category_id',
    	'photo_id',
    	'title',
    	'body'
    ];

$ php artisan tinker;
Psy Shell v0.7.2 (PHP 7.1.7 — cli) by Justin Hileman
>>> $post = App\Post::create([‘title’=>’my first post’, ‘body’=>’I love laravel’]);
=> App\Post {#661
title: “my first post”,
body: “I love laravel”,
updated_at: “2019-12-14 20:40:37”,
created_at: “2019-12-14 20:40:37”,
id: 1,
}

AdminPostsController.php

use App\Post;
public function index()
    {
        //
        $posts = Post::all();
        return view('admin.posts.index', compact('post'));
    }

resources/views/posts/index.blade.php

<table class="table table-striped">
		<thead>
		<tr>
		  <th>Id</th>
		  <th>User</th>
		  <th>Category</th>
		  <th>Photo</th>
		  <th>Title</th>
		  <th>body</th>
		  <th>Created</th>
		  <th>Updated</th>
		</tr>
		</thead>
		<tbody>
		@if($posts)
			@foreach($posts as $post)
			<tr>
			  <td>{{$post->id}}</td>
			  <td>{{$post->user_id}}</td>
			  <td>{{$post->category_id}}</td>
			  <td>{{$post->photo_id}}</td>
			  <td>{{$post->title}}</td>
			  <td>{{$post->body}}</td>
			  <td>{{$post->created_at->diffForHumans()}}</td>
			  <td>{{$post->updated_at->diffForHumans()}}</td>
			</tr>
			@endforeach
		@endif
		</tbody>
	</table>

Model:User.php

public function posts(){
        return $this->hasMany('App\Post');
    }

Model:Post.php

public function user(){
    	return $this->belongsTo('App\User');
    }

    public function photo(){
    	return $this->belongsTo('App\Photo');
    }

    public function category(){
    	return $this->belongsTo('App\Category');
    }

> update posts set user_id=10 where id=1;

posts/index.blade.php

<td>{{$post->user->name}}</td>

Role.php

protected $fillable = [
    	'name'
    ];

外部設計やワイヤーフレームだと、ユーザーエクスペリエンスが非常に重要なので、まずユーザーの主要機能を先に書いて管理者ページは後に書いた方が書きやすいが、実装フェーズだとユーザー登録できる管理者から先にコーディングしていく。
他のフレームワークでもそうなのかもしれないが、Laravelの開発ではDBのリレーションが非常に重要なんですね。

設計時と実装時で、作っていく順番が異なるので、気を付けないといけない。主要機能や難易度などから順番に実装するのではなく、DBのリレーション構造順。

Laravel ユーザ削除の書き方

一覧や編集ページから削除ボタンを押すと、$idを渡したcontroller destroyメソッドでdelete()で削除する。画像の場合は、unlink。
削除完了メッセージは、Session::flashでsetして、遷移後のページで、@if(Session::has())で、メッセージを表示する方法がある。

edit.blade.php

{!! Form::open(['method'=>'DELETE', 'action'=>['AdminUsersController@destroy', $user->id]]) !!}
        {{ csrf_field()}}
        
        <div class="form-group">
            {!! Form::submit('Delete User', ['class'=>'btn btn-danger']) !!}
        </div>
    {!! Form::close() !!}

AdminUsersController.php

public function destroy($id)
    {
        //
        User::findOrFail($id)->delete();
        return redirect('/admin/users');
    }

### flushの利用

use Illuminate\Support\Facades\Session;
public function destroy($id)
    {
        //
        User::findOrFail($id)->delete();

        Session::flash('deleted_user', 'The user has benn deleted');
        return redirect('/admin/users');
    }

index.blade.php

@if(Session::has('deleted_user'))
		<p class="bg-danger">{{session('deleted_user')}}</p>
	@endif

deleteする際には、belongsToでコネクトしている画像も削除する

public function destroy($id)
    {
        //
        $user = User::findOrFail($id);

        unlink(public_path() ."/". $user->photo->file);
        $user->delete();

        Session::flash('deleted_user', 'The user has benn deleted');
        return redirect('/admin/users');
    }

User.php

public function isAdmin(){
        if($this->role->name == "administrator" && $this->is_active == 1){
            return true;
        }
        return false;
    }

### ログイン後のページ設定
app/Http/Auth/AuthController.php

protected $redirectTo = '/admin';

ユーザ削除の仕様は、要件定義によって変わってくると思います。その場合は、controllerで制御する。

Laravel 権限よる表示制御と404ページの作成方法

Userモデル側でロールの判定は行い、middlewareではページ遷移の制御を行う
404はviews/errors/フォルダの下に404.blade.phpを作る

### middlewareによる制御
$ php artisan make:middleware Admin
$ git add .
$ git commit -m “user section working except security”

app/Http/kernel.php

protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'admin' => \App\Http\Middleware\Admin::class,
    ];

Route

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

User.php

public function isAdmin(){
        if($this->role->name == "administrator"){
            return true;
        }
        return false;
    }

app/Http/Middleware/Admin.php

use Illuminate\Support\Facades\Auth;
public function handle($request, Closure $next)
    {
        if(Auth::check()){
            if(Auth::user()->IsAdmin()){
                return $next($request);
            }

        }
        return redirect('/');
    }

※return redirect(404);で404にリダイレクトする

### 404ページ作成
resources/views/errors/404.blade.php

<h1>Custom 404 Page</h1>

That’s it.
他のアプリケーションでどうやってるかや開発文化などにもよるが、403、404、503あたりはカスタムで作っておきたい。ちょくちょく、404に異常に凝ったデザインを見かけるが、私はシンプル派。デザイン全然勉強してないからかな😅😅😅

Laravel CollectiveのPATCHとupdateメソッド

Laravel Collectiveで、編集する際は、Controllerから送られてきた$idに対し、Form::Model($user, [‘method’=>’PATCH’, …と書くだけ。

AdminUsersController.php

public function edit($id)
    {
        $user = User::FindOrFail($id);
        $roles = Role::lists('name', 'id')->all();
        return view('admin.users.edit', compact(['user','roles']));
    }

View: index.blade.php
{{route(‘admin.users.edit’, $user->id)}}で、Editページへのリンク

<tr>
			  <td>{{$user->id}}</td>
			  <td><img height="50" src="/{{$user->photo ? $user->photo->file : ''}}"></td>
			  <td><a href="{{route('admin.users.edit', $user->id)}}">{{$user->name}}</a></td>
			  <td>{{$user->email}}</td>
			  <td>{{$user->role->name}}</td>
			  <td>{{$user->is_active == 1 ? 'Active' : 'No Active'}}</td>
			  <td>{{$user->created_at->diffForHumans()}}</td>
			  <td>{{$user->updated_at->diffForHumans()}}</td>
			</tr>

edit.blade.php
->create.blade.phpをコピー
collectiveは、Form::Model($user, [‘method’=>’PATCH’, ‘action’=>[‘AdminUsersController@update’,$user->id], ‘files’=>true])と書く

{!! Form::Model($user, ['method'=>'PATCH', 'action'=>['AdminUsersController@update',$user->id], 'files'=>true]) !!}
        {{ csrf_field()}}
 
        <div class="form-group">
            {!! Form::label('name', 'Name') !!}
            {!! Form::text('name', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('email', 'Email') !!}
            {!! Form::email('email', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('role_id', 'Roles') !!}
            {!! Form::select('role_id',$roles, null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('is_active', 'Status') !!}
            {!! Form::select('is_active',array(1 => 'Active', 0 =>'Not Active'), null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('password', 'password') !!}
            {!! Form::password('password', null, ['class'=>'form-control']) !!}
        </div>
        <div class="form-group">
            {!! Form::label('photo_id', 'Photos') !!}
            {!! Form::file('photo_id', null, ['class'=>'form-control']) !!}
        </div>
        
        <div class="form-group">
            {!! Form::submit('Create Post', ['class'=>'btn btn-primary']) !!}
        </div>
    {!! Form::close() !!}

### 画像の表示
画像がないときは、placehold.itの画像を表示する🤩

<div class="col-sm-3">
        <img src="{{$user->photo ? '/'.$user->photo->file : 'https://placehold.it/400x400'}}" alt="" class="img-responsive img-rounded">
    </div>

AdminUsersController.php

public function update(UsersRequest $request, $id)
    {
        //
        $user = User::findOrFail($id);
        $input = $request->all();

        if($file = $request('photo_id')){
            $name = time(). $file->getClientOriginalName();
            $file->move('images', $name);

            $photo = Photo::create(['file'=>$name]);

            $input['photo_id'] = $photo->id;
        }

        $input['password'] = bcrypt($request->password);
        $user->update($input);
        return redirect('/admin/users');
    }

$ php artisan make:request UsersEditRequest

Request/UsersEditRequest.php

public function authorize()
    {
        return true;
    }
    public function rules()
    {
        return [
            'name' => 'required',
            'email' => 'required',
            'role_id' => 'required',
        ];
    }

AdminUsersController

use App\Http\Requests\UsersEditRequest;
public function update(UsersEditRequest $request, $id)
    {
        //
        $user = User::findOrFail($id);
        if($request->password == '' ){
            $input = $request->except('password');
        } else {
            $input = $request->all();
            $input['password'] = bcrypt($request->password);
        }

        if($file = $request->file('photo_id')){
            $name = time(). $file->getClientOriginalName();
            $file->move('images', $name);

            $photo = Photo::create(['file'=>$name]);

            $input['photo_id'] = $photo->id;
        }

        $user->update($input);
        return redirect('/admin/users');
    }

img src=”{{$user->photo ? ‘/’.$user->photo->file : ‘https://placehold.it/400×400’}}” alt=”” class=”img-responsive img-roundedの書き方は面白い。placeholdは画像化してimgフォルダに格納して呼び出していましたが、CDNのような使い方ができるのであれば、その方が楽。