
マイケル
どうもみなさんこんにちは!
マイケルです!
マイケルです!

エレキベア
クマ〜〜〜〜〜〜!!

マイケル
今回も前回に続いて、
自動販売機システムを作っていきます!
自動販売機システムを作っていきます!

マイケル
とりあえずはこれで最終回の予定だ!
メインとなる購入画面を作っていくぞ!
メインとなる購入画面を作っていくぞ!

エレキベア
いよいよ大詰めクマ〜〜〜!!
参考書籍

マイケル
基本的な実装方法などは、
前回に引き続きこちらを参考にして進めていきます。
前回に引き続きこちらを参考にして進めていきます。

エレキベア
やったるクマ〜〜〜〜!!
購入画面の仕様

マイケル
さて、第一回で説明しましたが
購入画面の仕様は以下のようになっています!
購入画面の仕様は以下のようになっています!

↑自動販売機:購入画面


マイケル
上部の入金エリアでお金を投入し、
下部の飲み物をクリックすると購入する
という、自動販売機のシミュレータになります!
下部の飲み物をクリックすると購入する
という、自動販売機のシミュレータになります!

エレキベア
まんま自販機クマね

マイケル
メンテナンス画面作成の時にはjuiceテーブルのみ使用していましたが、
自動販売機ごとの金額管理を行う必要があるため、
新たにmachineテーブルも使用します。
自動販売機ごとの金額管理を行う必要があるため、
新たにmachineテーブルも使用します。


エレキベア
確かmachineテーブルとjuiceテーブルは
1:Nの関係だったクマね
1:Nの関係だったクマね

マイケル
よく覚えてたね!
自動販売機ごとに飲み物の種類や在庫を管理するから
下記の図のようになるよ!
自動販売機ごとに飲み物の種類や在庫を管理するから
下記の図のようになるよ!


マイケル
このような関係にすることで、
自動販売機ごとに分けて処理することができるんだ!
自動販売機ごとに分けて処理することができるんだ!

↑自動販売機内の飲み物の購入ができる

エレキベア
なるほどクマね〜〜
やったるクマよ〜〜〜〜!!
やったるクマよ〜〜〜〜!!

マイケル
それじゃ作っていこう!
購入画面の作成

マイケル
今回作成するのは下記の部分!
新たに作るmachineテーブルのモデルと、
「/buy」で実行する画面・処理の実装だ!
新たに作るmachineテーブルのモデルと、
「/buy」で実行する画面・処理の実装だ!


エレキベア
これで全体が完成するクマね

マイケル
もう少しの辛抱だ!
今回もMVCの順で見ていこう!
今回もMVCの順で見ていこう!
Model

マイケル
まずはModelの作成からだ!
juiceテーブルのモデルはもう作成しているから、
新たにmachineテーブルのモデルを作成しましょう!
juiceテーブルのモデルはもう作成しているから、
新たにmachineテーブルのモデルを作成しましょう!
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Machine extends Model
{
protected $table = 'machine';
protected $primaryKey = 'id';
protected $guarded = array('id');
// 関連するjuceテーブルデータを取得
public function juices()
{
return $this->hasMany('App\Juice', 'm_id');
}
}
↑machineテーブルのModelクラス
エレキベア
変数の部分は前も出てきたクマが、
新たに作っている juicesメソッドは何クマ??
新たに作っている juicesメソッドは何クマ??

マイケル
これは、machineテーブルに紐づくjuiceテーブルのレコードを取得するために
hasMany()メソッドを定義しているんだ!
hasMany()メソッドを定義しているんだ!

マイケル
これを定義して、View側で「juices」を指定することで、
machineテーブルの「id」と
juiceテーブルの「m_id(第2引数)」に紐づくレコードを全て取得できるんだよ。
machineテーブルの「id」と
juiceテーブルの「m_id(第2引数)」に紐づくレコードを全て取得できるんだよ。

エレキベア
それは便利クマね・・・。
自動販売機のレコードに紐づく飲み物一覧を取得するには持ってこいクマ
自動販売機のレコードに紐づく飲み物一覧を取得するには持ってこいクマ

マイケル
そういうことだね!
表示の方法はこの後のViewの実装でみていこう!
表示の方法はこの後のViewの実装でみていこう!
View

