Added some boilerplate code to Hanyuu.

This commit is contained in:
flash 2023-01-06 22:20:13 +00:00
parent 594c92179b
commit bb9d7d4058
11 changed files with 299 additions and 6 deletions

3
.gitignore vendored
View File

@ -2,3 +2,6 @@
[Dd]esktop.ini
.DS_Store
/public/robots.txt
/lib/index-dev
/.debug
/config/config.ini

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "lib/index"]
path = lib/index
url = https://git.flash.moe/flash/index.git

View File

46
hanyuu.php Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace Hanyuu;
use Index\Autoloader;
use Index\Environment;
use Hanyuu\Config\ArrayConfig;
define('HAU_STARTUP', microtime(true));
define('HAU_ROOT', __DIR__);
define('HAU_CLI', PHP_SAPI === 'cli');
define('HAU_DEBUG', is_file(HAU_ROOT . '/.debug'));
define('HAU_DIR_PUBLIC', HAU_ROOT . '/public');
define('HAU_DIR_SOURCE', HAU_ROOT . '/src');
define('HAU_DIR_LIBRARIES', HAU_ROOT . '/lib');
define('HAU_DIR_CONFIG', HAU_ROOT . '/config');
define('HAU_NDX_PATH', HAU_DIR_LIBRARIES . '/index');
define('HAU_NDX_PATH_DEV', HAU_DIR_LIBRARIES . '/index-dev');
require_once (HAU_DEBUG && is_dir(HAU_NDX_PATH_DEV) ? HAU_NDX_PATH_DEV : HAU_NDX_PATH) . '/index.php';
Autoloader::addNamespace(__NAMESPACE__, HAU_DIR_SOURCE);
Environment::setDebug(HAU_DEBUG);
mb_internal_encoding('utf-8');
date_default_timezone_set('utc');
set_include_path(get_include_path() . PATH_SEPARATOR . HAU_ROOT);
set_exception_handler(function(\Throwable $ex) {
if(HAU_CLI)
die((string)$ex);
http_response_code(500);
ob_clean();
if(HAU_DEBUG) {
header('Content-Type: text/plain; charset=utf-8');
die((string)$ex);
}
header('Content-Type: text/html; charset-utf-8');
die('<h2>Hanyuu is sad.</h2>');
});
$hau = new HanyuuContext(ArrayConfig::open(HAU_DIR_CONFIG . '/config.ini'));
$hau->connectDb();

1
lib/index Submodule

@ -0,0 +1 @@
Subproject commit f8c6602ab953491a3e540d1ab9c77f2d885ee120

View File

@ -1,6 +1,9 @@
<!doctype html>
<title><?=ucfirst(explode('.', $_SERVER['HTTP_HOST'])[1]);?> ID</title>
<center>
<h1>Under Construction</h1>
<img src="//static.flash.moe/images/me-tan-2.png"/>
</center>
<?php
namespace Hanyuu;
require_once __DIR__ . '/../hanyuu.php';
$request = \Index\Http\HttpRequest::fromRequest();
$hau->setUpHttp();
$hau->dispatchHttp($request);

View File

@ -0,0 +1,78 @@
<?php
namespace Hanyuu\Config;
use InvalidArgumentException;
class ArrayConfig implements IConfig {
private const SCOPE_CHAR = ':';
private const SCANNER_MODE = INI_SCANNER_TYPED;
private const TEST_VALUE = 'X-Config-Test';
private array $cache = [];
private array $exists = [];
public function __construct(
private array $config
) {}
public function scopeTo(string $prefix): IConfig {
return new ScopedConfig($this, $prefix);
}
private function getRaw(string $name): mixed {
$parts = array_reverse(explode(self::SCOPE_CHAR, $name));
$value = $this->config;
while(count($parts) > 1) {
$part = array_pop($parts);
if(!array_key_exists($part, $value))
break;
$value = $value[$part];
}
if($parts[0] === '')
return $value;
return $value[$parts[0]] ?? null;
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
if(array_key_exists($name, $this->cache))
$value = $this->cache[$name];
else {
$this->cache[$name] = $value = $this->getRaw($name);
$this->exists[$name] = $value !== null;
}
if($type !== IConfig::T_ANY && CfgTools::type($value) !== $type)
$value = null;
return $value ?? $default ?? CfgTools::default($type);
}
public function hasValue(string $name): bool {
if(array_key_exists($name, $this->exists))
return $this->exists[$name];
$exists = array_key_exists($name, $this->cache)
|| $this->getRaw($name) !== null;
return $this->exists[$name] = $exists;
}
public static function open(string $path): self {
$parsed = parse_ini_file($path, true, self::SCANNER_MODE);
if($parsed === false)
throw new InvalidArgumentException('Unable to parse configuration file in $path.');
return new static($parsed);
}
public static function from(string $string): self {
$parsed = parse_ini_string($string, true, self::SCANNER_MODE);
if($parsed === false)
throw new InvalidArgumentException('Unable to parse configuration string in $string.');
return new static($parsed);
}
}

