228 lines
5.8 KiB
PHP
228 lines
5.8 KiB
PHP
<?php
|
|
/*
|
|
* Copyright (c) 2025, Антон Аксенов
|
|
* This file is part of iptv.axenov.dev web interface
|
|
* MIT License: https://git.axenov.dev/IPTV/web/src/branch/master/LICENSE
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Core;
|
|
|
|
use App\Core\TwigExtention as IptvTwigExtension;
|
|
use Dotenv\Dotenv;
|
|
use InvalidArgumentException;
|
|
use Redis;
|
|
use Slim\App;
|
|
use Slim\Factory\AppFactory;
|
|
use Slim\Views\Twig;
|
|
use Slim\Views\TwigMiddleware;
|
|
use Twig\Error\LoaderError;
|
|
use Twig\Extension\DebugExtension;
|
|
|
|
/**
|
|
* Загрузчик приложения
|
|
*/
|
|
final class Kernel
|
|
{
|
|
/**
|
|
* Версия приложения
|
|
*/
|
|
public const string VERSION = '1.0.0';
|
|
|
|
/**
|
|
* @var Kernel
|
|
*/
|
|
private static Kernel $instance;
|
|
|
|
/**
|
|
* @var App
|
|
*/
|
|
protected App $app;
|
|
|
|
/**
|
|
* @var IniFile
|
|
*/
|
|
protected IniFile $iniFile;
|
|
|
|
/**
|
|
* @var array Конфигурация приложения
|
|
*/
|
|
protected array $config = [];
|
|
|
|
/**
|
|
* @var Redis|null
|
|
*/
|
|
protected ?Redis $cache = null;
|
|
|
|
/**
|
|
* Закрытый конструктор
|
|
*
|
|
* @throws LoaderError
|
|
*/
|
|
private function __construct()
|
|
{
|
|
$this->app = AppFactory::create();
|
|
$this->loadSettings();
|
|
$this->loadRoutes();
|
|
$this->loadTwig();
|
|
|
|
return $this->app;
|
|
}
|
|
|
|
/**
|
|
* Возвращает объект приложения
|
|
*
|
|
* @return Kernel
|
|
*/
|
|
public static function instance(): Kernel
|
|
{
|
|
return self::$instance ??= new self();
|
|
}
|
|
|
|
/**
|
|
* Загружает файл .env или .env.$env
|
|
*
|
|
* @param string $env
|
|
* @return array
|
|
*/
|
|
protected function loadDotEnvFile(string $env = ''): array
|
|
{
|
|
$filename = empty($env) ? '.env' : ".env.$env";
|
|
if (!file_exists(root_path($filename))) {
|
|
return [];
|
|
}
|
|
|
|
$dotenv = Dotenv::createMutable(root_path(), $filename);
|
|
return $dotenv->safeLoad();
|
|
}
|
|
|
|
/**
|
|
* Загружает конфигурационные файлы
|
|
*
|
|
* @return void
|
|
*/
|
|
protected function loadSettings(): void
|
|
{
|
|
$env = $this->loadDotEnvFile();
|
|
if (!empty($env['APP_ENV'])) {
|
|
$this->loadDotEnvFile($env['APP_ENV']);
|
|
}
|
|
|
|
foreach (glob(config_path() . '/*.php') as $file) {
|
|
$key = basename($file, '.php');
|
|
$this->config += [$key => require_once $file];
|
|
}
|
|
|
|
date_default_timezone_set($this->config['app']['timezone'] ?? 'GMT');
|
|
}
|
|
|
|
/**
|
|
* Загружает маршруты
|
|
*
|
|
* @return void
|
|
* @see https://www.slimframework.com/docs/v4/objects/routing.html
|
|
*/
|
|
protected function loadRoutes(): void
|
|
{
|
|
foreach ($this->config['routes'] as $route) {
|
|
if (is_array($route['method'])) {
|
|
$definition = $this->app->map($route['method'], $route['path'], $route['handler']);
|
|
} else {
|
|
$method = trim($route['method']);
|
|
$isPossible = in_array($method, ['GET', 'POST', 'OPTIONS', 'PUT', 'PATCH', 'DELETE']);
|
|
|
|
$func = match (true) {
|
|
$method === '*' => 'any',
|
|
$isPossible => strtolower($method),
|
|
default => throw new InvalidArgumentException(sprintf('Неверный HTTP метод %s', $method))
|
|
};
|
|
|
|
$definition = $this->app->$func($route['path'], $route['handler']);
|
|
}
|
|
|
|
if (!empty($route['name'])) {
|
|
$definition->setName($route['name']);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Загружает шаблонизатор и его расширения
|
|
*
|
|
* @return void
|
|
* @throws LoaderError
|
|
* @see https://www.slimframework.com/docs/v4/features/twig-view.html
|
|
*/
|
|
protected function loadTwig(): void
|
|
{
|
|
$twig = Twig::create(root_path('views'), $this->config['twig']);
|
|
$twig->addExtension(new IptvTwigExtension());
|
|
$this->app->add(TwigMiddleware::create($this->app, $twig));
|
|
if ($this->config['twig']['debug']) {
|
|
$twig->addExtension(new DebugExtension());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Возвращает объект подключения к Redis
|
|
*
|
|
* @return Redis
|
|
* @see https://github.com/phpredis/phpredis/?tab=readme-ov-file
|
|
*/
|
|
public function redis(): Redis
|
|
{
|
|
if (!empty($this->cache)) {
|
|
return $this->cache;
|
|
}
|
|
|
|
$options = [
|
|
'host' => $this->config['cache']['host'],
|
|
'port' => (int)$this->config['cache']['port'],
|
|
];
|
|
|
|
if (!empty($this->config['cache']['password'])) {
|
|
$options['auth'] = $this->config['cache']['password'];
|
|
}
|
|
|
|
$this->cache = new Redis($options);
|
|
$this->cache->select((int)$this->config['cache']['db']);
|
|
$this->cache->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_JSON);
|
|
|
|
return $this->cache;
|
|
}
|
|
|
|
/**
|
|
* Возвращает значение из конфига
|
|
*
|
|
* @param string $key Ключ в формате "config.key"
|
|
* @param mixed|null $default Значение по умолчанию
|
|
* @return mixed
|
|
*/
|
|
public function config(string $key, mixed $default = null): mixed
|
|
{
|
|
$parts = explode('.', $key);
|
|
return $this->config[$parts[0]][$parts[1]] ?? $default;
|
|
}
|
|
|
|
/**
|
|
* Возвращает объект приложения
|
|
*
|
|
* @return App
|
|
*/
|
|
public function app(): App
|
|
{
|
|
return $this->app;
|
|
}
|
|
|
|
/**
|
|
* Возвращает объект ini-файла
|
|
*
|
|
* @return IniFile
|
|
*/
|
|
public function ini(): IniFile
|
|
{
|
|
return $this->iniFile ??= new IniFile();
|
|
}
|
|
}
|