プログラミング

連続の技習得編 オブジェクトをセッションへ格納 PHPポケモン57

PHP PHPポケモン ポケモン
連続の技習得編 オブジェクトをセッションへ格納 PHPポケモン57

セッション経由でのオブジェクト引き継ぎ

技習得の処理が整ってきたので、ここで連続技習得・連続レベルアップ時にも問題なく動作するように作り込んでいきます。ですが、現状のモーダルをレスポンスやメッセージと同様に、そのまま引き継いだとしてもエラーが発生します。

その原因がセッション経由でのオブジェクト渡しです。

PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化 PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化

  前回実装したオートローダーの使い方が盛大に間違っていたので、今回その間違いの説明をしながら、正しい実装方法をご紹介します。 申し訳ありません。(誠意)    オートロードについて(再)  必要なタイミングで必要なファイルをrequireまたはincludeするあれです。   前回spl_autoload_reg...

 

第17回で取り上げましたが、オブジェクトはセッションで次のページへ受け渡しすると、不完全なオブジェクト(__PHP_Incomplete_Class)になります。技習得時のレスポンスにオブジェクトは格納されていませんでしたが、モーダルでは新しい技のオブジェクトなどが含まれていますね。

 

現状のシステム構成のまま進めていくのであれば、解決策は2通りです。

  1. オブジェクトではなく配列形式で受け渡しをする
  2. オブジェクトをシリアライズする

 

まず、配列形式での受け渡しについてです。こちらは、現在のポケモン情報のようにexportなどのメソッドを用意して、画面移管後に新しくインスタンスを生成する方法です。

ですがポケモンのオブジェクトとは違い、レスポンスやモーダルは今後も増えていく可能性が高いです。その都度、配列形式を意識したデータの受け渡しをするとなれば大変ですね。

 

なので、今回は「オブジェクトのシリアライズ」で実装していきましょう。

 

シリアライズ(文字列化)

まず、シリアライズについて簡単に説明しておきます。PHPの関数を使って、オブジェクトを文字列化させることができます。jsonに似たような形式です。

シリアライズすれば、受け取り画面ではアンシリアライズの関数を実行することでオブジェクトを復元することが可能です。

 

今回使用するシリアライズ用の関数はserializeです。

 

一括でシリアライズさせるために、トレイトを用意してコントローラーとサービスで呼び出せるようにしておきましょう。

 

