目次

Apricot 各種基本コアクラス

y2sunlight 2020-04-25

Apricot に戻る

関連記事

次に、Applicationクラス以外の基本的なコアクラスを作ります。ここで作成する多くのクラスはシングルトンとして実装します。


設定管理

設定管理の責任を持つクラスは Configurationクラスです。基本的な機能だけをシンプルに実装しています。

設定ファイル

設定ファイルはconfig/setting フォルダに以下のネーミング規則で保存されます。

設定値へのアクセスには ドット表記 による「設定キー」によって行われます。設定キーの第1キーが設定ファイル名の{first_key}と一致します。以下にmonologでの例を示します。

/apricot/config/setting

monolog.setting.php
<?php
return
[
    'name' => env('LOG_NAME',env('APP_NAME')),
    'path' => env('LOG_PATH',var_dir('logs')),
    'level'=> env('LOG_LEVEL','debug'),
    'max_files'=> 0,
];

例えば「ログの名前」を参照する設定キーは、monolog.name になります。ドット表記による階層に制限はなく、monolog.phpで返す連想配列の階層が深ければ monolog.second_key.third_key.4th_key などのように深い階層も可能です。


Configurationクラス

Configurationクラスの実装コードを以下に示します。ユーザはこのクラスを直接利用するのではなく、次節に示す Configクラスを使用して下さい。

/apricot/core/Foundation

Configuration.php
<?php
namespace Core\Foundation;
 
/**
 * Improvised Configuration Class
 */
class Configuration
{
    /**
     * Configurations
     * @var array
     */
    protected $config = [];
 
    /**
     * Create Configuration
     */
    function __construct()
    {
        // Read Configuration
        foreach(glob(config_dir("setting/*.setting.php")) as $file)
        {
            $arr = explode('.', basename($file));
            if (is_file($file)) $this->read($file, $arr[0]);
        }
    }
 
    /**
     * Checks if a key is present
     * @param string $dot Dot-notation key
     * @return bool
     */
    public function has(string $dot):bool
    {
        return array_has($this->config, $dot);
    }
 
    /**
     * Get a value from the configuration
     * @param string $dot Dot-notation key
     * @param mixed $default
     * @return mixed
     */
    public function get(string $dot, $default = null)
    {
        return array_get($this->config, $dot, $default);
    }
 
    /**
     * Read configuration
     * @param string $config_file
     * @param string $top_key
     */
    private function read(string $config_file, string $top_key)
    {
        $config = require_once $config_file;
        if (is_array($config) && count($config))
        {
            $this->config[$top_key] = $config;
        }
    }
}


Configクラス

Configクラスは 上のConfigurationクラスをシングルトンにしたもので、以下のメソッドがあります。

使用法: Config::{メソッド}

メソッド機能
bool has(string $key)設定キーの存在確認
mixed get(string $key, $default = null)設定値の取得

/apricot/core

Config.php
<?php
namespace Core;
 
use Core\Foundation\Singleton;
use Core\Foundation\Configuration;
 
/**
 * Config Class - Configuration Wrapper
 *
 * @method static Configuration getInstance();
 * @method static bool has(string $key)
 * @method static mixed get(string $key, $default = null)
 */
class Config extends Singleton
{
    /**
     * Create Translation instance.
     * @return Configuration
     */
    protected static function createInstance()
    {
        return new Configuration();
    }
}

ヘルパー関数

Configクラスのget()メソッドは良く使用されるのでヘルパー関数に追加しておきます。

/apricot/core/helpers

boilerplates.php
/**
 * Get Configuration Variable
 * @param string $key
 * @param mixed $default
 * @return mixed configuration Variable
 */
function config($key, $default = null)
{
    return Core\Config::get($key, $default);
}


ロギング

ロギングは、monologをラップしたLogクラスが担当します。Logクラスはシングルトンとして実装し、以下のように使用します。機能的にはmonologと同じですが、PSR-3に従って使います。

使用法: Log::{メソッド}