マイケル
購入画面を新たに作成するため、
下記Viewクラスを作成しましょう!
下記Viewクラスを作成しましょう!
■ juiceapp.blade.php
格納先:app/resources/views/layouts
格納先:app/resources/views/layouts
<html>
<head>
<title>***ENEGY SOUL***</title>
<style>
body { font-family:Arial; font-size:16pt; color:#555555; background-color:#f5f5f5; margin:5px;}
hr { margin:5px 20px; border-top:1px dashed #555555; }
.title-area { position:relative; background-color:#333333; height:100px; width:100%; }
.title-area span { position:absolute; top:10px; left:15px; color:#eeeeee; font-size:45pt; text-align:left; letter-spacing:-4pt; }
.message-area { position:relative; height:50px; width:100%; font-size:13pt; overflow-y: scroll; }
.message-area .msg { position:absolute; left:15px; }
.message-area .err_msg { position:absolute; left:15px; color:red; }
.footer { text-align:left; font-size:10pt; margin:10px; border-top:solid 1px #ccc; color:#ccc; }
.juice-table th { background-color:#999; color:#fff; padding:5px 10px; }
.juice-table td { vertical-align:top; border: solid 1px #aaa; color:#999; padding:5px 5px; }
.juice-table .ju_image { width:70px; }
.juice-table .buttun-area { background-color:none; border:none; display:table-cell; vertical-align:middle; }
.juice-table .ins_button { background-color:#333333; height:45px; width:80px; border-radius:7%; color:#eee; font-size:15px; cursor:pointer; }
.juice-table .upd_button { background-color:#0066FF; height:45px; width:80px; border-radius:7%; color:#eee; font-size:15px; cursor:pointer; }
.juice-table .del_button { background-color:#FF3333; height:45px; width:80px; border-radius:7%; color:#eee; font-size:15px; cursor:pointer; }
.image-table td { border:none; }
.juice-table-title { font-size:18px; margin:15px 0px 0px 0px; }
/* ↓以下のCSS定義を追加 */
.money-area { height:70px; }
.money-area .money-text-area { vertical-align:center; text-align:right; }
.money-area .money-text { background-color:#333333; color:#eee; height:30px; width:120px; padding: 5px; font-size:20px; }
.money-area .money-10 { background-color:#d2b48c; border-radius:50%; width:50px; height:50px; margin: 0px 5px 5px 5px; font-size: 18px; cursor:pointer; }
.money-area .money-100 { background-color:#dcdcdc; border-radius:50%; width:50px; height:50px; margin: 0px 5px 5px 5px; font-size: 18px; cursor:pointer; }
.money-area .money-500 { background-color:#f5f5dc; border-radius:50%; width:50px; height:50px; margin: 0px 5px 5px 5px; font-size: 18px; cursor:pointer; }
.money-area .money-1000 { background-color:#fff8dc; border-radius:5%; width:80px; height:45px; margin: 0px 5px 5px 5px; font-size: 20px; cursor:pointer; }
.money-area .money-oturi { background-color:#eee; border-radius:5%; width:42px; height:42px; font-size:15px; text-align:center; cursor:pointer; }
.juice-container { display:flex; justify-content:flex-start; flex-wrap:wrap; width:100%; list-style:none; margin:0; padding:0; }
.juice-container li { display:flex; flex-flow:column; width:300px; border:1px solid #ccc }
.juice-container .ju-image { width:300px; height:300px; }
.juice-container .juice-name { width:300px; text-align:center; }
.juice-container .cold-msg { background-color:blue; color:#eee; width:300px; text-align:center; }
.juice-container .juice-price { background-color:#333333; color:#eee; width:300px; text-align:right; }
.juice-container .push-button-area { width:300px; text-align:center; margin: 10px; }
.juice-container .push-button { background-color:#666666; color:#eee; border-radius:100vh; width:200px; font-size:30px; cursor:pointer; }
.juice-container .push-button-disabled { background-color:#ccc; color:#eee; border-radius:100vh; width:200px; font-size:30px; }
.juice-container .juice-stock { width:300px; text-align:right; }
</style>
</head>
<body>
<div class="title-area">
<span>@yield('title')</span>
</div>
<div class="message-area">
@yield('message')
</div>
<hr size="1">
<div class="content">
@yield('content')
</div>
<div class="footer">
@yield('footer')
</div>
</body>
</html>
■ index.blade.php
格納先:app/resources/views/buy
格納先:app/resources/views/buy
@extends('layouts.juiceapp')
@section('title', 'ENEGY SOUL*')
@section('message')
@if (count($errors) > 0)
<span class="err_msg">
@foreach ($errors->all() as $error)
<div>・{{$error}}</div>
@endforeach
</span>
@else
<span class="msg">
@if (isset($machine))
<div>・普通の自販機シミュレータでござる。</div>
@else
<div>・対象の自動販売機が無いでござる。</div>
@endif
</span>
@endif
@endsection
@section('content')
@if (isset($machine))
<table class="money-area">
<tr>
<form action="/buy/money" id="moneyform" method="post">
@csrf
<input type="hidden" name="id" value="{{$machine->id}}">
<td class="money-text-area"><div class="money-text">¥{{$machine->money}}</div></td>
<td><input class="money-oturi" type="submit" name="oturi" value="返却"></td>
<td><input class="money-10" type="submit" name="10button" value="10"></td>
<td><input class="money-100" type="submit" name="100button" value="100"></td>
<td><input class="money-500" type="submit" name="500button" value="500"></td>
<td><input class="money-1000" type="submit" name="1000button" value="1000"></td>
</form>
</tr>
</table>
<br>
@if ($machine->juices != null)
<ul class="juice-container">
@foreach ($machine->juices as $juice)
<li>
<form action="/buy" id="buyform_{{$juice->id}}" method="post">
@csrf
<input type="hidden" name="id" value="{{$juice->id}}">
<input type="hidden" name="m_id" value="{{$juice->m_id}}">
<input type="hidden" name="price" value="{{$juice->price}}">
<input type="hidden" name="money" value="{{$machine->money}}">
<img class="ju-image" src="data:image/jpeg;base64,{{$juice->ju_image}}" alt="image">
<div class="juice-name">{{$juice->name}}</div>
<div class="cold-msg">つめた〜い</div>
<div class="juice-price">{{$juice->price}}円</div>
<div class="push-button-area">
@if (0 < $juice->stock)
<input class="push-button" type="submit" value="PUSH">
@else
<input class="push-button-disabled" type="submit" value="SOLD" disabled>
@endif
</div>
<div class="juice-stock">在庫:{{$juice->stock}}個</div>
</form>
</li>
@endforeach
</table>
@endif
@endif
@endsection
@section('footer')
copyright 2020 都会のエレキベア.
@endsection

マイケル
「juiceapp.blade.php」は前回作成しているからCSSの追加のみ!
メインとなる購入画面は「buy」フォルダの下に作成しましょう!
メインとなる購入画面は「buy」フォルダの下に作成しましょう!

マイケル
それでは各処理部分を見ていきます!

エレキベア
わくわくクマ〜〜〜〜〜
・入金処理の記述

マイケル
画面上部にある入金エリアはこんな感じだ!

<table class="money-area">
<tr>
<form action="/buy/money" id="moneyform" method="post">
@csrf
<input type="hidden" name="id" value="{{$machine->id}}">
<td class="money-text-area"><div class="money-text">¥{{$machine->money}}</div></td>
<td><input class="money-oturi" type="submit" name="oturi" value="返却"></td>
<td><input class="money-10" type="submit" name="10button" value="10"></td>
<td><input class="money-100" type="submit" name="100button" value="100"></td>
<td><input class="money-500" type="submit" name="500button" value="500"></td>
<td><input class="money-1000" type="submit" name="1000button" value="1000"></td>
</form>
</tr>
</table>

マイケル
これも前回と同じく、一つのformでいくつかの処理で分けるので
submitボタンのnameを分けておきましょう!
submitボタンのnameを分けておきましょう!

エレキベア
お金の種類によって入金額を変えるクマね
・購入処理の記述

マイケル
そして購入部分はこちらになります!

@if ($machine->juices != null)
<ul class="juice-container">
@foreach ($machine->juices as $juice)
<li>
<form action="/buy" id="buyform_{{$juice->id}}" method="post">
@csrf
<input type="hidden" name="id" value="{{$juice->id}}">
<input type="hidden" name="m_id" value="{{$juice->m_id}}">
<input type="hidden" name="price" value="{{$juice->price}}">
<input type="hidden" name="money" value="{{$machine->money}}">
<img class="ju-image" src="data:image/jpeg;base64,{{$juice->ju_image}}" alt="image">
<div class="juice-name">{{$juice->name}}</div>
<div class="cold-msg">つめた〜い</div>
<div class="juice-price">{{$juice->price}}円</div>
<div class="push-button-area">
@if (0 < $juice->stock)
<input class="push-button" type="submit" value="PUSH">
@else
<input class="push-button-disabled" type="submit" value="SOLD" disabled>
@endif
</div>
<div class="juice-stock">在庫:{{$juice->stock}}個</div>
</form>
</li>
@endforeach
</table>
@endif

マイケル
ここでMachineモデルの「hasMany()」で取得した結果を出力しているよ!
$machine->juicesと書くことで、juices()メソッドの戻り値が出力されるんだ!
$machine->juicesと書くことで、juices()メソッドの戻り値が出力されるんだ!

エレキベア
Modelの時の説明してたやつクマね
これだけで結合結果が出力できるのはすごいクマね
これだけで結合結果が出力できるのはすごいクマね

マイケル
それからBladeでは「@if」を使うことで判定条件も記述できるよ!
上の例では在庫(stock)が0の時にはボタンを非活性にしているんだ。
上の例では在庫(stock)が0の時にはボタンを非活性にしているんだ。

エレキベア
条件文が使えれば柔軟に表示処理を行えるクマね
Controller

マイケル
そして最後に、Controllerの記述はこんな感じだ!
・index()メソッド -> 初期表示
・buy()メソッド -> 購入処理
・maney()メソッド -> 入金処理
というように分けているよ!
・index()メソッド -> 初期表示
・buy()メソッド -> 購入処理
・maney()メソッド -> 入金処理
というように分けているよ!
<?php
namespace App\Http\Controllers;
use App\Juice;
use App\Machine;
use App\Http\Requests\BuyRequest;
use Illuminate\Http\Request;
class BuyController extends Controller
{
public function index(Request $request)
{
// クエリパラメタから対象の自動販売機を取得
$machine = Machine::where('id', $request->id)->first();
return view('buy.index', ['machine' => $machine]);
}
public function buy(BuyRequest $request)
{
// machineテーブルのmoneyを減らす
Machine::where('id', $request->m_id)->decrement('money', $request->price);
// juiceテーブルの在庫を減らす
Juice::where('id', $request->id)->decrement('stock', 1);
return redirect('/buy?id=' . $request->m_id);
}
public function money(Request $request)
{
if (isset($_POST['oturi'])) {
// おつり返却処理
// mochineテーブルのmoneyを0に更新
Machine::where('id', $request->id)->update(['money'=>0]);
} else {
// 入金処理
$input_money = 0;
if (isset($_POST['10button'])) {
$input_money = 10;
} elseif (isset($_POST['100button'])) {
$input_money = 100;
} elseif (isset($_POST['500button'])) {
$input_money = 500;
} elseif (isset($_POST['1000button'])) {
$input_money = 1000;
}
// mochineテーブルのmoneyに加算
Machine::where('id', $request->id)->increment('money', $input_money);
}
return redirect('/buy?id=' . $request->id);
}
}
・初期表示

マイケル
初期表示の処理は下記のように記述していて、
自動販売機IDに紐づくデータのみ取得しています!
自動販売機IDに紐づくデータのみ取得しています!
public function index(Request $request)
{
// クエリパラメタから対象の自動販売機を取得
$machine = Machine::where('id', $request->id)->first();
return view('buy.index', ['machine' => $machine]);
}
↑初期表示処理
マイケル
こう書くことで、
http://【サーバアドレス】/buy?id=【自動販売機ID】
でアクセスすればその自販機の一覧のみ取得できるんだ!
http://【サーバアドレス】/buy?id=【自動販売機ID】
でアクセスすればその自販機の一覧のみ取得できるんだ!

エレキベア
リクエストにパラメータを渡しているわけクマね


↑自動販売機IDに紐づく飲み物が表示される
・購入、入金処理

マイケル
購入処理は下記のように、
飲み物の価格の分だけお金を、在庫は1ずつ減らしています!
飲み物の価格の分だけお金を、在庫は1ずつ減らしています!
public function buy(BuyRequest $request)
{
// machineテーブルのmoneyを減らす
Machine::where('id', $request->m_id)->decrement('money', $request->price);
// juiceテーブルの在庫を減らす
Juice::where('id', $request->id)->decrement('stock', 1);
return redirect('/buy?id=' . $request->m_id);
}
↑購入処理
エレキベア
減らす処理はどこでやってるクマ?

マイケル
decrement(【項目名】,【減らす数】)
のメソッドを使うことで減らすことができるんだ!
のメソッドを使うことで減らすことができるんだ!

エレキベア
メソッドが用意されていると楽ちんクマね

マイケル
そして入金処理は以下のとおり!
前回と同じくsubmitボタンの名前で分岐させています!
前回と同じくsubmitボタンの名前で分岐させています!
public function money(Request $request)
{
if (isset($_POST['oturi'])) {
// おつり返却処理
// mochineテーブルのmoneyを0に更新
Machine::where('id', $request->id)->update(['money'=>0]);
} else {
// 入金処理
$input_money = 0;
if (isset($_POST['10button'])) {
$input_money = 10;
} elseif (isset($_POST['100button'])) {
$input_money = 100;
} elseif (isset($_POST['500button'])) {
$input_money = 500;
} elseif (isset($_POST['1000button'])) {
$input_money = 1000;
}
// mochineテーブルのmoneyに加算
Machine::where('id', $request->id)->increment('money', $input_money);
}
return redirect('/buy?id=' . $request->id);
}
↑入金処理
マイケル
こっちは逆にincrement()メソッドで加算しているね。

エレキベア
簡潔にかけて綺麗クマ
・バリデーションの記述

マイケル
そして次にバリデーションです!
これも前回と同じくFormRequestクラスを作成して設定しましょう!
これも前回と同じくFormRequestクラスを作成して設定しましょう!
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class BuyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
if ($this->path() == 'buy')
{
return true;
}
return false;
}
/**
* バリデーションルールの定義
*
* @return array
*/
public function rules()
{
// お金が足りているかのチェック
$remain = function($attribute, $value, $fail) {
$input_data = $this->all();
if ($input_data['money'] - $input_data['price'] <= 0)
{
$fail('【金額】お金が足りないでござる。');
}
};
// バリデーション
$price_validation = ['required', $remain];
return [
'id'=>'required',
'm_id'=>'required',
'price'=>$price_validation,
'money'=>'required',
];
}
/**
* エラーメッセージの定義
*
* @return array
*/
public function messages()
{
return [
'id.required'=>'【ジュースID】ジュースIDが設定されていないでござる。',
'm_id.required'=>'【自動販売機ID】自動販売機IDが設定されていないでござる。',
'price.required'=>'【価格】価格が設定されていないでござる。',
'money.required'=>'【金額】金額が設定されていないでござる。',
];
}
}

エレキベア
今回もrule()メソッドに少し
個別の処理を入れてるクマね
個別の処理を入れてるクマね

マイケル
その通り!
今回は複数項目の関連チェックを行うため、
下記チェックを追加しているよ!
今回は複数項目の関連チェックを行うため、
下記チェックを追加しているよ!
// お金が足りているかのチェック
$remain = function($attribute, $value, $fail) {
$input_data = $this->all();
if ($input_data['money'] - $input_data['price'] <= 0)
{
$fail('【金額】お金が足りないでござる。');
}
};

マイケル
このように記述すれば少し複雑なチェック処理も記述できるんだ!

エレキベア
オリジナルのチェック処理も入れ放題クマね
・Routeの設定

マイケル
それでは最後に記述したControllerを呼び出すため
Routeの設定をしましょう!
Routeの設定をしましょう!
<?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('edit', 'EditController@index');
Route::post('edit', 'EditController@edit');
Route::get('buy', 'BuyController@index');
Route::post('buy', 'BuyController@buy');
Route::post('buy/money', 'BuyController@money');

マイケル
追加するのは下の三行!
今回は入金処理と購入処理を別のアクションにしているため、postを2つ記述しているよ!
今回は入金処理と購入処理を別のアクションにしているため、postを2つ記述しているよ!

マイケル
以上で自動販売機システムの完成だーーー!!

エレキベア
やったクマ〜〜〜〜!!!!
おわりに

マイケル
というわけで三回に渡る長編企画でしたが
いかがだったでしょうか??
いかがだったでしょうか??

エレキベア
早足だったクマが、一つのシステムを完成させて
自信がついたクマーー!!
自信がついたクマーー!!

マイケル
それはよかった!
今回作ったシステムは最低限動作するけど、
・排他処理の実装
・ユーザ管理や権限周りの実装
・ページングの実装
あたりは省略しているから、次のステップアップを目指す人は挑戦してみてね!
今回作ったシステムは最低限動作するけど、
・排他処理の実装
・ユーザ管理や権限周りの実装
・ページングの実装
あたりは省略しているから、次のステップアップを目指す人は挑戦してみてね!

エレキベア
まだまだシステム開発は先が長いクマね

マイケル
知れば知るほど奥が深いWEBアプリ開発!
興味を持った方はぜひ挑戦してみてね!
興味を持った方はぜひ挑戦してみてね!

エレキベア
クマ〜〜〜〜〜!!

マイケル
それでは今日はこの辺で!
アデュー!!
アデュー!!
【Laravel】第三回 自動販売機システムを作ろう! 〜購入画面の作成〜 〜完〜
コメント