シリアライズトレイト(/App/Traits/SerializeTrait.php
<?php
 
trait SerializeTrait
{
    /**
    * オブジェクトのシリアライズ化
    * @param arg:mixed
    * @return mixed
    */
    public function serializeObject($arg)
    {
        if(is_array($arg)){
            // 配列の場合はループ
            $result = [];
            foreach($arg as $key => $val){
                $result[$key] = $this->serializeObject($val);
            }
            $arg = $result;
        }else if(is_object($arg)){
            // オブジェクトの場合はシリアライズ
            $arg = ['__serialize' => serialize($arg)];
        }
        return $arg;
    }
}

 

一括の場合は配列、単体の場合はオブジェクトで受け取ることになるので、引数はmixed想定で作成しています。

 

もし配列であれば、foreachを使って再度serializeObjectを呼び出しています。

受け取った値がオブジェクトであれば、serialize関数で文字列に変換します。ここで、そのまま返してしまうと復号化(アンシリアライズ)させる時に「どれを復号化すれば良いのか」がわからなくなってしまいます。個別で行なっても構いませんが、できれば一括で復号化させたいので、配列形式で返却しましょう。判別用として、キーを「__serialize」としています。

 

コントローラーとサービスの親クラスでトレイトを読み込み、格納時にシリアライズ化しましょう。

 

バトル画面ヘッド(/Resources/Partials/Layouts/Head/battle.php
// レスポンスはシリアライズ化
$_SESSION['__data']['before_reponses'] = $controller->serializeObject(
    $controller->getResponses()
);
$_SESSION['__data']['before_messages'] = $controller->getMessages();

 

現状、メッセージにはオブジェクトを格納することがありませんので、レスポンスのみシリアライズさせています。

 

アンシリアライズ(復号化)

次にアンシリアライズ(復号化)についてです。こちらもシリアライズトレイト内に、配列形式を想定してメソッドを作成しましょう。

 

シリアライズトレイト(/App/Traits/SerializeTrait.php
/**
* オブジェクトのアンシリアライズ化
* @param arg:mixed
* @return mixed
*/
public function unserializeObject($arg)
{
    // 配列の処理
    if(is_array($arg)){
        if(
            count($arg) === 1
            && isset($arg['__serialize'])
        ){
            // 配列の中身がシリアライズされたオブジェクトであればオブジェクト化
            $arg = unserialize($arg['__serialize']);
        }else{
            // 通常配列の場合はループ
            $result = [];
            foreach($arg as $key => $val){
                $result[$key] = $this->unserializeObject($val);
            }
            $arg = $result;
        }
    }
    return $arg;
}

 

配列の場合はループをさせていますが、もし配列内の個数が1つ__serializeというキーを持っていれば、unserialize関数を呼び出しています。

 

アンシリアライズ化のメソッドは、サービスでデータを受け取った際に使用します。

 

技習得用サービス(/App/Serivces/Battle/LearnMoveService.php
/**
* @param Pokemon:object
* @param before_response:array
* @param before_messages:array
* @param request:array
* @return void
*/
public function __construct($pokemon, $before_responses, $before_messages, $request)
{
    $this->pokemon = $pokemon;
    $this->before_responses = $this->unserializeObject($before_responses);
    $this->before_messages = $before_messages;
    $this->before_modals = $this->unserializeObject($before_modals);
    $this->request = $request;
}

 

これでオブジェクトの状態でデータを引き継ぐことができます。

※シリアライズする際は、データ改ざんの可能性を想定してハッシュ化させて照合したり、様々な対策をした上で行うことが推奨されています。また、現在はjsonを使ったデータの受け渡しが主流かつ安全と言われているため、本格的なシステム開発では十分注意して導入しましょう。

  

モーダルの引き継ぎ

それでは、モーダルの引き継ぎ処理を作成していきましょう。

 まずは、レスポンスやメッセージと同様に前回情報をセッションに格納し、技習得モーダルで受け取ります。

 

バトル画面ヘッド(/Resources/Partials/Layouts/Head/battle.php
//モーダルはシリアライズ化
$_SESSION['__data']['before_modals'] = $controller->serializeObject(
    $controller->getModalss()
);

 

バトルコントローラー(/App/Controller/Battle/BattleController.php
// branchメソッドswitch分岐
/******************************************
* 技の習得
*/
case 'learn_move':
// サービス実行
$service = new LearnMoveService(
    $this->pokemon,
    $_SESSION['__data']['before_reponses'],
    $_SESSION['__data']['before_messages'],
    $_SESSION['__data']['before_modals'],
    $this->request('param')
);
$service->execute();
// 描画するポケモン情報を置き換え
$this->before['friend'] = $service->getTmpPokemon();
break;

 

技習得用サービス(/App/Serivces/Battle/LearnMoveService.php
<?php
$root_path = __DIR__.'/../../..';
// 親クラス
require_once($root_path.'/App/Services/Service.php');
// トレイト
require_once($root_path.'/App/Traits/Service/Battle/ServiceBattleCheckTrait.php');
 
/**
* 技の習得処理
*/
class LearnMoveService extends Service
{
 
    use ServiceBattleCheckTrait;
 
    /**
    * @var Pokemon:object
    */
    protected $pokemon;
 
    /**
    * @var Pokemon:object
    */
    protected $tmp_pokemon;
 
    /**
    * @var array
    */
    protected $before_responses;
 
    /**
    * @var array
    */
    protected $request;
 
    /**
    * @param Pokemon:object
    * @param before_response:array
    * @param before_messages:array
    * @param before_modals:array
    * @param request:array
    * @return void
    */
    public function __construct($pokemon, $before_responses, $before_messages, $before_modals, $request)
    {
        $this->pokemon = $pokemon;
        $this->before_responses = $this->unserializeObject($before_responses);
        $this->before_messages = $before_messages;
        $this->before_modals = $this->unserializeObject($before_modals);
        $this->request = $request;
    }
 
    /**
    * @return void
    */
    public function execute()
    {
        // 描画用ポケモンオブジェクトの作成
        $this->tmp_pokemon = $this->createTmpPokemon();
        // 技の置き換え
        $this->replaceMove();
        // レスポンスの引き継ぎ
        $this->setResponse(
            $this->getUntreatedResponses($this->before_responses, $this->request['id'])
        );
        // メッセージの引き継ぎ
        $this->setMessage(
            $this->getUntreatedResponses($this->before_messages, $this->request['id'], 'message')
        );
        // モーダルの引き継ぎ
        $this->setModal(
            $this->getUntreatedResponses($this->before_modals, $this->request['id'], 'modal'), true
        );
    }
 
    /**
    * @return Pokomon:object
    */
    public function getTmpPokemon()
    {
        return $this->tmp_pokemon;
    }
 
    /**
    * 表示用のポケモンオブジェクトを生成
    * @return Pokemon:object
    */
    private function createTmpPokemon()
    {
        $pokemon = clone $this->pokemon;
        // クローンオブジェクトにレベルと残HPをセット
        $pokemon->setLevel($this->request['level']);
        $pokemon->setRemainingHp($this->request['hp']);
        $pokemon->setDefaultExp();
        return $pokemon;
    }
 
    /**
    * 技の置き換え
    * @return void
    */
    private function replaceMove()
    {
        // 忘れる技を取得
        $forget_move = $this->pokemon
        ->getMove($this->request['forget']);
        // 覚えさせる技を取得
        $new_move = new $this->before_responses[$this->request['id']]['move'];
        // 技を覚えさせる
        $this->pokemon
        ->setMove($new_move, $this->request['forget']);
        // メッセージの返却
        $this->setMessage('1 2の ……ポカン!');
        $this->setMessage($this->pokemon->getNickname().'は、'.$forget_move->getName().'の使い方をキレイに忘れた!そして......');
        $this->setMessage($this->pokemon->getNickname().'は新しく、'.$new_move->getName().'を覚えた!');
    }
 
    /**
    * 未処理レスポンス・メッセージ・モーダルの引き継ぎ処理
    * @param response:array
    * @param msg_id:string
    * @param param:string::response|message|modal
    * @return array
    */
    private function getUntreatedResponses(array $responses, string $msg_id, string $param='response')
    {
        $cnt = 1;
        switch ($param) {
            /********
            * メッセージの引き継ぎ
            */
            case 'message':
            $key = array_search(
                $msg_id,
                array_column($responses, 1), # メッセージIDの位置は1番目
                true
            );
            // 対象メッセージを含め3つ目までを削除
            $cnt = 3;
            break;
            /********
            * モーダルの引き継ぎ
            */
            case 'modal':
            $key = array_search(
                $msg_id,
                array_column($responses, 0), # メッセージIDの位置は0番目
                true
            );
            break;
            /********
            * レスポンスの引き継ぎ
            */
            default:
            // メッセージIDのレスポンスが入った位置を取得
            $key = array_search(
                $msg_id,
                array_keys($responses),
                true
            );
            break;
        }
        // 未処理だけを切り出して返却
        return array_splice($responses, $key + $cnt);
    }
 
}

 

現在覚えている技

ここから前回までに実装した機能の修正箇所です。まずは技モーダルの返却値についてです。

 

ポケモンクラスチェック用トレイト(/App/Traits/Class/Pokemon/ClassPokemonCheckTrait.php
// checkLevelMoveメソッド(旧:checkMove)
 
// モーダル用のレスポンスをセット
$this->setModal([
    'id' => $msg_id,
    'modal' => 'selectmove',
    'new_move' => $move
]);

 

前回は現在覚えている技に新しい技をマージして返却していましたが、それでは連続して技を覚える際に、新しく覚えた技が反映されていないオブジェクトが格納されたままになります。

これを回避するために、モーダルには現在の技+返却された技を出力させます。

 

忘れさせる技選択用モーダル(/Resources/Partials/Battle/Modals/selectmove.php
<div class="modal-body">
    <?php # 覚えている技 ?>
    <table class="table table-bordered table-sm table-hover">
        <thead class="thead-light">
            <tr>
                <th scope="col">覚えている技</th>
                <th scope="col">タイプ</th>
                <th scope="col">PP</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach($pokemon->getMove() as $key => $move): ?>
                <tr class="move-detail-link forget-selectmove <?php if($key === 4) echo 'active new-move'; ?>"
                    data-modal="#<?=$modal['id']?>-modal"
                    data-target="#<?=$modal['id']?>_<?=get_class($move['class'])?>-content"
                    data-name="<?=$move['class']->getName()?>"
                    data-num="<?=$key?>">
                    <th scope="row" class="w-50"><?=$move['class']->getName()?></th>
                    <td><?=$move['class']->getType()->getName()?></td>
                    <td><?=$move['remaining']?>/<?=$move['class']->getPp($move['correction'])?></td>
                </tr>
            <?php endforeach; ?>
            <?php # 覚えようとしている技 ?>
            <tr class="move-detail-link forget-selectmove active new-move"
                data-modal="#<?=$modal['id']?>-modal"
                data-target="#<?=$modal['id']?>_<?=get_class($modal['new_move'])?>-content"
                data-name="<?=$modal['new_move']->getName()?>"
                data-num="<?=$key?>">
                <th scope="row" class="w-50"><?=$modal['new_move']->getName()?></th>
                <td><?=$modal['new_move']->getType()->getName()?></td>
                <td><?=$modal['new_move']->getPp()?>/<?=$modal['new_move']->getPp()?></td>
            </tr>
        </tbody>
    </table>
    <?php # 技説明 ?>
    <div class="overflow-auto p-3 border" style="height:160px;">
        <?php foreach($pokemon->getMove() as $key => $move): ?>
            <div class="move-detail-content" id="<?=$modal['id']?>_<?=get_class($move['class'])?>-content">
                <h6><?=$move['class']->getName()?></h6>
                <hr>
                <p><?=$move['class']->getDescription()?></p>
            </div>
        <?php endforeach; ?>
        <?php # 覚えようとしている技 ?>
        <div class="move-detail-content active" id="<?=$modal['id']?>_<?=get_class($modal['new_move'])?>-content">
            <h6><?=$modal['new_move']->getName()?></h6>
            <hr>
            <p><?=$modal['new_move']->getDescription()?></p>
        </div>
    </div>
</div>

 

IDの重複回避

次にIDの重複回避です。モーダルは複数呼び出されることがあるため、IDを使用する場合は重複回避対策が必須となります。

ボタン等でIDを使用していたため、こちらをクラスに置き換えています。

<div class="modal-footer">
    <?php # 忘れさせるボタン ?>
    <button type="button"
    class="btn btn-danger btn-sm btn-forget-move"
    data-modal="#<?=$modal['id']?>-modal"
    data-msg_id="<?=$modal['id']?>"
    style="display:none;">
    <span class="move-name"></span>を忘れる</button>
    <?php # 諦めるボタン ?>
    <button type="button"
    class="btn btn-secondary btn-sm action-message-box btn-abandon-move"
    data-dismiss="modal">
    <?=$modal['new_move']->getName()?>を諦める</button>
</div>

 

また、jsで対象を指定する際に別のモーダルの値を取ってきてしまわないように、クリック発火するエレメントにはdata属性のmodalに現在のモーダルの判別値を格納しています。

 

これに合わせて、js側も修正しましょう。

  

バトル画面用js/Public/Assets/js/Battle/fight.js
/*----------------------------------------------------------
// 初期化する関数
----------------------------------------------------------*/
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var clickMoveInit = function(){
    $('.move-table-row').on('click', function(){
        // 技をフォームへセット
        $('#fight-form-param').val($(this).data('key'));
        // サブミット実行
        $('#fight-form').submit();
    });
}
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var selectForgetMoveInit = function(){
    $('.forget-selectmove').on('click', function(){
        var modal = $(this).data('modal');
        // 諦めるボタンの無効化切り替え
        if($(this).hasClass('new-move')){
            $(modal).find(".btn-abandon-move")
            .prop('disabled', false);
            $(modal).find('.btn-forget-move')
            .hide();
            return;
        }else{
            $(modal).find("#btn-abandon-move")
            .prop('disabled', true);
        }
        // 技名を取得
        var name = $(this).data('name');
        // ボタンに技名をセット
        $(modal).find('.btn-forget-move .move-name')
        .text(name);
        $(modal).find('.btn-forget-move')
        .show();
    });
}
 
/**
* 技テーブルクリック時の関数
* @function click
* @return void
**/
var submitForgetMoveInit = function(){
    $('.btn-forget-move').on('click', function(){
        var modal = $(this).data('modal');
        // 技をフォームへセット
        var data = {
            id: $(this).data('msg_id'),
            forget: $(modal).find('.forget-selectmove.active').data('num'),
            level: $('#level').text(),
            hp: $('#hpbar-friend').attr('aria-valuenow')
        };
        // フォームを用意
        $.each(data, function(key, val){
            // フォームの最初にパラメーターを追加
            $('#remote-form').append(
                '<input type="hidden" name="param[' + key + ']" value="' + val + '">'
            );
        })
        // サブミット実行
        $('#remote-form-action').val('learn_move');
        $('#remote-form').submit();
    });
}
 
/*----------------------------------------------------------
// 初期化
----------------------------------------------------------*/
jQuery(function($){
    clickMoveInit();
    selectForgetMoveInit();
    submitForgetMoveInit();
});

 

これで連続レベルアップと複数の技習得にも対応させることができました。実際に動きを見てみましょう。

 

 

1つのレベルに数個の技、レベルアップ時後にも技の習得を設定していましたが、問題なく動作してくれました。

これで技習得処理は完成です。

 

まとめ

いかがだったでしょうか。

今回のPHPポケモンではセッションを経由したオブジェクトの引き渡し方法をご紹介しました。

 

システム開発では方法を模索することがとても大切です。そして、複雑化しそうなときは1から考え直してみると、意外な突破口が見えてきます。

今回もオブジェクトをどう処理しようか、既にモーダルにセットした技を更新するにはどうすれば良いか、などを考えていましたが、考えれば考えるほど処理が複雑化しそうだったので、一度最初から考え直し、そもそも「すべての技を格納しておく必要がない」という答えに行き着きました

 

現在プログラミング学習に取り組んでいる方、ゲームづくりに興味がある方は、ぜひ参考にしてみてくださいね。

 

注目の記事

【Adobe Animate CC】モーショントゥイーンを使ったアニメーション作成
動画編集
Adobe,Animate,Illustrator,Photoshop,アニメーション
【Adobe Animate CC】モーショントゥイーンを使ったアニメーション作成

    今回はキャラクターがうちわを扇いでいるアニメーションのAdobe Animateを使った作成方法をご紹介します。 完成イメージは以下の通りです。   Photoshopなどを使ってもgifアニメーションの作成はできますが、Adobe Animateのモーショントゥイーンを使えば、変化するポイントだけを設定す...

捕獲処理実装編 PHPポケモン 80
プログラミング
PHP,PHPポケモン,ポケモン
捕獲処理実装編 PHPポケモン 80

捕獲処理の作成 前回モンスターボールのクラスを作成したので、今回は捕獲判定までの一連の処理を仕上げていきます。サービス自体は他のアイテムと一緒にするためItemServiceを呼び出し、その中で使用されたアイテムを判断して分岐を作ります。   バトル中のアイテムサービス(/App/Services/Battle/ItemService.ph...

スキル習得で挫折しないための3ステップ
雑記
デザイン,ブログ,プログラミング
スキル習得で挫折しないための3ステップ

  せっかく高い教材を買ったのに、気づいたら積んでしまっている・・・   そんな悩み、あなたには有りませんか? 今回は、そんな方のための勉強方法をテーマに説明していきます。 正しい勉強方法を身に着けていれば、どういったメリットがあるのでしょうか? 三日坊主になりにくい いざという...

【無料】早起きをして神戸へ行こう!「為になる雑談朝活」
イベント
三宮,朝活,神戸
【無料】早起きをして神戸へ行こう!「為になる雑談朝活」

  朝活を実施することになりましたので、その目的な概要をまとめました。 神戸三宮での開催を予定しておりますので、もしお近くにお住まいの方で日時が会いましたらご参加ください。 土日祝辺りで週1日程度の不定期開催を予定しています。学びにつながる、けど参加しやすい雑談形式ということを主としています...

知らなきゃ損!表記ゆれに強いSEO対策とは「オーガニック検索ユーザーを増やそう」
SEO対策
description,keyword,meta,title
知らなきゃ損!表記ゆれに強いSEO対策とは「オーガニック検索ユーザーを増やそう」

  「なかなかオーガニック検索の割合が増えない」 「どういったSEO対策をすればいいかわからない」   ブログやサイト運営者の方で、思うように成果が出ないと悩んでいる人は、まだまだSEO対策が十分ではない可能性があります。そして、SEO対策の中でも知らないと損する一つが「表記ゆれ」への対応です。 ...

システムエンジニア向けまとめ情報サイト IT News Checker
ネットワーク
システムエンジニア向けまとめ情報サイト IT News Checker

新年のスタートダッシュが遅れ気味に見えるかも知れませんが、年末からじっくりと作業を進めており、やっとある程度形になりました。 師走の如く作業に走り、気分転換に雪遊びもしつつ、成人式を終えた辺りでリリースしたのが「IT News Checker」です。   IT News Checkerとは 簡潔に説明すると、まと...

LANとWANについて【第2回 ド素人のためのネットワーク講座】
ネットワーク
IoT,LAN,WAN,Wi-Fi
LANとWANについて【第2回 ド素人のためのネットワーク講座】

  YQUALがお送りする、ド素人のためのネットワーク講座。 栄えある第2回は「LANとWANについて」です。   フロントエンジニアや現在プログラミングを学習中の方を中心に、ネットワークについての基礎的理解を深めていくための内容になっています。 ざっくりとした説明で物足りない、または細かく見ていった...

PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化
プログラミング
PHP,PHPポケモン,ポケモン
PHPポケモン「オートロード編(修正版)」17 おまけ:日本語化

  前回実装したオートローダーの使い方が盛大に間違っていたので、今回その間違いの説明をしながら、正しい実装方法をご紹介します。 申し訳ありません。(誠意)    オートロードについて(再)  必要なタイミングで必要なファイルをrequireまたはincludeするあれです。   前回spl_autoload_reg...

カテゴリ

SEO対策 イベント デザイン ネットワーク ビジネスモデル フリーランス プログラミング マーケティング ライティング 動画編集 雑記

タグ

5G Adobe AfterEffects AI ajax amazon Animate api artisan atom Automator AWS Bluetooth CSS CVR description EC-CUBE4 ECショップ ESLint Facebook feedly foreach function Google Google AdSense Honeycode htaccess HTML IEEE 802.11ax Illustrator Instagram IoT JavaScript jQuery jQuery UI keyword LAN Laravel Linux MacBook MAMP meta MLM MySQL NoCode note OS OSI参照モデル Paypal Photoshop PHP phpMyAdmin PHPポケモン PremierePro rss SEO SEO対策 Sequel Pro Skype SNS SSH Symfony TCP/IP title Toastr Trait Twig Twitter UCC V系 WAN WebSub Wi-Fi wiki Windows WordPress XAMPP xml Xserver YouTube YouTuber Zoom アーティスト アウトプット アクセス層 アニメーション アフィリエイト イーブイ インターネット インプット エンジニア オブジェクト指向 お金配り クリック単価 クリック数 コミュニケーション能力 コロナ コンサルティング サムネイル システムエンジニア スタートアップ スタイルシート スパム データベース ディープフェイク デザイナー デザイン テレワーク ナンパ ニュース ネットワークモデル ノマドワーク バナー ピカチュウ ビジネス フィード フリーランス ブロガー ブログ プログラマー プログラミング プログラミング学習 プログラミング教育 プロトコル ホームページ制作 ポケモン マークアップ マーケティング メール リモートワーク レンダリング 三井住友 三宮 仕事依頼 児童デイ 児童デイサービス 児童発達支援 公開鍵 初心者 助成金 勉強法 営業 広告 広告収入 必勝マニュアル 放課後等デイサービス 朝活 楽天 深層学習 無線LAN 独立 神戸 福祉 秘密鍵 翻訳 自己啓発 英語 見積書 計算機 読書 起業 迷惑メール 配列 銀の弾丸 集客 雑学力