メソッド機能
void emergency(string $message, array $context = [])emergencyレベルのログ
void alert(string $message, array $context = [])alertレベルのログ
void critical(string $message, array $context = [])criticalレベルのログ
void error(string $message, array $context = [])errorレベルのログ
void warning(string $message, array $context = [])warningレベルのログ
void notice(string $message, array $context = [])noticeレベルのログ
void info(string $message, array $context = [])infoレベルのログ
void debug(string $message, array $context = [])debugレベルのログ
void log($level, string $message, array $context = [])任意レベルのログ

Logクラスの実装は以下のようです。

/apricot/core

Log.php
<?php
namespace Core;
 
use Core\Foundation\Singleton;
use Monolog\Logger;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\RotatingFileHandler;
 
/**
 * Log Class - Monolog\Logger Wrapper
 *
 * @method static Logger getInstance();
 * @method static void emergency(string $message, array $context = []) Action must be taken immediately.
 * @method static void alert(string $message, array $context = []) Runtime errors that do not require immediate action but should typically be logged and monitored.
 * @method static void critical(string $message, array $context = []) Critical conditions.
 * @method static void error(string $message, array $context = []) Exceptional occurrences that are not errors.
 * @method static void warning(string $message, array $context = []) Normal but significant events.
 * @method static void notice(string $message, array $context = []) Interesting events.
 * @method static void info(string $message, array $context = []) User logs in, SQL logs.
 * @method static void debug(string $message, array $context = []) Logs with an arbitrary level.
 * @method static void log($level, string $message, array $context = []) Logs with an arbitrary level.
 */
class Log extends Singleton
{
    /**
     * Log Exception
     * @param string $level 'debug','info','notice','warning','error','critical','alert','emergency
     * @param \Exception $e
     */
    public static function exception(string $level, \Exception $e)
    {
        self::getInstance()->log($level, $e->getMessage(),[$e->getFile(), $e->getLine(), $e->getTraceAsString()]);
    }
 
    /**
     * Create Monolog Logger instance.
     * @return \Monolog\Logger
     */
    protected static function createInstance()
    {
        $log_name = config('monolog.name');
        $log_path = config('monolog.path');
        $log_level = config('monolog.level');
        $log_max_files = config('monolog.max_files',0);
 
        // ログハンドラーの作成
        // ログフォーマット設定: ログ内の改行を許可、付加情報が空の場合無視する
        $log_file_name = "{$log_path}/{$log_name}.log";
        $stream = new RotatingFileHandler($log_file_name, $log_max_files, $log_level);
        $stream->setFormatter(new LineFormatter(null, null, true, true));
 
        // ログチャネルの作成  ////////////////////////
        $instance = new Logger($log_name);
        $instance->pushHandler($stream);
        return $instance;
    }
}

ロギング設定は以下のようにシンプルなものです。


設定ファイル

/apricot/config/setting

monolog.setting.php
<?php
return
[
    'name' => env('LOG_NAME',env('APP_NAME')),
    'path' => env('LOG_PATH',var_dir('logs')),
    'level'=> env('LOG_LEVEL','debug'),
    'max_files'=> 0,
];


集約例外ハンドラー

apricotでは例外ハンドラーとしてWhoopsを使います。例外ハンドラーの動作はデバッグ用と本番用で分けて実装します。

Whoopsのカスタマイズ

デバッグ用の例外ハンドラーにはWhoopsで提供されている PrettyPageHandlerクラス を使いますが、このクラスにはログ出力の機能がないので継承してログ出力機能を実装した PrettyErrorHandlerWithLoggerクラス を作ります。

/apricot/core/Derivations

PrettyErrorHandlerWithLogger.php
<?php
namespace Core\Derivations;
 
/**
 * PrettyErrorHandler With Logger
 */
class PrettyErrorHandlerWithLogger extends \Whoops\Handler\PrettyPageHandler
{
    /**
     * {@inheritDoc}
     * @see \Whoops\Handler\PrettyPageHandler::handle()
     */
    public function handle()
    {
        // エラーログ出力
        $exception = parent::getException();
        \Core\Log::critical($exception->getMessage(),[$exception->getFile(),$exception->getLine(), $exception->getTraceAsString()]);
 
        parent::handle();
    }
}


