Fippiyのプログラム学習内容アウトプットBlog

日々の学習内容をアウトプットして振り返りを実施する。

Laravel開発、複数削除処理に対応する【1】書籍情報削除処理を作成する

前回記事までで、書籍一括登録対応を完了させました。

まとめて登録ができたので、次はまとめて削除に対応させていきます。

登録時と同様に、複数件に対する処理ができれば実装できそうです。結果についても登録時と同様に一覧表示で結果を出力できればユーザーにも結果が分かりやすく伝えられそうです。

今回の目的

登録されている書籍情報を複数削除できるようにする

なぜやるか

現状は本1件の詳細画面からしか削除する手段がなく、個別削除しかできないので複数まとめて削除できるようにする

やりたいこと

  • 削除する本を複数選択して削除を行う
  • 所有者のいる本は削除できない

やったこと

  • 書籍一覧から削除したい本を選択できるようにする
  • 書籍一覧をフォーム化してデータ送信できるようにする
  • 処理ステータスを配列格納し、結果表示に使用する

実施内容

複数削除をどう実現するか

複数まとめて登録を行った時は登録したいISBNコードをまとめてフォームに入力することで、入力データから必要な情報を収集して登録していました。

では、複数まとめて削除をするときはどうするか?削除時に削除したいデータをまとめてフォームに入力できれば同じ様な処理で行えそうです。

複数登録の手法から考える

登録時と全く同じ手法で考えるなら、登録されている本のタイトルやISBNコードを指定すれば実現できます…が、どれが登録されているかは一覧表示で確認することになります、もしくは検索してさがすことになります。

一覧表示される書籍情報…つまりは登録されている本のリストが表示できているので、ここから削除したい本を選択できるようにすれば、わざわざ削除したい情報をフォームから入力する必要はありません。

今回は一覧表示されている本のリストから削除させるという方法で実装してみることにしました。

削除する本をリストから選ぶ

現在、書籍情報一覧は初めに表示されるindexページとして用意しています。

f:id:Fippiy:20190524133252p:plain

書籍一覧表示

登録済み書籍のリストとして表示されます。

ここで削除したい本を選択して削除実行とすれば、一括削除できそうです。

わざわざ一括削除用ページを用意する必要もなさそうですね。削除なので、削除専用ページに移動してからでないと出来ないようにするべき…という考えもあるかもしれませんが。

今回は、この一覧表示から削除できるようにしていきます。

 

ビューを編集する

コンポーネントを編集する

まずは一覧から対象の本を選択できるようにします。

# ~/resources/views/components/books_list.blade.php

@section('content')
 <div class="index-content">
  <div class="books-list">
   <div class="books-list__title bookpage-color">
    登録書籍一覧
   </div>
   @if (isset($books))
    @component('components.books_list',['books'=>$books])
     @slot('page_path')
      book
     @endslot
     @slot('detail')
      detail
     @endslot
    @endcomponent
   @endif
  </div>
 </div>
@endsection

book.indexのコンテンツ部です。

リスト表示についてはコンポーネント"books_list"として作成しています。

indexからは編集できないので、コンポーネントを修正することにしました。

 

[編集前]

# ~/resources/views/components/books_list.blade.php

<div class="book-table">
 @if (isset($books))
  @foreach ($books as $book)
   <div class="book-table__list">
    <div class="book-table__list--picture">
     <a href="/{{$page_path}}/{{$book->id}}">
      @if (isset($book->picture))
       <img src="{{$book->picture}}">
      @elseif (isset($book->cover))
       <img src="{{$book->cover}}">
      @else
       <img src="../image/no-entry.jpg">
      @endif
     </a>
    </div>
    <div class="book-table__list--detail">
     <a href="/{{$page_path}}/{{$book->id}}">
      <h3 class="list-book-title">{{$book->title}}</h3>
     </a>
     <p class="list-book-detail">
      {{ str_limit($book->$detail, $limit = 300, $end = '...') }}
     </p>
    </div>
   </div>
  @endforeach
 @endif
</div>

コンポーネントでアクションから受け取った配列からデータを展開して表示しています。ここに選択用のチェックボックスを加えることにしました。

 

[編集後]

