目次

Apricot コア作成の準備

y2sunlight 2020-04-25

Apricot に戻る

関連記事

まずは、apricotのコアを作る為に以下を準備します。


環境設定

アプリケーション全体の環境設定は phpdotenv で行います。phpdotenvで使用する環境ファイル( .env ) はプロジェクトフォルダ直下に設置します。

/apricot

.env
# Application
APP_NAME=Apricot
APP_VERSION=0.9
#APP_SECRET=Please set a random 32 characters
APP_SECRET=0123456789ABCEDF0123456789ABCEDF
APP_DEBUG=true
APP_TIMEZONE=Asia/Tokyo
APP_LANG=ja
 
# Loging
LOG_NAME ="apricot"
LOG_LEVEL = "debug"
環境変数設定内容必須
APP_NAMEアプリケーション名(半角英数字)string
APP_VERSIONバージョンstring
APP_SECRETシークレット文字列
安全の為にランダムな32文字を設定して下さい
string
APP_DEBUGデバッグモードbool
APP_TIMEZONタイムゾーンstring
LOG_NAMEログ名 (省略時はAPP_NAMEと同じ)string
LOG_LEVELログレベル (省略時はdebug)string


フォルダの作成

coreフォルダ

以下に示すようにプロジェクトフォルダ下に、コア用のフォルダ core を作成し、その下に4つのフォルダ(Derivations, Exceptions, Foundation, helpers)を作成します。

apricot [プロジェクト]
 |
 ├── core [Apricotのコア]
 |    |
 |    ├── Derivations [ライブラリの派生クラス]
 |    ├── Exceptions  [例外]
 |    ├── Foundation  [基盤]
 |    └── helpers     [ヘルパー関数]

configフォルダ

同様に、アプリケーション設定用のフォルダ config を作成し、その下に2つのフォルダ(setting, setup)を作成します。

apricot [プロジェクト]
 |
 ├── config [設定ファイル]
 |    |
 |    ├── setting [機能別の一般設定]
 |    └── setup   [機能別の起動設定]

assetsフォルダ

同様に、リソース用のフォルダ assets を作成し、その下に3つのフォルダ(lang, sql, views)を作成します。

apricot [プロジェクト]
 |
 ├── assets [リソース]
 |    |
 |    |── lang  [言語別の出力テキスト]
 |    |── sql   [SQLファイル]
 |    └── views [HTMLテンプレート]

var フォルダ

同様に、フォルダ var を作成し、その下に3つのフォルダ(cache, db, logs)を作成します。

apricot [プロジェクト]
 |
 ├── var
 |    |
 |    ├── cache [キャッシュ]
 |    ├── db    [DBファイル(sqlite)]
 |    └── logs  [ログ]


例外クラス

Exceptionを継承した2つの例外クラスを core\Exceptions に作成します。

/apricot/core/Exceptions

HttpException.php
<?php
namespace Core\Exceptions;
 
/**
 * Http Exception
 */
class HttpException extends \Exception
{
    /**
     * Http Status Code
     * @var int
     */
    private $statusCode;
 
    /**
     * Create HttpException
     * @param int $statusCode
     * @param string $message
     * @param int $code
     * @param \Exception $previous
     */
    public function __construct(int $statusCode, string $message = null, int $code = 0, \Exception $previous = null)
    {
        $this->statusCode = $statusCode;
        parent::__construct(isset($message) ? $message : "Http Status : {$statusCode}", $code, $previous);
    }
 