設定ファイル

/apricot/config/setting

whoops.setting.php
<?php
return
[
    'debug' => env('APP_DEBUG',false),
    'controller' => \App\Exceptions\UncaughtExceptionHandler::class,
    'action' => 'render',
];


初期設定ファイル

集約例外ハンドラーの初期設定ファイルを以下に示します。

/apricot/config/setup

whoops.setup.php
<?php
//-------------------------------------------------------------------
// エラーハンドラー(Whoops)の初期設定
//-------------------------------------------------------------------
return function():bool
{
    $whoops = new \Whoops\Run;
    if(config('whoops.debug'))
    {
        //----------------------------
        // デバッグ用のエラーハンドラー
        //----------------------------
        $whoops->pushHandler(new \Core\Derivations\PrettyErrorHandlerWithLogger);
    }
    else
    {
        //----------------------------
        // 本番用のエラーハンドラー
        //----------------------------
        $whoops->pushHandler(function($exception, $inspector, $run)
        {
            // エラーログ出力
            \Core\Log::critical($exception->getMessage(),[$exception->getFile(),$exception->getLine(), $exception->getTraceAsString()]);
 
            // ユーザ向けエラー画面の表示
            // TODO: ここは例外のループを抑止しなかればならない
            $controller = config('whoops.controller',null);
            $action = config('whoops.action',null);
            if (isset($controller)&&isset($action))
            {
                $instance = new $controller();
                $response = call_user_func_array(array($instance, $action), [$exception]);
            }
            return \Whoops\Handler\Handler::QUIT;
        });
    }
 
    $whoops->register();
    return true; // Must return true on success
};


デバッグバー

デバッグ機能としてphp-debugbarを導入し、設定によってこの機能がON/OFFできるようにします。

公開用リソースの設置

php-debugbarはサーバ側の変数をクライアント画面で表示するので、JavaScriptやCSSなどのリソース設定が必要になり、これらのリソースは公開フォルダー( public )の下に設置する必要があります。以下にその手順を示します。

  1. public/resources の下に debugbar フォルダを作成します
  2. vender/maximebf/debugbar/src/DebugBar/Resources/ の下にある全てのファイルとフォルダを、上で作ったdebugbar フォルダの中にコピーします

結果的に以下のようになります:

/apricot/public/resources/debugbar

vendor/
widgets/
debugbar.css
debugbar.js
openhandler.css
openhandler.js
widgets.css
widgets.js


DebugBarのカスタマイズ

apricotではデバッグ出力用に、DebugBar提供の StandardDebugBar クラスを使用します。この StandardDebugBar を使うためには、次の2つのステップが必要になります:

  1. コレクター(Collector)を使ってデバッグ出力を行う
  2. デバッグ出力を画面にレンダリングする

apricotではこの2つのステップを使い易くする為に、StandardDebugBar クラスを以下のようにカスタマイズして使います。

/apricot/core/Derivations

StandardDebugBar.php
<?php
namespace Core\Derivations;
 
use DebugBar\DataCollector\DataCollectorInterface;
 
/**
 * StandardDebugBar - Inclusion of \DebugBar\StandardDebugBar Class
 */
Class StandardDebugBar
{
    /**
     * DebugBar
     * @var \DebugBar\StandardDebugBar
     */
    protected $debugBar;
 
    /**
     * JavascriptRenderer
     * @var \DebugBar\JavascriptRenderer
     */
    protected $renderer;
 
    /**
     * Create custom StandardDebugBar instance.
     */
    public function __construct()
    {
        // Create StandardDebugBar
        $this->debugBar = new \DebugBar\StandardDebugBar();
 
        // Get JavascriptRenderer
        $base_url = config('debugbar.renderer.base_url');
        $base_path = config('debugbar.renderer.base_path');
        $this->renderer = $this->debugBar->getJavascriptRenderer($base_url, $base_path);
        $this->renderer->setEnableJqueryNoConflict(false);
    }
 
    /**
     * Renders the html to include needed assets
     * @return string
     */
    public function renderHead():string
    {
        if (config('debugbar.debug'))
        {
            return $this->renderer->renderHead();
        }
        return '';
    }
 
    /**
     * Returns the code needed to display the debug bar
     * @return string
     */
    public function render():string
    {
        if (config('debugbar.debug'))
        {
            $initialize  = config('debugbar.renderer.initialize', true);
            $stacked_data = config('debugbar.renderer.stacked_data', true);
            return $this->renderer->render($initialize, $stacked_data);
        }
        return '';
    }
 
    /**
     * Get Data Collector
     * @param string $name
     * @return DataCollectorInterface
     */
    public function getCollector(string $name="messages"): DataCollectorInterface
    {
        return $this->debugBar->getCollector($name);
    }
}

