進化や技習得、HPバーや経験値バーの演出ができているのに、なぜ状態異常の演出はされていないの?
そう感じている方が少なからずいるはずです。
現段階では、状態異常になっても次の画面に移管しなければ表示されません。これは、PHP側で内部処理は行われているが、メッセージに合わせた動的な変更がされていないからです。
実は、この実装を妨げている原因が「レスポンストレイト」なのです。
今回のPHPポケモンは、よりよいシステム構成にするための「グローバル関数」と「ヘルパー関数」の実装方法についてご紹介します。
グローバル関数
グローバル変数という名称はよく聞くかも知れませんが、グローバル関数という言葉にはあまり聞き馴染みがない人が多いはずです。なぜなら、あくまでこれは仮名称で、どこからでも呼び出せる関数ということを表現しているだけだからです。
例えば、tranJpという日本語置き換えの関数を序盤に実装しましたよね。これは、ページの最初に読み込んでおくことで、どこからでも呼び出すことが可能です。
これを、レスポンス(メッセージ・モーダルを含む)関係で活用できるようにするのが、今回のグローバル関数の目的です。
グローバル変数の活用
作成するグローバル関数と合わせて使いたいのが「グローバル変数」です。セッションやポストなどもグローバル変数(スーパーグローバル変数)ですが、今回はこれとは別のグローバル変数を用意します。
例えば、以下の例を見てみましょう。
<?php
$global = 'グローバル変数';
function test(){
return $global;
}
var_dump(test());
上記のコードでは、test関数内で宣言している$globalは「見つかりません」となりエラーが吐き出されてしまいます。関数内では、あくまでその関数内で作成された変数や引数しか使うことができません。
ですが、これをグローバル宣言することで、testという関数内でも$globalを使うことができます。
<?php
$global = 'グローバル変数';
function test(){
global $global;
return $global;
}
var_dump(test());
関数内で、globalという宣言をして変数を記述します。こうすることで「この関数で使う$globalは、関数外で使っているglobalのことだよ」ということを知らせてやることができるのです。
レスポンスのクラス化と一元化
それでは、原因となっているレスポンストレイトについてです。
現在は、どのクラスでも活用できるようにトレイトとしてレスポンス関係を管理していましたが、こうすることでそれぞれのクラス内にレスポンスが含まれてしまうことになります。そのせいで、引き継ぎ処理などがややこしくなってしまい、混乱や機能制限を招く結果となっています。
なのでそれを解決するためにも、レスポンスはクラスとして用意することで、一元管理しましょう。
レスポンスクラス(/Classes/Response.php)
<?php
class Response
{
/**
* メッセージの格納用
* @var array
*/
private $messages = [];
/**
* レスポンスデータの格納用
* @var array
*/
private $responses = [];
/**
* モーダルの格納用
* @var array
*/
private $modals = [];
/**
* メッセージIDの発行
*
* @return string
*/
public function issueMsgId()
{
// IDを生成
$id = 'msg'.substr(bin2hex(random_bytes(16)), 0, 16);
// ユニークになるようにチェック
while(in_array($id, $_SESSION['__message_ids'] ?? [], true)){
$id = 'msg'.substr(bin2hex(random_bytes(16)), 0, 16);
}
$_SESSION['__message_ids'][] = $id;
return $id;
}
/**
* 全リセット
*
* @return void
*/
public function resetResponsesAll()
{
$this->messages = [];
$this->responses = [];
$this->modals = [];
}
/**==================================================================
* メッセージ関係の処理
==================================================================**/
/**
* メッセージの取得
*
* @return array
*/
public function getMessages()
{
return $this->messages;
}
/**
* メッセージの格納
*
* @param string|array $msg
* @param mixed $param
* @return array
*/
public function setMessage($msg, $param=null)
{
if(empty($msg)){
// 空の場合はスキップ
return;
}
if(is_array($msg)){
// 一括登録
$this->messages = array_merge($this->messages, $msg);
}else{
// 単発登録
$this->messages[] = [$msg, $param, ''];
}
}
/**
* アニメーション用の自動メッセージの格納
*
* @param mixed $param
* @return array
*/
public function setAutoMessage($param)
{
$this->messages[] = ['', $param, 'auto'];
}
/**
* 空メッセージの格納
*
* @param string $param
* @return array
*/
public function setEmptyMessage(string $param='')
{
$this->messages[] = ['', $param, ''];
}
/**
* メッセージの初期化
*
* @return void
*/
public function resetMessage()
{
$this->messages = [];
}
/**
* メッセージの最初のキーを取得
*
* @return void
*/
public function getMessageFirstKey()
{
return array_key_first($this->messages);
}
/**
* メッセージの最後のキーを取得
*
* @return void
*/
public function getMessageLastKey()
{
return array_key_last($this->messages);
}
/**==================================================================
* レスポンス関係の処理
==================================================================**/
/**
* 指定したレスポンステータの取得
*
* @param string|integer
* @return mixed
*/
public function getResponse($param)
{
if(isset($this->responses[$param])){
return $this->responses[$param];
}
}
/**
* レスポンステータの全取得
*
* @return array
*/
public function getResponses()
{
return $this->responses;
}
/**
* レスポンステータの格納
*
* @param mixed $response
* @param mixed $key
* @return array
*/
public function setResponse($response, $key=null)
{
if(empty($response)){
// 空の場合はスキップ
return;
}
if(is_null($key)){
// キー指定無し
if(is_array($response)){
// $responseが配列の場合の処理
$this->responses = array_merge($this->responses, $response);
}else{
// $responseが配列ではない場合の処理
$this->responses[] = $response;
}
}else{
// キー指定有り
$this->responses[$key] = $response;
}
}
/**
* 指定されたプロパティをレスポンスにセット(出力)
*
* @return void
*/
public function exportProperty(...$properties)
{
foreach($properties as $property){
$this->setResponse($this->$property, $property);
}
}
/**
* レスポンステータの初期化
*
* @return void
*/
public function resetResponse()
{
$this->responses = [];
}
/**==================================================================
* モーダル関係の処理
==================================================================**/
/**
* モーダルテータの取得
*
* @return array
*/
public function getModals()
{
return $this->modals;
}
/**
* モーダル用テータの格納
*
* @param array $param
* @param boolean $merge
* @return array
*/
public function setModal(array $param, bool $merge=false)
{
if(empty($param)){
// 空の場合はスキップ
return;
}
if($merge){
// 結合(引き継ぎ)
$this->modals = array_merge($this->modals, $param);
}else{
// モーダル格納
$this->modals[] = $param;
// モーダル起動時用のからメッセージをセット
$this->messages[] = ['', '', ''];
}
}
/**
* モーダル情報の初期化
*
* @return void
*/
public function resetModal()
{
$this->modals = [];
}
}
トレイトの内容を、そのままクラスに置き換えました。このクラスにメッセージを格納することで一元管理をしていきましょう。
ヘルパー関数
さて、前項で説明したグローバル関数と、新しく作成したレスポンスクラスを使って一元管理をするとしましたが、実際にどのように紐付けていくかがネックです。
例えばポケモンクラス内でレスポンスクラスにアクセスするためには、メソッド内でグローバル関数の宣言が必要になります。
/**
* 正式名称を取得する
* @return string
*/
public function getName()
{
global $global;
return $this->name;
}
これを全メソッドに行うとなれば、明らかに非効率的です。なので、グローバル宣言をしなくても呼び出せるグローバル関数を経由することで、グローバル変数に用意されたレスポンスのインスタンスにアクセスし、メソッドを呼び出す方法を取ります。
これを、フレームワークなどで名称活用されている「ヘルパー関数」としましょう。
全クラス内から共通インスタンスへのアクセス
それでは、全クラス内からアクセスできるレスポンスインスタンスを作成しましょう。
レスポンス関係のヘルパー関数を格納するファイル(ResponseGlobal.php)を作成し、これをページの最初で読み込んでおきます。
require_once(__DIR__.'/App/Globals/ResponseGlobal.php');
それでは、ヘルパー関数の作成方法を見ていきましょう。以下、メッセージを取得するためのヘルパー関数です。
<?php
// レスポンス用グローバル関数
$root_path = __DIR__.'/../..';
// クラス読み込み
require_once($root_path.'/Classes/Response.php');
$global_responses = new Response;
/**
* メッセージの取得
* @return array
*/
function getMessages()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
ポイントは、関数名とレスポンスのメソッド名を合わせているという点です。こうしておくことで、__FUNCTION__で現在の関数名が取得できるので、そのままグローバル変数のメソッド名として使用することができます。
もし他のインスタンスに対して用意する際に、メソッド名が重複する場合は、関数名を取得せず、メソッド指定で呼び出すようにしておきましょう。
次に、引数指定があるメッセージの格納用のヘルパー関数を見ていきましょう。
/**
* メッセージの格納
* @param string|array $msg
* @param mixed $param
* @return void
*/
function setMessage($msg, $param=null)
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name($msg, $param);
}
こちらは、引数をそのまま指定するだけでOKです。この要領で、レスポンス用のヘルパー関数を作成していきましょう。
レスポンス用ヘルパー関数
<?php
// レスポンス用グローバル関数
$root_path = __DIR__.'/../..';
// クラス読み込み
require_once($root_path.'/Classes/Response.php');
$global_responses = new Response;
/**
* メッセージIDの発行
* @return string
*/
function issueMsgId()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**
* 全リセット
* @return void
*/
function resetResponsesAll()
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name();
}
/**==================================================================
* メッセージ関係の処理
==================================================================**/
/**
* メッセージの取得
* @return array
*/
function getMessages()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**
* メッセージの格納
* @param string|array $msg
* @param mixed $param
* @return void
*/
function setMessage($msg, $param=null)
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name($msg, $param);
}
/**
* アニメーション用の自動メッセージの格納
*
* @param mixed $param
* @return void
*/
function setAutoMessage($param)
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name($param);
}
/**
* 空メッセージの格納
*
* @param string $param
* @return void
*/
function setEmptyMessage(string $param='')
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name($param);
}
/**
* メッセージの初期化
* @return void
*/
function resetMessage()
{
global $global_responses;
$name = __FUNCTION__;
$global_responses->$name();
}
/**
* メッセージの最初のキーを取得
*
* @return mixed
*/
function getMessageFirstKey()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**
* メッセージの最後のキーを取得
*
* @return mixed
*/
function getMessageLastKey()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**==================================================================
* レスポンス関係の処理
==================================================================**/
/**
* 指定したレスポンステータの取得
*
* @param string|integer
* @return mixed
*/
function getResponse($param)
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name($param);
}
/**
* レスポンステータの全取得
*
* @return array
*/
function getResponses()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**
* レスポンステータの格納
*
* @param mixed $response
* @param mixed $key
* @return array
*/
function setResponse($response, $key=null)
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name($response, $key);
}
/**
* レスポンステータの初期化
*
* @return void
*/
function resetResponse()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**==================================================================
* モーダル関係の処理
==================================================================**/
/**
* モーダルテータの取得
*
* @return array
*/
function getModals()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
/**
* モーダル用テータの格納
*
* @param array $param
* @param boolean $merge
* @return array
*/
function setModal(array $param, bool $merge=false)
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name($param, $merge);
}
/**
* モーダル情報の初期化
*
* @return void
*/
function resetModal()
{
global $global_responses;
$name = __FUNCTION__;
return $global_responses->$name();
}
一元化したことにより、セッションを活用したメッセージIDの重複チェックは不要かなと思っています。現在は支障が無いので、合間を見て修正をかけていきます。
また、現段階では可変長引数への対応ができないのと、プロパティへのアクセスができないため、exportPropertyメソッドは不要になりました。
あとは、全コードのメッセージやレスポンスの格納方法を変更するだけです。
例として、レベル技習得確認メソッド(checkeLevelMove)の記述を見てみましょう。
/**
* 現在のレベルで覚えられる技があるか確認する処理
*
* @return void
*/
protected function checkLevelMove()
{
// レベルアップして覚えられる技があれば習得する
$level_move_keys = array_keys(
array_column($this->level_move, 0),
$this->level
);
foreach($level_move_keys as $key){
$move_class = $this->level_move[$key][1];
// 覚えようとしている技(クラス)が存在かつ重複していないか
if(!in_array($move_class, array_column($this->move, 'class'), true)){
// インスタンス化
$move = new $move_class;
if(count($this->move) < 4){
/**
* 技が4つ未満なら即習得
*/
// 技クラスをセット
$this->setMove($move);
setMessage($move->getName().'を覚えた!');
}else{
/**
* 技選択用モーダルの返却
*/
// メッセージIDを生成
$msg_id = issueMsgId();
// レベルアップメッセージ
setMessage($this->getNickName().'は'.$move->getName().'を覚えたい');
setMessage('しかし技を4つ覚えるので精一杯だ');
setMessage($move->getName().'の代わりに他の技を忘れさせますか?', $msg_id);
// レスポンスデータをセット
setResponse([
'toggle' => 'modal',
'target' => '#'.$msg_id.'-modal',
'move' => $move_class
], $msg_id);
// モーダル用のレスポンスをセット
setModal([
'id' => $msg_id,
'modal' => 'selectmove',
'new_move' => $move
]);
// 諦めメッセージを事前に用意しておく
setMessage($this->getNickName().'は'.$move->getName().'を覚えるのを諦めた');
}
}
}
}
大分スマートになりました。また、今まで引き継ぎ処理としていたメッセージを取得してコントローラーのメッセージに格納、その後取得先のメッセージをリフレッシュするという処理をなくすことができました。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは内部の修正として、グローバル・ヘルパー関数を活用した「レスポンスの一元化」の方法をご紹介しました。
この変更を本番環境に反映させる際には、再度セーブデータの全リセットを行う予定です。ご了承ください。
また、この変更に合わせて状態異常の演出に取り欠かれるので、こちらは近日中に実装します。
現在プログラミング学習に取り組んでいる方、興味を持たれている方は、ぜひ参考にしてみてくださいね。