目次

Apricot アプリケーションクラス

y2sunlight 2020-04-25

Apricot に戻る

関連記事

さて、準備が出来たのでいよいよコアのクラス群を作って行きたいと思います。


Applicationクラスの仕様

最初にApplicationクラスを作成しますが、その前にApplicationクラスの責任を明確にしておきます。

Applicationクラスの責任

Applicationクラスは、以下の公開メソッドを持っています。詳細はソースコードを参照して下さい。

フロントコントローラ(index.php)から呼び出されるメソッド

公開メソッド機能
setup(array $app=[])アプリケーションのセットアップ
run(callable $routeDefinitionCallback)アクションの実行

ゲッターメソッド

公開メソッド機能
getInstance():ApplicationApplicationインスタンスの取得
getSetting($dot = null, $default=null)アプリケーション設定(app.php)の取得
getProjectDir():stringプロジェクトフォルダの取得
getConfigDir():stringconfig(一般設定)フォルダの取得
getAssetsDir():stringassets(資源)フォルダの取得
getVarDir():stringvar(データ)フォルダの取得
getPublicDirectory():stringpublic(公開)の取得
getRouteBase():stringURIルートベースの取得
getControllerName():stringカレントコントローラ名の取得
getActionName():stringカレントアクション名の取得


Applicationクラス(暫定版)

以下に暫定版のApplicationクラスを示します。ほとんどの機能は実装できていますが、アクションを実行するメソッド(executeAction)だけが未実装(スタブ)です。

/apricot/core

Application.php
<?php
namespace Core;
 
/**
 * Application Class
 */
class Application
{
    /**
     * Application Instance
     * var Application
     */
    private static $instance = null;
 
    /**
     * Application Setting
     * @var array
     */
    private $app = [];
 
    /*
     * Project Directories
     */
    private $projectDir;
    private $configDir;
    private $assetsDir;
    private $varDir;
 
    /*
     * Public Directory
     */
    private $publicDir;
 
    /*
     * Route Base Path
     */
    private $routeBase;
 
    /*
     * Controller Name
     */
    private $controllerName;
 
    /*
     *Action Name;
     */
    private $actionName;
 
    /**
     * Get Project dir
     * @return string
     */
    public function getProjectDir():string {return $this->projectDir;}
 
    /**
     * Get config dir
     * @return string
     */
    public function getConfigDir():string {return $this->configDir; }
 
    /**
     * Get assets dir
     * @return string
     */
    public function getAssetsDir():string {return $this->assetsDir; }
 
    /**
     * Get var dir
     * @return string
     */
    public function getVarDir():string {return $this->varDir; }
 
    /**
     * Get Public Directory
     * @return string
     */
    public function getPublicDirectory():string {return $this->publicDir; }
 
    /**
     * Get Route Base Path
     * @return string
     */
    public function getRouteBase():string {return $this->routeBase; }
 
    /**
     * Get controller Name
     * @return string
     */
    public function getControllerName():string {return $this->controllerName; }
 
    /**
     * Get Action Name
     * @return string
     */
    public function getActionName():string {return $this->actionName; }
 
    /**
     * Get Application instance.
     * @return \Core\Application
     */
    static public function getInstance():Application
    {
        if (!self::$instance)
        {
            throw new \RuntimeException('Application has not been set.');
        }
        return self::$instance;
    }
 
    /**
     * Create Application
     * @param string $projectDir
     * @param string $publicDir
     */
    function __construct(string $projectDir, string $publicDir)
    {
        if (!self::$instance)
        {
            // Set Project Directories
            $this->projectDir = $projectDir;
            $this->configDir = $projectDir.'/config';
            $this->assetsDir = $projectDir.'/assets';
            $this->varDir = $projectDir.'/var';
 
            // Set Public Directory
            $this->publicDir = $publicDir;
 
            // Set Route Base Path
            $routeBase = dirname($_SERVER['SCRIPT_NAME']);
            if (preg_match('/^[\\.\\\\]$/', $routeBase)) $routeBase='';
            $this->routeBase = $routeBase;
 
            // Set Dotenv
            \Dotenv\Dotenv::createImmutable($projectDir)->load();
 
            // Set timezone
            date_default_timezone_set(env('APP_TIMEZONE','UCT'));
 
            self::$instance = $this;
        }
    }
 