カスタム化された StandardDebugBar クラスのコンストラクタでは、後述の設定ファイル( debugbar.setting.php )に従ってJavascriptのレンダラーを取得しています。

この StandardDebugBar クラスでは以下のメソッドが実装されています。

renderHead() と render() はHTMLテンプレート内で使います。


DebugBarクラス

DebugBarは、上でカスタマイズしたStandardDebugBarをラップしたクラスで、シングルトンとして実装します。DebugBar クラスはデバッグ出力のレンダリングで使用します。実際のデバッグ出力は次に説明する Debug クラスが担当します。

使用法: DebugBar::{メソッド}

メソッド機能
string renderHead()HTMLヘッダー用のレンダリング文字列を返す
mixed render()HTMLボディー用のレンダリング文字列を返す
\DataCollector\DataCollectorInterface
getCollector(string $name=“messages”)
デバッグ出力用のコレクターの取得

DebugBar クラスの実装は以下のようです。

/apricot/core

DebugBar.php
<?php
namespace Core;
 
use Core\Foundation\Singleton;
use Core\Derivations\StandardDebugBar;
 
/**
 * DebugBar Class - StandardDebugBar Wrapper
 *
 * @method static SimpleDebugBar getInstance()
 * @method static string renderHead()
 * @method static mixed render()
 * @method static \DataCollector\DataCollectorInterface getCollector(string $name="messages")
 */
class DebugBar extends Singleton
{
    /**
     * Create SimpleDebugBar instance.
     * @return \Core\Derivations\SimpleDebugBar;
     */
    protected static function createInstance()
    {
        return new StandardDebugBar();
    }
}


Debugクラス

実際にデバッグライトを行うクラスです。機能的にはDebugBarのコレクター( DataCollectorInterface )と同じですが、ロギングと同様にPSR-3に従って以下のように使います。以下の関数は基本的にvar_dump()と同じように変数の内容をダンプします (これらの関数の違いは単に出力レベルが付いているだけです)。Debug::debug($this) とすれば自分のメンバ変数が全てダンプされます。

使用法: Debug::{メソッド}

メソッド機能
void emergency(string $message, array $context = [])emergencyレベル
void alert(string $message, array $context = [])alertレベル
void critical(string $message, array $context = [])criticalレベル
void error(string $message, array $context = [])errorレベル
void warning(string $message, array $context = [])warningレベル
void notice(string $message, array $context = [])noticeレベル
void info(string $message, array $context = [])infoレベル
void debug(string $message, array $context = [])debugレベル
void log($level, string $message, array $context = [])任意レベル

Debugクラスの実装は以下のようです。

/apricot/core

Debug.php
<?php
namespace Core;
 
use Core\Foundation\CallStatic;
 
/**
 * Debug Class - LoggerInterface Wrapper
 *
 * @method static \Psr\Log\LoggerInterface getInstance()
 * @method static void debug($message, array $context = array())
 * @method static void info($message, array $context = array())
 * @method static void notice($message, array $context = array())
 * @method static void warning($message, array $context = array())
 * @method static void error($message, array $context = array())
 * @method static void critical($message, array $context = array())
 * @method static void alert($message, array $context = array())
 * @method static void emergency($message, array $context = array())
 * @method static void log($level, $message, array $context = array())
 */