# ~/resources/views/components/books_list.blade.php

@foreach ($books as $book)
 <div class="book-table__list">
  <div class="book-table__list--checkbox">
   <input type="checkbox" name="select_books" value="{{$book->id}}">
  </div>
  <div class="book-table__list--picture">
   <a href="/{{$page_path}}/{{$book->id}}">
    @if (isset($book->picture))
     <img src="{{$book->picture}}">
    @elseif (isset($book->cover))
     <img src="{{$book->cover}}">
    @else
     <img src="../image/no-entry.jpg">
    @endif
   </a>
  </div>
  <div class="book-table__list--detail">
   <a href="/{{$page_path}}/{{$book->id}}">
    <h3 class="list-book-title">{{$book->title}}</h3>
   </a>
   <p class="list-book-detail">
    {{ str_limit($book->$detail, $limit = 300, $end = '...') }}
   </p>
  </div>
 </div>
@endforeach

リストの左側となる場所へselect_bookとして配列を作成し、ここに本のidを付与してチェックボックスを作成しました。

f:id:Fippiy:20190524135201p:plain

一覧表示選択ボックス

一覧表示から複数データを選択できるような形となりました。

ここから選択したデータに対してアクションがおこせるようになれば、一括削除ができそうです。

 

一覧表示をフォーム化する

チェックボックスからデータを送信できる形にしたので、フォームを追加してそのまま実際に送信できるように編集することにしました。

 

# ~/resources/views/components/books_list.blade.php

<div class="book-table">
 @if (isset($books))
  <form action="{{ route('book.some_delete') }}" method="post">
   <div class="book-table__btn">
    <span>操作:</span>
    <input type="submit" class="book-table__btn--delete" value="書籍情報一括削除">
   </div>
   {{ csrf_field() }}
   @foreach ($books as $book)
    <div class="book-table__list">
     <div class="book-table__list--checkbox">
     <input type="checkbox" name="select_books" value="{{$book->id}}">
    </div>
 
   〜 中略 〜
 
   @endforeach
  </form>
 @endif
</div>

リスト全体をフォームとして作成し、トークンと一括削除用のsubmitボタンを用意しました。これで、リスト全体がそのまま削除用フォームとなりました。

f:id:Fippiy:20190524140030p:plain

一括削除フォーム

リスト内で削除するデータにチェックをいれて削除ボタンを押すと削除される…ようにします。

 

コントローラーを編集する

フォームデータを受け取る

フォームからpost送信できるようになりましたので、次はコントローラーで値を受け取って処理を実施していきます。

# ~/app/Http/Controllers/BookController.php