    /**
     * Get an application setting value
     * @param string|null $dot Dot-notation key
     * @param mixed $default
     * @return mixed
     */
    public function getSetting($dot = null, $default=null)
    {
        return array_get($this->app, $dot, $default);
    }
 
    /**
     * Checks if an application setting key is present
     * @param string $dot Dot-notation key
     * @return bool
     */
    public function hasSetting(string $dot):bool
    {
        return array_has($this->app, $dot);
    }
 
    /**
     * Setup Application
     * @param array $app Application Setting
     */
    public function setup(array $app=[])
    {
        $this->app = $app;
 
        // Application setup
        if (!empty($this->app) && array_key_exists('setup', $this->app))
        {
            foreach($this->app['setup'] as $setup)
            {
                $func = require_once $setup;
                if (!is_callable($func) || ($func()===false))
                {
                    throw new \RuntimeException("Application Setup Error: {$setup}");
                }
            }
        }
    }
 
    /**
     * Run Application
     * @param callable $routeDefinitionCallback
     */
    public function run(callable $routeDefinitionCallback)
    {
        // Create Dispatcher
        $dispatcher = \FastRoute\simpleDispatcher($routeDefinitionCallback);
 
        // Fetch method and URI from somewhere
        $httpMethod = $_SERVER['REQUEST_METHOD'];
        $uri = $_SERVER['REQUEST_URI'];
 
        // Strip query string (?foo=bar) and decode URI
        if (false !== $pos = strpos($uri, '?'))
        {
            $uri = substr($uri, 0, $pos);
        }
        $uri = rawurldecode($uri);
 
        $routeInfo = $dispatcher->dispatch($httpMethod, $uri);
        switch ($routeInfo[0])
        {
            case \FastRoute\Dispatcher::NOT_FOUND:
                abort(404, 'Page Not Found');
                break;
 
            case \FastRoute\Dispatcher::METHOD_NOT_ALLOWED:
                abort(405, 'Method Not Allowed');
                break;
 
            case \FastRoute\Dispatcher::FOUND:
 
                $handler = $routeInfo[1];
                $params = $routeInfo[2];
 
                if (is_callable($handler))
                {
                    // Case of callable
                    $handler($params);
                }
                elseif(strpos($handler,'@')!==false)
                {
                    // Case of Controller/Action
                    list($this->controllerName, $this->actionName) = explode('@', $handler);
 
                    // Ecexute action
                    $this->executeAction($this->controllerName, $this->actionName, $params);
                }
                else
                {
                    abort(500,'Action Not Found');
                }
                break;
        }
    }
 
    /**
     * Ecexute action
     * @param string $controllerName
     * @param string $actionName
     * @param array $params
     */
    private function executeAction(string $controllerName, string $actionName, array $params=[])
    {
        // TODO: Stub Version
        $controller = "\\App\\Controllers\\{$controllerName}";
        $instance = new $controller();
        call_user_func_array(array($instance, $actionName), $params);
    }
}


Applicationクラスのヘルパー関数

Applicationクラスのゲッターメソッドで良く使用されるものは boilerplates.php にヘルパー関数として追加しておきます。

/apricot/core/helpers

boilerplates.php
// ...
 
/**
 * Get application setting value
 * @param string|null $dot Dot-notation key
 * @param mixed $default
 * @return mixed
 */
function app($dot = null, $default=null)
{
    return Core\Application::getInstance()->getSetting($dot, $default);
}
 
/**
 * Checks if an application setting key is present
 * @param string $dot Dot-notation key
 * @return bool
 */
function app_has($dot = null)
{
    return Core\Application::getInstance()->hasSetting($dot);
}
 
/**
 * Get project directory
 * @param string|null $default
 * @return string project directory
 */
function project_dir($path = null):string
{
    return add_path(Core\Application::getInstance()->getProjectDir(), $path);
}
 
