第12回PHPポケモンは「レスポンス機能編」です。
メッセージやデータのやり取り部分を、よりシステム風に作成していきます。
第1回はコチラ
レスポンス(Response)の実装
今までは結果(メッセージ等)はechoを使って出力していましたが処理が行われたタイミングで出力されてしまうため、何かと不便です。また、詳細やステータスなど配列で受け取ったものも、そのデータの置き場が定まっておらずクラス内で解決させていたので使い回しにも向いていませんでしたね。
今回はそれらを解決するために、レスポンス用のクラス(トレイト)を作成して、メッセージやデータを自由に取り出しできるようにしていきましょう。
メッセージの返却
まずはメッセージの処理から作成していきます。新しくResponseTraitをトレイトフォルダへ作成しましょう。
レスポンス用トレイト(/Traits/ResponseTrait.php)
<?php
trait ResponseTrait
{
/**
* メッセージの格納用
* @var array
*/
private $msgs = [];
/**
* メッセージの取得
*
* @return array
*/
public function getMessages()
{
return $this->msgs;
}
/**
* メッセージの格納
*
* @param string $msg
* @param string $param error|success default=null
* @return array
*/
public function setMessage($msg, $param=null)
{
$this->msgs[] = [$msg, $param];
}
}
構造は至って単純です。用意したmsgsというプロパティにsetMessageというメソッドを使って格納し、getMessagesというメソッドを使って取り出すだけです。メッセージは1件だけとは限らないため、配列を想定して管理します。
setMessageの第2引数の$paramは、そのメッセージがどういったタイプかを判別するためのパラメーターとして用意しています。大きく分ければ、エラーメッセージと成功メッセージの2つがありますよね。今回はその2種類を判別して、パラメーターとしてセットすることで、出力メッセージの色に反映させる仕様にします。
それでは、ポケモンクラスでこのレスポンス用トレイトを読み込んで、出力メッセージをセットしてみましょう。
<?php
require_once(__DIR__.'/../Traits/ResponseTrait.php');
require_once(__DIR__.'/../Traits/Pokemon/SetTrait.php');
require_once(__DIR__.'/../Traits/Pokemon/GetTrait.php');
// ポケモン
abstract class Pokemon
{
use ResponseTrait;
use SetTrait;
use GetTrait;
--省略
/**
* インスタンス作成時に実行される処理
*
* @param object|null
* @return void
*/
public function __construct($before=null)
{
// 進化前のポケモンと一致しているかチェック
if(is_a($before, $this->child_class ?? null)){
// 進化した際の処理
$this->takeOverAbility($before);
$this->setMessage($this->name.'に進化した', 'success');
$this->checkMove();
}else{
// 捕まえた際の処理
$this->setLevel();
$this->setDefaultExp();
$this->setDefaultMove();
$this->setIv();
$this->setMessage($this->name.'をゲットした', 'success');
}
}
/**
* レベルアップ処理
*
* @var integer
*/
protected function actionLevelUp()
{
// レベルアップ
$this->level++;
$this->setMessage($this->getNickName().'のレベルは'.$this->level.'になった!', 'success');
// 現在のレベルで習得できる技があるか確認
$this->checkMove();
}
/**
* 現在のレベルで覚えられる技があるか確認する処理
*
* @var integer
*/
protected function checkMove()
{
// レベルアップして覚えられる技があれば習得する
$level_move_keys = array_keys(array_column($this->level_move, 0), $this->level);
foreach($level_move_keys as $key){
$move_name = $this->level_move[$key][1];
// 覚えようとしている技が重複していないか確認
if(!in_array($move_name, $this->move, true)){
$this->setMove($move_name);
$this->setMessage($move_name.'を覚えた!', 'success');
}
}
}
--省略
}
今までechoで記述していた部分を、setMessageを使って格納していきます。ポケモンクラスに読み込んでおけば、GetTraitやSetTraitでも使用できるため、同じように格納しておきましょう。
一括登録
ポケモンクラスを操作するのはコントローラークラスです。もちろんコントローラークラスでもメッセージが発生しているため、ポケモンクラスと同様にコントローラークラスでもレスポンスのトレイトを読み込んでセットしていきます。
コントローラークラス(/Class/Controller.php)
<?php
require_once(__DIR__.'/../Traits/ResponseTrait.php');
require_once(__DIR__.'/Pokemon/Pikachu.php');
require_once(__DIR__.'/Pokemon/Fushigidane.php');
require_once(__DIR__.'/Pokemon/Hitokage.php');
require_once(__DIR__.'/Pokemon/Zenigame.php');
// コントローラー
class Controller
{
use ResponseTrait;
--省略
/**
* @return void
*/
public function __construct($post, $session)
{
if(isset($post['pokemon'])){
// ポケモンをゲットした
$this->checkPokemon($post['pokemon']);
}
if(isset($post['action'])){
// アクションを選択した
$this->pokemon = $session['pokemon'] ?? null; # 更新時エラー回避用にnullをセット
// アクションメソッドの実行
$this->action($post['action'], $post['params'][$post['action']] ?? null);
}
}
/**
* ポケモンクラスの存在チェック
*
* @param string $pokemon(class_name)
* @var void
*/
private function checkPokemon($pokemon)
{
if(class_exists($pokemon)){
$this->pokemon = new $pokemon;
// Pokemonのインスタンスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}else{
$this->setMessage('選択されたポケモンは存在しません', 'error');
}
}
--省略
}
checkPokemonメソッドに注目しましょう。コントローラー内ではポケモンクラスを呼び出しています。もちろん、呼び出せばポケモンクラスにもメッセージが溜まっていくのですが、コントローラークラスとポケモンクラスでは溜まっていく場所が異なるため、溜まったメッセージは結合させる必要があります。
// Pokemonのインスタンスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
ポケモンクラスを呼び出した後、ポケモンクラスのメッセージを全てコントローラークラスのメッセージに格納します。ですが、getMessagesの返り値は配列のため、このままセットすれば階層が1段ずれてしまうため、setMessageのメソッドに対して一括登録用の仕様を追加します。
レスポンス用トレイト(Traits/ResponseTrait.php)
/**
* メッセージの格納
*
* @param string|array $msg
* @param string $param error|success default=null
* @return array
*/
public function setMessage($msg, $param=null)
{
if(is_array($msg)){
// 一括登録
$this->msgs = array_merge($this->msgs, $msg);
}else{
// 単発登録
$this->msgs[] = [$msg, $param];
}
}
もし第1引数の$msgが配列であれば、array_mergeを使って$msgプロパティの後ろに追加しています。こうしておけば階層がずれてしまうことなく、順番通りにメッセージを溜めていくことができます。
array_merge(PHP.net)
また、ポケモンのインスタンスはコントローラーと違い、リセットするまでは使い回しているため、アクションを起こす度にメッセージが蓄積していくことになります。そうならないためにも、メッセージの初期化用メソッドを作成しておきます。
レスポンス用トレイト(/Traits/ResponseTrait.php)
/**
* メッセージの初期化
*
* @return void
*/
public function resetMessage()
{
$this->msgs = [];
}
コントローラーを呼び出したタイミングで、引き継いだポケモンクラスのメッセージを初期化できれば良いので、コンストラクタに初期化の処理を記述しておきます。
/**
* @return void
*/
public function __construct($post, $session)
{
if(get_parent_class($session['pokemon'] ?? null) === 'Pokemon'){
// Pokemonのインスタンスに溜まったメッセージを初期化
$session['pokemon']->resetMessage();
}
if(isset($post['pokemon'])){
// ポケモンをゲットした
$this->checkPokemon($post['pokemon']);
}
if(isset($post['action'])){
// アクションを選択した
$this->pokemon = $session['pokemon'] ?? null; # 更新時エラー回避用にnullをセット
// アクションメソッドの実行
$this->action($post['action'], $post['params'][$post['action']] ?? null);
}
}
get_parent_classで、第2引数の$sessionから受け取ったポケモンのインスタンスの親がポケモンクラスかどうかを確認しています。もし引き継いでいた場合は、初期化して処理をスタートすることで、古いメッセージが溜まり続けることを回避します。
get_parent_class(PHP.net)
データの返却
メッセージの返却機能ができたので、次はデータの返却機能を作りましょう。こちらも同様に格納用プロパティとメソッド、取得用メソッドを作成します。
レスポンス用トレイト(Traits/ResponseTrait.php)
<?php
trait ResponseTrait
{
--メッセージ分を省略
/**
* レスポンステータの格納用
* @var array
*/
private $responses = [];
/**
* レスポンステータの取得
*
* @return array
*/
public function getResponses()
{
return $this->responses;
}
/**
* レスポンステータの格納
*
* @param mixed @response
* @return array
*/
public function setResponse($response, $key=null)
{
if(is_null($key)){
$this->responses[] = $response;
}else{
$this->responses[$key] = $response;
}
}
/**
* レスポンステータの初期化
*
* @return void
*/
public function resetResponse()
{
$this->responses = [];
}
}
メッセージもresponseにはかわりないので、もし一括で管理したい人はキーで振り分けるなどして判別してください。今回はresponseには返却したい配列などのデータを格納することが目的ですので、メッセージと分けています。
データのセット時には、キーを指定できるようにしています。場合によっては複数のデータを扱うケースもあるため、こうしておけばどこにどのデータが入っているかがわかりやすくなります。
それでは、詳細(getDetails)やステータス(getStats)で受け取った配列をデータとしてセットしていきましょう。
コントローラー(/Classes/Controller.php)
/**
* アクション
*
* @param string $action(method_name)
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
if($action === 'reset' || is_null($this->pokemon)){
// 初期化
$this->pokemon = null;
return;
}
// 呼び出せるメソッドか判別
if(is_callable([$this->pokemon, $action])){
if(in_array($action, ['setExp', 'setNickname'], true) && empty($param)){
$this->setMessage('セットする値を入力してください', 'error');
return;
}
// メソッド実行結果を$resultに格納
$result = $this->pokemon
->$action($param);
switch ($action) {
// 詳細
case 'getDetails':
$this->setResponse($result, '詳細');
break;
// ステータス
case 'getStats':
$this->setResponse($result, 'ステータス');
break;
// 経験値の取得
case 'setExp':
$this->pokemon = $result;
break;
}
// Pokemonクラスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}else{
$this->setMessage('このアクションは使用できません', 'error');
}
}
毎度お馴染みながら、わかりやすいようにキーを日本語で指定しています。このあたりが気になる方は、英数字を使うようにしてください。
ここでは記述しませんが、getDetailsで取得していた技の一覧もimplodeを無くして配列のまま返却するように変更しておきましょう。
出力エリアの作成
メッセージやデータをResponseTraitによって格納できるようになったので、操作用フォームの下に出力エリアを作成しましょう。
出力ファイル(/index.php)
<style>
pre{
background-color: black;
color: lime;
padding: 1rem;
}
.success{
color: limegreen;
}
.error{
color: red;
}
</style>
--省略
<h2>実行結果</h2>
<?php foreach($controller->getMessages() as list($msg, $class)): ?>
<p class="<?=$class?>"><?=$msg?></p>
<?php endforeach; ?>
<?php if(!empty($controller->getResponses())): ?>
<pre><?php var_export($controller->getResponses()); ?></pre>
<?php endif; ?>
今回一部CSSを使って整えています。このあたりは自由にカスタムしてください。
メッセージは配列で、メッセージとパラメーター(error、success、空)で構成されているので、foreachで回しながら出力していきます。パラメーターは今回クラス名として使い、それに合わせてCSSで色付けしています。受け取ったデータ(getResponses)はどういったデータが来るかによって出力の方法が異なるため、var_exportの結果をpreタグを使って表示させます。
それでは、出力結果を見てみましょう。
メッセージ、データ(レスポンス)それぞれが出力結果のエリアに表示されました。このようにそれぞれをデータとして返却するようにしておけば、フロント側で自由に表示させることができるので便利です。
最後に、今回新しく追加したResponseTraitと、大幅に変更したControllerとindexのコードを載せておきます。PokemonコードやGetとSetのトレイトについてはecho部分を修正するか、次回のコード配布をお待ち下さい。
レスポンス用トレイト(/Traits/ResponseTrait.php)
<?php
trait ResponseTrait
{
/**
* メッセージの格納用
* @var array
*/
private $msgs = [];
/**
* レスポンステータの格納用
* @var array
*/
private $responses = [];
/**
* メッセージの取得
*
* @return array
*/
public function getMessages()
{
return $this->msgs;
}
/**
* メッセージの格納
*
* @param string|array $msg
* @param string $param error|success default=null
* @return array
*/
public function setMessage($msg, $param=null)
{
if(is_array($msg)){
// 一括登録
$this->msgs = array_merge($this->msgs, $msg);
}else{
// 単発登録
$this->msgs[] = [$msg, $param];
}
}
/**
* メッセージの初期化
*
* @return void
*/
public function resetMessage()
{
$this->msgs = [];
}
/**
* レスポンステータの取得
*
* @return array
*/
public function getResponses()
{
return $this->responses;
}
/**
* レスポンステータの格納
*
* @param mixed @response
* @return array
*/
public function setResponse($response, $key=null)
{
if(is_null($key)){
$this->responses[] = $response;
}else{
$this->responses[$key] = $response;
}
}
/**
* レスポンステータの初期化
*
* @return void
*/
public function resetResponse()
{
$this->responses = [];
}
}
コントローラー(/Classes/Controller.php)
<?php
require_once(__DIR__.'/../Traits/ResponseTrait.php');
require_once(__DIR__.'/Pokemon/Pikachu.php');
require_once(__DIR__.'/Pokemon/Fushigidane.php');
require_once(__DIR__.'/Pokemon/Hitokage.php');
require_once(__DIR__.'/Pokemon/Zenigame.php');
// コントローラー
class Controller
{
use ResponseTrait;
/**
* ポケモン格納用
* @var object
*/
public $pokemon;
/**
* アクション一覧
* @var array
*/
private $action_list = [
'getStats' => 'ステータス',
'getDetails' => '詳細',
'setNickname' => 'ニックネームを付ける',
'setExp' => '経験値をセット',
'reset' => 'リセット',
];
/**
* ポケモン一覧
* @var array
*/
private $pokemon_list = [
'Pikachu' => 'ピカチュウ',
'Fushigidane' => 'フシギダネ',
'Hitokage' => 'ヒトカゲ',
'Zenigame' => 'ゼニガメ',
];
/**
* @return void
*/
public function __construct($post, $session)
{
if(get_parent_class($session['pokemon'] ?? null) === 'Pokemon'){
// Pokemonのインスタンスに溜まったメッセージとレスポンスデータを初期化
$session['pokemon']->resetMessage();
$session['pokemon']->resetResponse();
}
if(isset($post['pokemon'])){
// ポケモンをゲットした
$this->checkPokemon($post['pokemon']);
}
if(isset($post['action'])){
// アクションを選択した
$this->pokemon = $session['pokemon'] ?? null; # 更新時エラー回避用にnullをセット
// アクションメソッドの実行
$this->action($post['action'], $post['params'][$post['action']] ?? null);
}
}
/**
* ポケモンクラスの存在チェック
*
* @param string $pokemon(class_name)
* @var void
*/
private function checkPokemon($pokemon)
{
if(class_exists($pokemon)){
$this->pokemon = new $pokemon;
// Pokemonのインスタンスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}else{
$this->setMessage('選択されたポケモンは存在しません', 'error');
}
}
/**
* アクション
*
* @param string $action(method_name)
* @param mixed $param
* @return void
*/
private function action($action, $param)
{
if($action === 'reset' || is_null($this->pokemon)){
// 初期化
$this->pokemon = null;
return;
}
// 呼び出せるメソッドか判別
if(is_callable([$this->pokemon, $action])){
if(in_array($action, ['setExp', 'setNickname'], true) && empty($param)){
$this->setMessage('セットする値を入力してください', 'error');
return;
}
// メソッド実行結果を$resultに格納
$result = $this->pokemon
->$action($param);
switch ($action) {
// 詳細
case 'getDetails':
$this->setResponse($result, '詳細');
break;
// ステータス
case 'getStats':
$this->setResponse($result, 'ステータス');
break;
// 経験値の取得
case 'setExp':
$this->pokemon = $result;
break;
}
// Pokemonクラスに溜まったメッセージを取得
$this->setMessage($this->pokemon->getMessages());
}else{
$this->setMessage('このアクションは使用できません', 'error');
}
}
/**
* アクション一覧の取得
*
* @return array
*/
public function getActionList()
{
return $this->action_list;
}
/**
* ポケモン一覧の取得
*
* @return array
*/
public function getPokemonList()
{
return $this->pokemon_list;
}
}
出力ファイル(/index.php)
<?php
require_once(__DIR__.'/Classes/Controller.php');
session_start();
$controller = new Controller($_POST, $_SESSION);
$action_list = $controller->getActionList();
$pokemon_list = $controller->getPokemonList();
?>
<!DOCTYPE html>
<html lang="jp" dir="ltr">
<head>
<meta charset="utf-8">
<title>PHPポケモン</title>
<meta name="robots" content="noindex, nofollow" />
<style>
pre{
background-color: black;
color: lime;
padding: 1rem;
}
.success{
color: limegreen;
}
.error{
color: red;
}
</style>
</head>
<body>
<header>
<h1>PHPポケモン</h1>
<hr>
</header>
<main>
<?php if(isset($controller->pokemon)): ?>
<section>
<p>現在所有しているポケモン:<?=$controller->pokemon->getNickname()?>(<?=$controller->pokemon->getName()?>)</p>
<hr>
</section>
<?php endif; ?>
<section>
<h2>メソッドの実行</h2>
<form action="/" method="post">
<?php if(is_object($controller->pokemon ?? null)): ?>
<?php $_SESSION['pokemon'] = $controller->pokemon; # ポケモンのインスタンスをセッションに格納 ?>
<ol>
<?php foreach($action_list as $action => $title): ?>
<li>
<input type="radio" name="action" id="<?=$action?>" value="<?=$action?>" required>
<label for="<?=$action?>"><?=$title?></label>
<ul>
<?php switch($action): case 'setNickname': ?>
<li>
<input type="text" name="params[<?=$action?>]" size="30" placeholder="ニックネームを入力※最大5文字">
</li>
<?php break; ?>
<?php case 'setExp': ?>
<li>
<input type="number" min="1" name="params[<?=$action?>]" size="100" placeholder="経験値を入力">
</li>
<?php break; ?>
<?php endswitch; ?>
</ul>
</li>
<?php endforeach; ?>
</ol>
<?php else: ?>
<?php
// ポストとセッションをリセット
$_POST = [];
$_SESSION = [];
?>
<p>ポケモンを選択してください</p>
<select name="pokemon" required>
<option>--選択してください--</option>
<?php foreach($pokemon_list as $class => $pokemon): ?>
<option value="<?=$class?>"><?=$pokemon?></option>
<?php endforeach; ?>
</select>
<?php endif; ?>
<input type="submit" value="実行">
</form>
<hr>
</section>
<section>
<h2>実行結果</h2>
<?php foreach($controller->getMessages() as list($msg, $class)): ?>
<p class="<?=$class?>"><?=$msg?></p>
<?php endforeach; ?>
<?php if(!empty($controller->getResponses())): ?>
<pre><?php var_export($controller->getResponses()); ?></pre>
<?php endif; ?>
</section>
</main>
</body>
</html>
まとめ
いかがだったでしょうか。
今回のPHPポケモン「レスポンス機能編」ではメッセージやデータの返却機能を実装しました。
みなさんもオリジナル仕様にカスタムしながら、ぜひプログラミング学習を楽しんでくださいね。