public function someDelete(Request $request){
 // フォームデータ取得
 unset($request['_token']); // トークン削除
 $datas = $request->input('select_books'); // 削除書籍情報をフォームから取得
 $count = count($datas); // 取得件数

削除処理の冒頭部分です。今回、フォームから送信されるselect_booksは配列としてフォーム上で指定しているので、$request->allで受け取るとネストした配列となってしまいます。また、他の値は送信されないので、select_books配列のみが送信されますので、select_booksのみを受け取る記述としました。

$datasには削除を行う本のidが配列として格納されます。

# terminal

$datas

=> [

     "1",

     "2",

   ]

 

送信データがなければ処理しない

仮にフォームで選択がないまま送信すると、処理するデータがありませんので、バリデーションでエラーを返す処理をいれました。

# ~/app/Http/Controllers/BookController.php

// 取得データなければ処理中止
if ($count == 0) {
 // 削除情報が1件もないときはバリデーションエラーにする
 $validator = Validator::make(['deletebook' => false], ['deletebook' => 'accepted'],
  ['削除する本が選択されていません']);
 if ($validator->fails()) {
  return redirect('book')
   ->withErrors($validator)
   ->withInput();
 }
}

 

受け取ったデータを処理する

続いて、$datasとして受け取った本のidにたいして処理を実施します。

# ~/app/Http/Controllers/BookController.php

// 複数登録同様に結果配列をつくる
// 処理用配列へ追加
$i = 1; // 結果出力番号
foreach($datas as $data){
 // 一度に削除できる上限数で処理を停止
 if ($i > 20){
  break;
 }
 // 配列格納
 $deletebooks = array(
  'process' => 'processing', // 処理中ステータス
  'number' => $i, // 番号
  'bookdata_id' => $data, // 削除する本のid
  'msg' => null, // 処理テキスト
 );
 $i++;
}

先程の続きです。一括登録の記事をみて頂いた方には分かると思いますが、一括登録をした時のコードを利用して同じように処理を実施しています。

まず、削除処理に対しても上限件数を設定しています。

そして、処理データの情報を格納する配列にデータを準備し処理状況と本のid、処理結果のテキストが登録出来るようにしています。

 

所有者の確認

削除する時の条件として、所有者がいる場合は削除できないという点がありました。

所有者のいる書籍情報を削除してしまうと、所有者が参照する本がないことになってしまいます。

# ~/app/Http/Controllers/BookController.php

// 所有者がいないか確認
for ($i = 0; $i < $count; $i++){
 $have_property = Property::where('bookdata_id', $deletebooks[$i]['bookdata_id'])
  ->first();
 if ($have_property != null) {
  data_set($deletebooks[$i], 'title', $have_property->bookdata->title);
  // 表示タイトル名を追加
  data_set($deletebooks[$i], 'msg', "所有者がいるため削除できません");
  // フォームデータ重複チェック
  data_set($deletebooks[$i], 'process', 'use_user_on'); // 処理ステータスエラー
 }
}

$deletebooksに格納されている情報から本のidを参照して所有者情報に所持者がいるか確認しています。この処理自体は1件の本を削除する時に確認していた作業と同じです。配列に複数の本情報があるので、複数くりかえしていることと、変数を配列用にあわせてやれば、ここは以外と簡単に作成できました。

 

削除処理

だれも所有していなければ、いよいよ削除を行います。

# ~/app/Http/Controllers/BookController.php

// 削除データとりだし
for ($i = 0; $i < $count; $i++){
 if ($deletebooks[$i]['process'] == 'processing'){
  // 削除レコード取得
  $delete_book = Bookdata::find($deletebooks[$i]['bookdata_id']);

  // 写真削除がある場合
  if (isset($delete_book['picture'])) {
 
   〜 略 〜
  }
  // レコード削除
  data_set($deletebooks[$i], 'title', $delete_book->title); // 表示タイトル名を追加
  $delete_book->delete();
  data_set($deletebooks[$i], 'msg', "を削除しました"); // メッセージを追加
  data_set($deletebooks[$i], 'process', 'completion'); // 処理ステータス変更
 }
}
return view('book.delete_result',['answers' => $deletebooks]);

$deletebooksで処理中のステータスで残っているデータに対して削除処理を行っています。

実際に削除する前に処理結果に'title'キーの配列を追加し、本のタイトル名を取得して結果表示させるようにしています。

写真の処理がありますが、個別登録や後から任意に写真を追加している場合はここで削除を行います。この処理は個別削除時にも実施しており内容も同じであるため割愛しています。

 

ここまで処理が完了できれば、結果表示用のビューにデータを渡して上げてコントローラー処理は終了です。

 

結果表示する

一括登録と同様に、一括削除についても結果表示画面に出力するようにしました。

f:id:Fippiy:20190524143244p:plain

処理結果

一括登録時の結果画面を利用して削除についても作成しました。

 

# ~/resources/views/book/delete_result.blade.php

@section('content')
 <div class="index-content">
  <div class="books-list">
   <div class="books-list__title bookpage-color">
    書籍削除結果
   </div>
   <div class="isbn-result">
   @foreach ($answers as $answer)
    <div class="isbn-result__box">
     <div class="isbn-result__box--number">{{$answer['number']}}</div>
     <div class="isbn-result__box--detail">
      <div class="isbn-result__box--head">
       <span class="isbn-result__box--isbn">{{$answer['title']}}</span>
       <span class="isbn-result__box--msg">{{$answer['msg']}}</span>
      </div>
     </div>
    </div>
   @endforeach
  </div>
 </div>
@endsection

処理結果データを順番に表示させています。

 

 

以上で書籍情報の一括削除としては一通り完成しました…が、このままでは不完全と分かりました。

次の記事で対応していきます。