/**
 * Get config directory
 * @param string $path Sub path, if necessary
 * @return string config directory
 */
function config_dir($path = null):string
{
    return add_path(Core\Application::getInstance()->getConfigDir(), $path);
}
 
/**
 * Get assets directory
 * @param string $path Sub path, if necessary
 * @return string assets directory
 */
function assets_dir($path = null):string
{
    return add_path(Core\Application::getInstance()->getAssetsDir(), $path);
}
 
/**
 * Get var directory
 * @param string $path Sub path, if necessary
 * @return string var directory
 */
function var_dir($path = null):string
{
    return add_path(Core\Application::getInstance()->getVarDir(), $path);
}
 
/**
 * Get public directory
 * @param string $path Sub path, if necessary
 * @return string public directory
 */
function public_dir($path = null):string
{
    return add_path(Core\Application::getInstance()->getPublicDirectory(),$path);
}
 
/**
 * Get application URL
 * @param string $path Sub path, if necessary
 * @return string URL
 */
function url($path = null):string
{
    // TODO: APP_URLが無い時は、DomainとProtocolから絶対URLを作る
    $base = env('APP_URL', Core\Application::getInstance()->getRouteBase());
    return add_path($base,$path);
}
 
/**
 * Get file URL With version
 * @param string $filename
 * @return string URL
 */
function url_ver(string $filename)
{
    return url($filename).'?v='.env('APP_VERSION');
}
 
/**
 * Get routing path
 * @param string $path Sub path, if necessary
 * @return string routing path
 */
function route($path = null):string
{
    return add_path(Core\Application::getInstance()->getRouteBase(),$path);
}
 
/**
 * Get current controller name
 * @return string name
 */
function controllerName():string
{
    return Core\Application::getInstance()->getControllerName();
}
 
/**
 * Get current action name
 * @return string name
 */
function actionName():string
{
    return Core\Application::getInstance()->getActionName();
}


Applicationクラスの設定ファイル

Applicationクラスは2つの設定ファイル( app.php と routes.php)を持っています。これらはconfigフォルダ内に保存されています。

app.php

app.php はライブラリとミドルウェアに関する構成とセキュリティーの定義が含まれています。

/apricot/config

app.php
<?php
return
[
    'setup' =>[],
    'middleware' =>[],
    'auth' =>[],
    'csrf' =>[],
];

routes.php

routes.php にはルーティング(URIとアクションの紐づけ)の設定が含まれています。

/apricot/config

routes.php
<?php
//-------------------------------------------------------------------
// Route Definition Callback
//-------------------------------------------------------------------
return function (FastRoute\RouteCollector $r)
{
    $base = Core\Application::getInstance()->getRouteBase();
    $r->addGroup($base, function (FastRoute\RouteCollector $r) use($base)
    {
        // Home
        // TODO: Stub Version
        $r->get('/', function() use($base){
            header("Content-type: text/html; charset=utf-8");
            echo 'Hello, Apricot!';
        });
    });
};

ルーティング設定に関してはFastRouteを参照して下さい。


テスト実行

ここまでの実装で一度実行してみましょう。以下のように index.php を編集します。

/apricot/public

index.php
<?php
//-------------------------------------------------------------------
// オートローダーの登録
//-------------------------------------------------------------------
require dirname(__DIR__).'/vendor/autoload.php';
 
//-------------------------------------------------------------------
// パスの設定
//-------------------------------------------------------------------
$project_path = dirname(__DIR__);
$public_path = __DIR__;
 
//-------------------------------------------------------------------
// アプリケーション初期化
//-------------------------------------------------------------------
$application = new Core\Application($project_path, $public_path);
 
// TODO: セッション開始
 
// アプリケーションセットアップ
$application->setup(require_once config_dir('app.php'));
 
//-------------------------------------------------------------------
// アクションの実行
//-------------------------------------------------------------------
$application->run(require_once config_dir('routes.php'));

Applocationクラスの実装が出来たので、index.php に新しく2つの機能が追加されました。

ブラウザ上で以下のURLにアクセスしてみて下さい。

http://localhost/ws2019/apricot/public/

次のように表示されます:

Hello, Apricot!