class Debug extends CallStatic
{
    /**
     * Create Debug instance.
     * @return \Psr\Log\LoggerInterface
     */
    public static function getInstance()
    {
        // DebugBarの作成
        return \Core\DebugBar::getCollector('messages');
    }
}


設定ファイル

/apricot/config/setting

debugbar.setting.php
<?php
return
[
    'debug' => env('APP_DEBUG',false),
    'renderer' => [
        'base_url' => url('resources/debugbar'),
        'base_path' => public_dir('resources/debugbar'),
        'initialize' => true,
        'stacked_data' => true,
    ],
];


HTMLテンプレート

HTMLテンプレートは、BladeOneをラップしたViewクラスが担当します。Viewクラスはシングルトンとして実装し、以下のように使用します。BladeOneと同じメソッド使用できますが、apricotで使用するのはrun()メソッドだけです。

使用法: View::{メソッド}

メソッド機能
string run(string $view, array $variables = [])テンプレートエンジンの実行

Viewクラスの実装は以下のようです。

/apricot/core

View.php
<?php
namespace Core;
 
use Core\Foundation\Singleton;
use eftec\bladeone\BladeOne;
 
/**
 * View Class - BladeOne Wrapper
 *
 * @method static BladeOne getInstance();
 * @method static string run(string $view, array $variables = []) run the blade engine. It returns the result of the code.
 * @method static void setAuth($user = '', $role = null, $permission = []) Authentication. Sets with a user,role and permission
 * @method static void share($varname, $value) Adds a global variable
 * @method static BladeOne setOptimize($bool = false) If true then it optimizes the result (it removes tab and extra spaces).
 * @method static BladeOne setIsCompiled($bool = false) If false then the file is not compiled and it is executed directly from the memory. By default the value is true. It also sets the mode to MODE_SLOW.
 * @method static void setMode(int $mode) Set the compile mode
 * @method static void setFileExtension(string $fileExtension) Set the file extension for the template files. It must includes the leading dot e.g. .blade.php
 * @method static string getFileExtension() Get the file extension for template files.
 * @method static void setCompiledExtension(string $fileExtension) Set the file extension for the compiled files. Including the leading dot for the extension is required, e.g. .bladec
 * @method static string getCompiledExtension() Get the file extension for template files.
 * @method static string runString(string $string, array $data = []) run the blade engine. It returns the result of the code.
 * @method static void directive(string $name, callable $handler) Register a handler for custom directives.
 * @method static void directiveRT(string $name, callable $handler) Register a handler for custom directives for run at runtime
 * @method static void setErrorFunction(callable $fn) It sets the callback function for errors. It is used by @error
 * @method static void setCanFunction(callable $fn) It sets the callback function for authentication. It is used by @can and @cannot
 * @method static void setAnyFunction(callable $fn) It sets the callback function for authentication. It is used by @canany
 */
class View extends Singleton
{
    /**
     * Create Blade instance.
     * @return \eftec\bladeone\BladeOne
     */
    protected static function createInstance()
    {
        $templatePath = config('bladeone.template_path');
        $compiledPath = config('bladeone.compile_path');
        $mode = config('bladeone.mode');
        return new BladeOne($templatePath, $compiledPath, $mode);
    }
}

Viewクラスはテンプレートファイルのパス、コンパイル後のHTMLファイルのパス及び実行モードをBladeOneのコンストラクタに渡しているだけです。それらの値は、設定ファイル(bladeone.setting.php)から取得します。


設定ファイル

/apricot/config/setting

bladeone.setting.php
<?php
return
[
    'template_path' => env('VIEW_TEMPLATE',assets_dir('views')),
    'compile_path' => env('VIEW_CACHE',var_dir('cache/views')),
    'mode' => \eftec\bladeone\BladeOne::MODE_AUTO,
];


初期設定ファイル

Viewクラスには以下の初期設定ファイルが存在します。

/apricot/config/setup