27
src/Config/CfgTools.php Normal file
View File

@ -0,0 +1,27 @@
<?php
namespace Hanyuu\Config;
final class CfgTools {
public static function type($value): string {
return gettype($value);
}
public static function isValidType(string $type): bool {
return $type === IConfig::T_ARR
|| $type === IConfig::T_BOOL
|| $type === IConfig::T_INT
|| $type === IConfig::T_STR;
}
private const DEFAULTS = [
IConfig::T_ANY => null,
IConfig::T_STR => '',
IConfig::T_INT => 0,
IConfig::T_BOOL => false,
IConfig::T_ARR => [],
];
public static function default(string $type) {
return self::DEFAULTS[$type] ?? null;
}
}

14
src/Config/IConfig.php Normal file
View File

@ -0,0 +1,14 @@
<?php
namespace Hanyuu\Config;
interface IConfig {
public const T_ANY = '';
public const T_STR = 'string';
public const T_INT = 'integer';
public const T_BOOL = 'boolean';
public const T_ARR = 'array';
public function scopeTo(string $prefix): IConfig;
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed;
public function hasValue(string $name): bool;
}

View File

@ -0,0 +1,37 @@
<?php
namespace Hanyuu\Config;
use InvalidArgumentException;
class ScopedConfig implements IConfig {
private IConfig $config;
private string $prefix;
private const SCOPE_CHAR = ':';
public function __construct(IConfig $config, string $prefix) {
if($prefix === '')
throw new InvalidArgumentException('$prefix may not be empty.');
if(!str_ends_with($prefix, self::SCOPE_CHAR))
$prefix .= self::SCOPE_CHAR;
$this->config = $config;
$this->prefix = $prefix;
}
private function getName(string $name): string {
return $this->prefix . $name;
}
public function scopeTo(string $prefix): IConfig {
return $this->config->scopeTo($this->getName($prefix));
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
return $this->config->getValue($this->getName($name), $type, $default);
}
public function hasValue(string $name): bool {
return $this->config->hasValue($this->getName($name));
}
}

81
src/HanyuuContext.php Normal file
View File

@ -0,0 +1,81 @@
<?php
namespace Hanyuu;
use RuntimeException;
use Index\Data\IDbConnection;
use Index\Data\DbTools;
use Index\Http\HttpFx;
use Index\Http\HttpRequest;
use Index\Routing\IRouter;
use Hanyuu\Config\IConfig;
class HanyuuContext {
private const DB_INIT = 'SET SESSION time_zone = \'+00:00\', sql_mode = \'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION\';';
private IConfig $config;
private IDbConnection $dbConn;
public function __construct(IConfig $config) {
$this->config = $config;
}
public function connectDb(?IDbConnection $dbConn = null): void {
$dbConn ??= DbTools::create($this->config->getValue('database:dsn', IConfig::T_STR, 'null'));
$dbConn->execute(self::DB_INIT);
$this->dbConn = $dbConn;
}
public function getDb(): IDbConnection {
return $this->dbConn;
}
public function getDbQueryCount(): int {
$result = $this->dbConn->query('SHOW SESSION STATUS LIKE "Questions"');
return $result->next() ? $result->getInteger(0) : 0;
}
public function setUpHttp(): void {
$this->router = new HttpFx;
$this->router->use('/', function($response) {
$response->setPoweredBy('Hanyuu');
});
$this->registerErrorPages();
$this->registerHttpRoutes();
}
public function dispatchHttp(?HttpRequest $request = null): void {
$this->router->dispatch($request);
}
private function registerErrorPages(): void {
/*$this->router->addErrorHandler(400, function($response) {
$response->setContent(Template::renderRaw('errors.400'));
});
$this->router->addErrorHandler(403, function($response) {
$response->setContent(Template::renderRaw('errors.403'));
});
$this->router->addErrorHandler(404, function($response) {
$response->setContent(Template::renderRaw('errors.404'));
});
$this->router->addErrorHandler(500, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/500.html'));
});
$this->router->addErrorHandler(503, function($response) {
$response->setContent(file_get_contents(MSZ_TEMPLATES . '/503.html'));
});*/
}
private function registerHttpRoutes(): void {
$this->router->get('/', function($response, $request) {
$siteName = $this->config->getValue('site:name', IConfig::T_STR, 'Hanyuu');
return "<!doctype html>\r\n"
. "<title>{$siteName} ID</title>\r\n"
. "<center>\r\n"
. " <h1>Under Construction</h1>\r\n"
. " <img src=\"//static.flash.moe/images/me-tan-2.png\" alt=\"\"/>\r\n"
. "</center>\r\n";
});
}
}