    /**
     * Get Http Status Code
     * @return int
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }
}
TokenMismatchException.php
<?php
namespace Core\Exceptions;
 
/**
 * Token Mismatch Exception
 */
class TokenMismatchException extends \Exception
{
    /**
     * Create TokenMismatchException
     * @param string $message
     * @param int $code
     * @param \Exception $previous
     */
    public function __construct(string $message = 'Token Mismatch Exception', int $code = 0, \Exception $previous = null)
    {
        parent::__construct($message, $code, $previous);
    }
}


ヘルパー

グローバル関数を保存するためのPHPファイルを core\helpers に作成します。目的別に2種類のPHPファイルがあります。


boilerplates.php

boilerplates.php へは関数を逐次追加します。現段階での内容は以下の通りです。

関数機能
env($key, $default = null)環境変数(.envファイル設定値)の取得
abort(int $code, string $message=null)HTTP例外(400,500番台)の発生
boilerplates.php
<?php
/**
 * Get Environment Variable
 * @param string $key
 * @param mixed $default
 * @return mixed environment Variable
 */
function env($key, $default = null)
{
    $value = getenv($key);
    if ($value === false)
    {
        return $default;
    }
 
    switch (strtolower($value))
    {
        case 'true': return true;
        case 'false':return false;
        case 'empty':return '';
        case 'null' :return null;
    }
    return $value;
}
 
/**
 * Abort
 * @param int $code
 * @param string $message
 * @throws \Core\Exceptions\HttpException
 */
function abort(int $code, string $message=null)
{
    throw new Core\Exceptions\HttpException($code, $message);
}


utilities.php

utilities.php にはapricot内部で使用する様々な関数が実装さいれています。ほとんどの場合、アプリケーションから使用することはないと思います。使用法などは以下のソースコードを参照して下さい。

/apricot/core/helpers

utilities.php
<?php
/**
 * Add path
 * @param string $base Base path
 * @param string $path Sub path, if necessary
 * @return string path
 */
function add_path(string $base, string $path = null) : string
{
    return (is_null($path)) ? $base : rtrim($base,'/').'/'.$path;
}
 
/**
 * Convert a multi-dimensional associative array to a Dot-notation array
 * @param  array   $hash multi-dimensional associative array
 * @param  string  $prepend
 * @return array   a Dot-notation array
 */
function array_dot(array $hash, $prepend = '')
{
    $dot_arry = [];
    foreach ($hash as $key => $value)
    {
        if (is_array($value) && count($value))
        {
            $dot_arry = array_merge($dot_arry, array_dot($value, $prepend.$key.'.'));
        }
        else
        {
            $dot_arry[$prepend.$key] = $value;
        }
    }
    return $dot_arry;
}
 
/**
 * Get an item from an array using Dot-notation
 * @param  array $hash multi-dimensional associative array
 * @param  string $dot key using Dot-notation
 * @param  mixed $default
 * @return mixed
 */
function array_get(array $hash, string $dot=null, $default=null)
{
    if (!isset($dot)) return $hash;
 
    $keys = explode('.', $dot);
    foreach($keys as $key)
    {
        if (array_key_exists($key, $hash))
        {
            $hash = $hash[$key];
        }
        else
        {
            return $default;
        }
    }
    return $hash;
}
 
/**
 * Checks if a key is present in an array using Dot-notation
 * @param  array $hash multi-dimensional associative array
 * @param  string $dot key using Dot-notation
 * @return bool
 */
function array_has(array $hash, string $dot):bool
{
    $keys = explode('.', $dot);
    foreach($keys as $key)
    {
        if (array_key_exists($key, $hash))
        {
            $hash = $hash[$key];
        }
        else
        {
            return false;
        }
    }
    return true;
}
 
/**
 * Get a subset of the input for only specified items
 * @param array $input
 * @param array|mixed $keys
 * @return array subset of the input
 */
function array_only(array $input, $keys)
{
    $key_arr = is_array($keys) ? $keys : array_slice(func_get_args(),1);
 
    $results = [];
    foreach ($key_arr as $key)
    {
        $results[$key] = $input[$key];
    }
    return $results;
}
 
/**
 * Get a subset of the input except for specified items
 * @param array $input
 * @param array|mixed $keys
 * @return array subset of the input
 */
function array_except(array $input, $keys=null)
{
    $key_arr = is_array($keys) ? $keys : array_slice(func_get_args(),1);
 
    $results = [];
    foreach($input as $key=>$value)
    {
        if (!in_array($key,$key_arr)) $results[$key]=$value;
    }
    return $results;
}
 
/**
 * Generate random numbers
 * @param number $length
 * @return string
 */
function str_random($length = 32)
{
    return substr(bin2hex(random_bytes($length)), 0, $length);
}
 
/**
 * Get short class name
 * @param object $object
 * @return string
 */
function get_short_class_name($object)
{
    return (new \ReflectionClass($object))->getShortName();
}
 
/**
 * Get snake_case from UpperCamelCase or lowerCamelCase
 * @param string $camel
 * @return string|null
 */
function snake_case(string $camel =null)
{
    if (!isset($camel)) return null;
 
    $snake = preg_replace('/([A-Z])/', '_$1', $camel);
    return ltrim(strtolower($snake), '_');
}
 
/**
 * Get SQL text from a file
 * @param string $filename
 * @return array
 */
function file_get_sql(string $filename):array
{
    if (!file_exists($filename)) return [];
 
    // Read a file
    $text = file_get_contents($filename);
    $text = str_replace(["\r\n","\r"], "\n", $text);
 
    // Remove comment
    $text = preg_replace("/\/\*.*?\*\//s", '', $text);
    $text = preg_replace("/--.*?$/m", '', $text);
 
    // Split SQL text
    $sql = preg_split("/\s*;\s*/", $text);
    array_walk($sql, function(&$item){
        $item = trim($item);
    });
    $sql = array_filter($sql, function($val){
        return !empty(trim($val));
    });
    return $sql;
}


ベースクラス

apricotでは、設定管理、ログ、デバッグバー、HTMLテンプレート、トランスレーションなどのクラスをシングルトンとして実装します。そして、それらのシングルトンのメソッドを静的にアクセス出来るようにします。これらの仕組みを実装する為に、core\Foundation に次の2つのベースクラスを作ります。

/apricot/core/Foundation

CallStatic.php
<?php
namespace Core\Foundation;
 
/**
 * CallStatic
 */
abstract class CallStatic
{
    protected static abstract function getInstance();
 
    /**
     * Handle calls to Instance,Statically.
     * @param  string  $method
     * @param  array   $args
     * @return mixed
     */
    public static function __callStatic($method, $args)
    {
        $instance = static::getInstance();
        return $instance->$method(...$args);
    }
}
Singleton.php
<?php
namespace Core\Foundation;
 
/**
 * Singleton
 */
abstract class Singleton extends CallStatic
{
    protected static abstract function createInstance();
 
    /**
     * Get instance.
     * @return object
     */
    public static function getInstance()
    {
        static $instance = null;
        if (!$instance)
        {
            $instance = static::createInstance();
        }
        return $instance;
    }
}


オートローディングの変更

以下のようにcomposer.jsonへ autoload を追加します。

/apricot

composer.json
{
	....
	"require" : {
		...
	},
	"autoload" : {
		"psr-4" : {
			"Core\\" : "core/"
		},
		"files": [
			"core/helpers/utilities.php",
			"core/helpers/boilerplates.php"
		]
	}
}

オートローディングファイルを更新する為に、プロジェクトフォルダで以下のコマンドを実行します。

composer dump-autoload