今までは技やタイプを一括requireという荒業で対応していましたが、フシギダネ系列の技を実装した際に「こんなん全部読み込んでられるか!」と流石になったので、簡易ながらオートローダーを実装していきます。
そして、実装したらしたで色々と問題も浮かび上がってきたので、このあたりは回を進めて行きながら修正予定です。
それでは今回はPHPのオートローダー(autoloader)についてご紹介します。
一部解釈ミスがあったので、本記事の修正版を挙げています。本記事も参考にしながら本格的且つ簡易的なオートローダーは次記事をご参考ください
オートローダーとは
簡単に言えば、必要な時に必要なファイルを自動的に読み込む便利な機能のことです。
PHPであればspl_autoload_registerという関数使って、指定したファイルをrequireやincludeすることができます。
spl_autoload_register(PHP.net)
簡易オートローダーの作成
とは言っても、今回は全てのファイルをオートローダーを使って読み込むようなことはしません。あくまで使用するのは「ポケモン」「技」「タイプ」というデータベースの用にしているファイルのみです。
※現段階は上記3つですが、必要に応じて増やしていきます
通常オートローダーはクラスで作成されますが、インスタンス化するのも面倒なので今回はトレイトで作成します。
※もし都合が悪くなればクラスに変更予定です
オートローダー(/Traits/AutoLoaderTrait.php)
<?php
trait AutoLoaderTrait
{
/**
* オートローダー
*
* @param string $class_name
* @param string $folder
* @return void
*/
protected function autoLoader($class_name, $folder='')
{
if(!empty($folder)){
// フォルダ指定されていれば最後にスラッシュを付与
$folder = $folder.'/';
}
spl_autoload_register(function($class_name) use($folder){
// クラス名からファイルを検索
$path = __DIR__ . '/../Classes/'.$folder.$class_name.'.php';
if(file_exists($path)){
// 見つかった場合は読み込み実行
require $path;
}
});
}
}
それではautoLoaderのメソッドを見ていきましょう。
protected function autoLoader($class_name, $folder='')
{
if(!empty($folder)){
// フォルダ指定されていれば最後にスラッシュを付与
$folder = $folder.'/';
}
spl_autoload_register(function($class_name) use($folder){
// クラス名からファイルを検索
$path = __DIR__ . '/../Classes/'.$folder.$class_name.'.php';
if(file_exists($path)){
// 見つかった場合は読み込み実行
require $path;
}
});
}
引数の1つ目はクラス名です。渡されたクラス名は読み込む対象となるファイル名として使用されるため、必ず作成したクラス名とファイル名は一致させておきましょう。
第2引数では、フォルダを選べる仕様です。オートローダーでは、指定のクラスがどのパスに存在しているかを探し出さなければなりません。ですが、様々な階層を調べるのはそれだけでも大きな手間になります。今回はオートローダーを使ったファイルの読み込み、現状Classesフォルダ以下に限定されているため、ポケモンであればPokemon、技であればMoveのように選んで読み込みができるようにしています。
※現仕様では上位階層のファイルも読み込めてしまいますが、今回はスルーします。もし参考にシステムへ組み込もうと考えている人はご注意ください。
ではsql_autoload_registerについて見てみましょう。
spl_autoload_register(function($class_name) use($folder){
// クラス名からファイルを検索
$path = __DIR__ . '/../Classes/'.$folder.$class_name.'.php';
if(file_exists($path)){
// 見つかった場合は読み込み実行
require $path;
}
});
第1引数にはコールバック関数が渡されます。今回は無名関数を引数に記述しています。
コールバック関数の引数にはクラス名が入ります。今回は$folderも使用したいので、useを使ってコールバック関数内で使えるように変数を渡しています。
あとは、パス作成して存在をチェック、もし見つかればrequireするといった単純な仕様になっています。
インスタンス化トレイトの修正
それでは作成したオートローダーを使って、該当クラスのファイルを読み込んでいきましょう。まずは前回作成したインスタンス化トレイトの修正からです。
インスタンス化トレイト(/Traits/InstanceTrait.php)
<?php
trait InstanceTrait
{
/**
* インスタンス化関数
* @param string $class_name
* @return object
*/
protected function getInstance($class_name)
{
// 存在チェックをして読み込み
if(class_exists($class_name)){
return new $class_name();
}
}
/**
* ポケモンのインスタンス化関数
* @param string $class_name
* @return object
*/
protected function getPokemonInstance($class_name)
{
// ポケモンフォルダを指定してオートローダーを実行
$this->autoLoader($class_name, 'Pokemon');
return $this->getInstance($class_name);
}
/**
* 技のインスタンス化関数
* @param string $class_name
* @return object
*/
protected function getMoveInstance($class_name)
{
// ポケモンフォルダを指定してオートローダーを実行
$this->autoLoader($class_name, 'Move');
return $this->getInstance($class_name);
}
/**
* タイプのインスタンス化関数
* @param string $class_name
* @return object
*/
protected function getTypeInstance($class_name)
{
// ポケモンフォルダを指定してオートローダーを実行
$this->autoLoader($class_name, 'Type');
return $this->getInstance($class_name);
}
}
ポケモン用、技用、タイプ用の3つを追加しました。それぞれに用意したのは、array_mapのコールバック関数で使用している関係上、引数でフォルダを指定するとなれば少し複雑化してしまうため、今回は必要数用意するという方法を取りました。
それぞれ、呼び出されたタイプに合わせてオートローダーを実行してからインスタンスの取得をしています。
これで、数多くのrequire_onceをせずに欲しいクラスのファイルだけを取得することが可能です。
ポケモンクラスの修正
では、技やタイプを呼び出しているポケモン回りから修正を加えていきましょう。
ポケモンクラス(/Classes/Pokemon.php)
<?php
require_once(__DIR__.'/../Traits/AutoLoaderTrait.php');
require_once(__DIR__.'/../Traits/ResponseTrait.php');
require_once(__DIR__.'/../Traits/InstanceTrait.php');
require_once(__DIR__.'/../Traits/Pokemon/SetTrait.php');
require_once(__DIR__.'/../Traits/Pokemon/GetTrait.php');
// ポケモン
abstract class Pokemon
{
use AutoLoaderTrait;
use ResponseTrait;
use InstanceTrait;
use SetTrait;
use GetTrait;
--省略
/**
* 現在のレベルで覚えられる技があるか確認する処理
*
* @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_class = $this->level_move[$key][1];
// オートローダーを使った技クラスの読み込み
$this->autoLoader($move_class, 'Move');
// 覚えようとしている技(クラス)が存在かつ重複していないか
if(class_exists($move_class) && !in_array($move_class, $this->move, true)){
// 技クラスをセット
$this->setMove($move_class);
// インスタンス化
$move = new $move_class();
$this->setMessage($move->getName().'を覚えた!', 'success');
}
}
}
まずページの最初で、作成したオートローダーのトレイトを読み込みます。不要になった一括読み込み用のIncludeは削除しました。
次に、技の確認用メソッド(checkMove)です。今まではすべての技ファイルが既に読み込まれていましたが、今回は必要に応じて読み込めば良いので、class_existsの前に技指定でオートローダーを実行させました。
次に、タイプの読み込みもしなければならなのでGet関係のメソッドを見直しましょう。
Get格納トレイト(/Traits/Pokemon/GetTrait.php)
/**
* 覚えている技の一覧を取得する
* @return array
*/
public function getMove()
{
// array_mapで配列内の技クラスをインスタンス化
return array_map([$this, 'getMoveInstance'], $this->move);
}
--省略
/**
* タイプの取得
*
* @param string|array|object $return
* @var void
*/
public function getTypes($return='object')
{
/**
* タイプ名の取得用関数
* @param object
* @var string
*/
function getTypesName($obj){
return $obj->getName();
}
// array_mapで配列内のタイプクラスをインスタンス化
$types = array_map([$this, 'getTypeInstance'], $this->types);
switch ($return) {
case 'string':
// array_mapでタイプ名の配列にしたものを、implodeで文字列に変換
$types = implode(',', array_map('getTypesName', $types));
break;
case 'array':
// array_mapでタイプ名の配列にして返却
$types = array_map('getTypesName', $types);
break;
}
return $types;
}
技の一覧取得用メソッド(getMove)と、タイプ取得用メソッドにオートロードを追加しました。これでrequire_once地獄から抜け出すことが出来ました。
※現状だと一部class_existsによるオートロードが働いています。こちらは別回で修正予定です。
まとめ
いかがだったでしょうか。
今回のPHPポケモンでは「オートローダーの実装」をご紹介しました。
0からの構築をすると、回を進めて行くごとに粗が目立ったり「こうしておけば良かった」というのがどんどん浮かんできます。本来の開発では設計書や仕様書を作り進めていくことをオススメしますが、学習目的であればプログラミングの理解を深めていくためのきっかけにもなります。
現在学習中の方は、ぜひ参考にしてくださいね。