bladeone.setup.php
<?php
//-------------------------------------------------------------------
// View template (BladeOne)の初期設定
//-------------------------------------------------------------------
return function():bool
{
    // @now directive
    \Core\View::directive('now', function()
    {
        return "<?php echo date('Y-m-d H:i'); ?>";
    });
    return true; // Must return true on success
};

ここでは、HTMLテンプレートで使用するカスタムディレクティブを追加します。上のコードは、現在時刻を表示する @now ディレクティブの追加を行っています。CSRFトークンを出力する @csrf ディレクティブなどもここで実装する予定です。


トランスレーション

トランスレーションは Translationクラスに実装されており、このクラスも基本的な機能だけをシンプルに作成しています。

言語ファイル

トランスレーションで使用する言語ファイルは assets/lang フォルダに言語毎に保存されます。日本語の場合は言語コードが ja なので assets/lang/ja/ に保存されます。言語ファイルのネーミング規則は以下の通りです。

このファイルには各言語でのテキストが連想配列によって {キー}=>{テキスト} の形式で格納されています。テキストの取得には ドット表記 を使用します。キーの最初の部分は設定ファイル名の{first_key}と一致します。以下に例を示します。

/apricot/assets/lang/ja

messages.php
<?php
return [
    'app'=>[
        'title'=>env('APP_NAME'),
        'menu'=>[
            'menu1'=>'Menu1',
            'menu2'=>'Menu2',
            'menu3'=>'Menu3',
            'logout'=>'Logout',
            'about_me'=>'About Me',
        ],
    ],
];

例えば「アプリのタイトル」を参照するキーは、messages.app.title に、Usersメニューを参照するには messages.app.menu.users になります。


Translationクラス

Translationクラスの実装コードを以下に示します。ユーザはこのクラスを直接利用するのではなく、次節に示す Langクラスを使用して下さい。

/apricot/core/Foundation

Translation.php
<?php
namespace Core\Foundation;
 
/**
 * Improvised Translation Class
 */
class Translation
{
    /*
     * Messages
     */
    private $messages = [];
 
    /*
     * Create Translation
     * @param string $lang
     */
    public function __construct(string $lang='ja')
    {
        // Read Messages
        foreach(glob(assets_dir("lang/{$lang}/*.php")) as $file)
        {
            $arr = explode('.', basename($file));
            if (is_file($file)) $this->read($file, $arr[0]);
        }
    }
 
    /**
     * Checks if a key is present
     * @param string $key
     * @return bool
     */
    public function has(string $key):bool
    {
        return array_key_exists($key, $this->messages);
    }
 
    /**
     * Get a value from the Messages.
     * @param string $key
     * @param string $params
     * @return string
     */
    public function get(string $key, array $params = []):string
    {
        if ($this->has($key))
        {
            $message = $this->messages[$key];
            if (!empty($params))
            {
                $message = str_replace(array_keys($params), array_values($params), $message);
            }
        }
        else
        {
            $message = $key;
        }
        return $message;
    }
 
    /**
     * Read Messages
     * @param string $lang_file
     * @param string $top_key
     */
    private function read(string $lang_file, string $top_key)
    {
        $messages = require_once $lang_file;
        if (is_array($messages) && count($messages))
        {
            $dot_arr = array_dot($messages, $top_key.'.');
            $this->messages = array_merge($this->messages, $dot_arr);
        }
    }
}


Langクラス

Langクラスは 上のTranslationクラスをシングルトンにしたもので、以下のメソッドがあります。

使用法: Lang::{メソッド}

メソッド機能
bool has(string $key)キーの存在確認
string get(string $key, array $params = [])言語テキストの取得

/apricot/core

Lang.php
<?php
namespace Core;
 
use Core\Foundation\Singleton;
use Core\Foundation\Translation;
 
/**
 * Lang Class - Translation Wrapper
 *
 * @method static Translation getInstance();
 * @method static bool has(string $key)
 * @method static string get(string $key, array $params = [])
 */
class Lang extends Singleton
{
    /**
     * Create Translation instance.
     * @return Translation
     */
    protected static function createInstance()
    {
        // cf.) $_SERVER['HTTP_ACCEPT_LANGUAGE'];
        $lang = env('APP_LANG','ja');
        return new Translation($lang);
    }
}
上の実装では環境変数から言語コードを取得していますが、国際対応として実装する場合は、$_SERVER['HTTP_ACCEPT_LANGUAGE'] から言語コードを取得した方が良いです。

ヘルパー関数

Langクラスのget()メソッドは良く使用されるのでヘルパー関数に追加しておきます。

/apricot/core/helpers

boilerplates.php
/**
 * Get Translated Message
 * @param string $key
 * @param string $params
 * @return string translated Message
 */
function __($key, $params = [])
{
    return Core\Lang::get($key, $params);
}
この関数名は __ です。2つ並んだアンダースコアはPythonプログラマーの間では dunders (double underscoreの意) と呼ばれ特別なクラス内メンバに付加されますが、ここではそのような意味はなくトランスレータを表す関数名としてLaravelに準じました。


エラーバッグ

エラーバッグ(ErrorBagクラス)は、入力エラーなどの業務的なエラー(例外ではないエラー)を管理する為のクラスです。

ErrorBagクラス

エラーバッグには名前を付けることができます。エラーは連想配列で保存されバッグ内の各エラーにはキーが付いています。ErrorBagクラスには以下のメソッドがあります。

メソッド機能
__construct($errors=null, string $name=self::DEFAULT_NAME)エラーバッグの生成
count(string $name=null):intエラー数の取得
has(string $key, string $name=self::DEFAULT_NAME):boolキーによるエラーの存在確認
get(string $key, string $name=self::DEFAULT_NAME)キーによるエラーの取得
all(string $name=null):array全てのエラーの取得
put($errors)エラー配列の設定
エラーバッグはIteratorAggregateインターフェースを実装してるのでforeach()などのIteratorを使用した構文が使用できます。但し、Countable インターフェイス は実装していないので、count関数ではなくErrorBag@countメソッドを使用して下さい。

/apricot/core/Foundation

ErrorBag.php
<?php
namespace Core\Foundation;
 
/**
 * Error Bag Class
 */
class ErrorBag implements \IteratorAggregate
{
    public const DEFAULT_NAME = 'error';
 
    /**
     * Bag name
     * @var string
     */
    private $name;
 
    /**
     * Errors
     * @var array
     */
    private $errors = [];
 
    /**
     * Create Error bag
     * @param array $errors Associative array
     * @param string $name Bag name
     */
    public function __construct($errors=null, string $name=self::DEFAULT_NAME)
    {
        $this->name = $name;
        if (isset($errors))
        {
            $this->put($errors);
        }
    }
 
    /**
     * Count errors
     * @param string $name Bag name
     * @return int
     */
    public function count(string $name=null):int
    {
        if (!isset($name) || ($this->name==$name))
        {
            return count($this->errors);
        }
        else
        {
            return 0;
        }
    }
 
    /**
     * Checks if a key is present
     * @param string $key Error key
     * @param string $name Bag name
     * @return boolean
     */
    public function has(string $key, string $name=self::DEFAULT_NAME):bool
    {
        if ($this->name==$name)
        {
            return array_key_exists($key, $this->errors);
        }
        return false;
    }
 
    /**
     * Get error a bag
     * @param string $key Error key
     * @param string $name Bag name
     * @return mixed return null if a key is not present
     */
    public function get(string $key, string $name=self::DEFAULT_NAME)
    {
        $result = null;
        if ($this->name==$name)
        {
            if ($this->has($key, $name))
            {
                return $this->errors[$key];
            }
        }
        return $result;
    }
 
    /**
     * Get all errors
     * @param string $name Bag name
     * @return array
     */
    public function all(string $name=null):array
    {
        if (!isset($name) || ($this->name==$name))
        {
            return $this->errors;
        }
        else
        {
            return [];
        }
    }
 
    /**
     * Put errors
     * @param array $error Associative array
     */
    public function put($errors)
    {
        $arr = is_array($errors) ? $errors : (array)$errors;
        $this->errors = $arr;
    }
 
    /**
     * IteratorAggregate Interface
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->errors);
    }
}