diff options
Diffstat (limited to 'src')
25 files changed, 1467 insertions, 0 deletions
diff --git a/src/Application.php b/src/Application.php new file mode 100644 index 00000000..c9023c7b --- /dev/null +++ b/src/Application.php @@ -0,0 +1,137 @@ +<?php + +namespace Engelsystem; + +use Engelsystem\Config\Config; +use Engelsystem\Container\Container; +use Engelsystem\Container\ServiceProvider; +use Psr\Container\ContainerInterface; + +class Application extends Container +{ + /** @var string|null */ + protected $appPath = null; + + /** @var bool */ + protected $isBootstrapped = false; + + /** + * Registered service providers + * + * @var array + */ + protected $serviceProviders = []; + + /** + * Application constructor. + * + * @param string $appPath + */ + public function __construct($appPath = null) + { + if (!is_null($appPath)) { + $this->setAppPath($appPath); + } + + $this->registerBaseBindings(); + } + + protected function registerBaseBindings() + { + static::setInstance($this); + Container::setInstance($this); + $this->instance('app', $this); + $this->instance('container', $this); + $this->instance(Container::class, $this); + $this->instance(Application::class, $this); + $this->bind(ContainerInterface::class, Application::class); + } + + /** + * @param string|ServiceProvider $provider + * @return ServiceProvider + */ + public function register($provider) + { + if (is_string($provider)) { + $provider = $this->make($provider); + } + + $this->serviceProviders[] = $provider; + + $provider->register(); + + if ($this->isBootstrapped) { + $this->call([$provider, 'boot']); + } + + return $provider; + } + + /** + * Boot service providers + * + * @param Config|null $config + */ + public function bootstrap(Config $config = null) + { + if ($this->isBootstrapped) { + return; + } + + if ($config instanceof Config) { + foreach ($config->get('providers', []) as $provider) { + $this->register($provider); + } + } + + foreach ($this->serviceProviders as $provider) { + $this->call([$provider, 'boot']); + } + + $this->isBootstrapped = true; + } + + protected function registerPaths() + { + $appPath = $this->appPath; + + $this->instance('path', $appPath); + $this->instance('path.config', $appPath . DIRECTORY_SEPARATOR . 'config'); + $this->instance('path.lang', $appPath . DIRECTORY_SEPARATOR . 'locale'); + } + + /** + * Set app base path + * + * @param string $appPath + * @return static + */ + public function setAppPath($appPath) + { + $appPath = realpath($appPath); + $appPath = rtrim($appPath, DIRECTORY_SEPARATOR); + + $this->appPath = $appPath; + + $this->registerPaths(); + + return $this; + } + + /** + * @return string|null + */ + public function path() + { + return $this->appPath; + } + + /** + * @return bool + */ + public function isBooted() + { + return $this->isBootstrapped; + } +} diff --git a/src/Config/Config.php b/src/Config/Config.php new file mode 100644 index 00000000..34c21a78 --- /dev/null +++ b/src/Config/Config.php @@ -0,0 +1,100 @@ +<?php + +namespace Engelsystem\Config; + +class Config +{ + /** + * The config values + * + * @var array + */ + protected $data = []; + + /** + * @param string|null $key + * @param mixed $default + * @return mixed + */ + public function get($key, $default = null) + { + if (is_null($key)) { + return $this->data; + } + + if ($this->has($key)) { + return $this->data[$key]; + } + + return $default; + } + + /** + * @param string|array $key + * @param mixed $value + */ + public function set($key, $value = null) + { + if (is_array($key)) { + foreach ($key as $configKey => $configValue) { + $this->set($configKey, $configValue); + } + + return; + } + + $this->data[$key] = $value; + } + + /** + * @param string $key + * @return bool + */ + public function has($key) + { + return isset($this->data[$key]); + } + + /** + * @param string $key + */ + public function remove($key) + { + unset($this->data[$key]); + } + + /** + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->get($key); + } + + /** + * @param string $key + * @param mixed $value + */ + public function __set($key, $value) + { + $this->set($key, $value); + } + + /** + * @param string $key + * @return bool + */ + public function __isset($key) + { + return $this->has($key); + } + + /** + * @param string $key + */ + public function __unset($key) + { + $this->remove($key); + } +} diff --git a/src/Config/ConfigServiceProvider.php b/src/Config/ConfigServiceProvider.php new file mode 100644 index 00000000..01b648df --- /dev/null +++ b/src/Config/ConfigServiceProvider.php @@ -0,0 +1,26 @@ +<?php + +namespace Engelsystem\Config; + +use Engelsystem\Container\ServiceProvider; + +class ConfigServiceProvider extends ServiceProvider +{ + public function register() + { + $defaultConfigFile = config_path('config.default.php'); + $configFile = config_path('config.php'); + + $config = $this->app->make(Config::class); + $this->app->instance('config', $config); + + $config->set(require $defaultConfigFile); + + if (file_exists($configFile)) { + $config->set(array_replace_recursive( + $config->get(null), + require $configFile + )); + } + } +} diff --git a/src/Container/Container.php b/src/Container/Container.php new file mode 100644 index 00000000..44c57b6f --- /dev/null +++ b/src/Container/Container.php @@ -0,0 +1,10 @@ +<?php + +namespace Engelsystem\Container; + +use Illuminate\Container\Container as IlluminateContainer; +use Psr\Container\ContainerInterface; + +class Container extends IlluminateContainer implements ContainerInterface +{ +} diff --git a/src/Container/ServiceProvider.php b/src/Container/ServiceProvider.php new file mode 100644 index 00000000..2a1bbebf --- /dev/null +++ b/src/Container/ServiceProvider.php @@ -0,0 +1,31 @@ +<?php + +namespace Engelsystem\Container; + +use Engelsystem\Application; + +abstract class ServiceProvider +{ + /** @var Application */ + protected $app; + + /** + * ServiceProvider constructor. + * + * @param Application $app + */ + public function __construct(Application $app) + { + $this->app = $app; + } + + /** + * Register container bindings + */ + public function register() { } + + /** + * Called after other services had been registered + */ + public function boot() { } +} diff --git a/src/Database/DatabaseServiceProvider.php b/src/Database/DatabaseServiceProvider.php new file mode 100644 index 00000000..364816cc --- /dev/null +++ b/src/Database/DatabaseServiceProvider.php @@ -0,0 +1,31 @@ +<?php + +namespace Engelsystem\Database; + +use Engelsystem\Container\ServiceProvider; +use Exception; +use PDO; + +class DatabaseServiceProvider extends ServiceProvider +{ + public function register() + { + $config = $this->app->get('config'); + Db::connect( + 'mysql:host=' . $config->get('database')['host'] . ';dbname=' . $config->get('database')['db'] . ';charset=utf8', + $config->get('database')['user'], + $config->get('database')['pw'] + ) || $this->exitOnError(); + + Db::getPdo()->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + Db::getPdo()->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + /** + * @throws Exception + */ + protected function exitOnError() + { + throw new Exception('Error: Unable to connect to database'); + } +} diff --git a/src/Database/Db.php b/src/Database/Db.php new file mode 100644 index 00000000..7042998f --- /dev/null +++ b/src/Database/Db.php @@ -0,0 +1,188 @@ +<?php + +namespace Engelsystem\Database; + +use PDO; +use PDOException; +use PDOStatement; + +class Db +{ + /** @var PDO */ + protected static $db; + + /** @var PDOStatement */ + protected static $stm = null; + + /** @var bool */ + protected static $lastStatus = true; + + /** + * Connect to database + * + * @param string $dsn + * @param string $username + * @param string $password + * @param array $options + * @return bool + */ + public static function connect($dsn, $username = null, $password = null, $options = []) + { + try { + self::$db = new PDO($dsn, $username, $password, $options); + } catch (PDOException $e) { + return false; + } + + return true; + } + + /** + * Run a prepared query + * + * @param string $query + * @param array $bindings + * @return PDOStatement + */ + public static function query($query, array $bindings = []) + { + self::$stm = self::$db->prepare($query); + self::$lastStatus = self::$stm->execute($bindings); + + return self::$stm; + } + + /** + * Run a sql query + * + * @param string $query + * @return bool + */ + public static function unprepared($query) + { + self::$stm = self::$db->query($query); + self::$lastStatus = (self::$stm instanceof PDOStatement); + + return self::$lastStatus; + } + + /** + * Run a select query + * + * @param string $query + * @param array $bindings + * @return array + */ + public static function select($query, array $bindings = []) + { + self::query($query, $bindings); + + return self::$stm->fetchAll(PDO::FETCH_ASSOC); + } + + /** + * Run a select query and return only the first result or null if no result is found. + * + * @param string $query + * @param array $bindings + * @return array|null + */ + public static function selectOne($query, array $bindings = []) + { + $result = self::select($query, $bindings); + + if (empty($result)) { + return null; + } + + return array_shift($result); + } + + /** + * Run an insert query + * + * @param string $query + * @param array $bindings + * @return int Row count + */ + public static function insert($query, array $bindings = []) + { + self::query($query, $bindings); + + return self::$stm->rowCount(); + } + + /** + * Run an update query + * + * @param string $query + * @param array $bindings + * @return int + */ + public static function update($query, array $bindings = []) + { + self::query($query, $bindings); + + return self::$stm->rowCount(); + } + + /** + * Run a delete query + * + * @param string $query + * @param array $bindings + * @return int + */ + public static function delete($query, array $bindings = []) + { + self::query($query, $bindings); + + return self::$stm->rowCount(); + } + + /** + * Run a single statement + * + * @param string $query + * @param array $bindings + * @return bool + */ + public static function statement($query, array $bindings = []) + { + self::query($query, $bindings); + + return self::$lastStatus; + } + + /** + * Returns the last error + * + * @return array + */ + public static function getError() + { + if (!self::$stm instanceof PDOStatement) { + return [-1, null, null]; + } + + return self::$stm->errorInfo(); + } + + /** + * Get the PDO instance + * + * @return PDO + */ + public static function getPdo() + { + return self::$db; + } + + /** + * @return PDOStatement|false|null + */ + public static function getStm() + { + return self::$stm; + } +} diff --git a/src/Exceptions/ExceptionsServiceProvider.php b/src/Exceptions/ExceptionsServiceProvider.php new file mode 100644 index 00000000..a9bc2b17 --- /dev/null +++ b/src/Exceptions/ExceptionsServiceProvider.php @@ -0,0 +1,58 @@ +<?php + +namespace Engelsystem\Exceptions; + +use Engelsystem\Container\ServiceProvider; +use Engelsystem\Exceptions\Handlers\HandlerInterface; +use Engelsystem\Exceptions\Handlers\Legacy; +use Engelsystem\Exceptions\Handlers\LegacyDevelopment; +use Engelsystem\Exceptions\Handlers\Whoops; +use Whoops\Run as WhoopsRunner; + +class ExceptionsServiceProvider extends ServiceProvider +{ + public function register() + { + $errorHandler = $this->app->make(Handler::class); + $this->addProductionHandler($errorHandler); + $this->addDevelopmentHandler($errorHandler); + $this->app->instance('error.handler', $errorHandler); + $this->app->bind(Handler::class, 'error.handler'); + $errorHandler->register(); + } + + public function boot() + { + /** @var Handler $handler */ + $handler = $this->app->get('error.handler'); + $request = $this->app->get('request'); + + $handler->setRequest($request); + } + + /** + * @param Handler $errorHandler + */ + protected function addProductionHandler($errorHandler) + { + $handler = $this->app->make(Legacy::class); + $this->app->instance('error.handler.production', $handler); + $errorHandler->setHandler(Handler::ENV_PRODUCTION, $handler); + $this->app->bind(HandlerInterface::class, 'error.handler.production'); + } + + /** + * @param Handler $errorHandler + */ + protected function addDevelopmentHandler($errorHandler) + { + $handler = $this->app->make(LegacyDevelopment::class); + + if (class_exists(WhoopsRunner::class)) { + $handler = $this->app->make(Whoops::class); + } + + $this->app->instance('error.handler.development', $handler); + $errorHandler->setHandler(Handler::ENV_DEVELOPMENT, $handler); + } +} diff --git a/src/Exceptions/Handler.php b/src/Exceptions/Handler.php new file mode 100644 index 00000000..ee15717a --- /dev/null +++ b/src/Exceptions/Handler.php @@ -0,0 +1,135 @@ +<?php + +namespace Engelsystem\Exceptions; + +use Engelsystem\Exceptions\Handlers\HandlerInterface; +use Engelsystem\Http\Request; +use ErrorException; +use Throwable; + +class Handler +{ + /** @var string */ + protected $environment; + + /** @var HandlerInterface[] */ + protected $handler = []; + + /** @var Request */ + protected $request; + + const ENV_PRODUCTION = 'prod'; + const ENV_DEVELOPMENT = 'dev'; + + /** + * Handler constructor. + * + * @param string $environment prod|dev + */ + public function __construct($environment = self::ENV_PRODUCTION) + { + $this->environment = $environment; + } + + /** + * Activate the error handler + */ + public function register() + { + set_error_handler([$this, 'errorHandler']); + set_exception_handler([$this, 'exceptionHandler']); + } + + /** + * @param int $number + * @param string $message + * @param string $file + * @param int $line + */ + public function errorHandler($number, $message, $file, $line) + { + $exception = new ErrorException($message, 0, $number, $file, $line); + $this->exceptionHandler($exception); + } + + /** + * @param Throwable $e + */ + public function exceptionHandler($e) + { + if (!$this->request instanceof Request) { + $this->request = new Request(); + } + + $handler = $this->handler[$this->environment]; + $handler->report($e); + $handler->render($this->request, $e); + $this->die(); + } + + /** + * Exit the application + * + * @codeCoverageIgnore + * @param string $message + */ + protected function die($message = '') + { + echo $message; + die(); + } + + /** + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + + /** + * @param string $environment + */ + public function setEnvironment($environment) + { + $this->environment = $environment; + } + + /** + * @param string $environment + * @return HandlerInterface|HandlerInterface[] + */ + public function getHandler($environment = null) + { + if (!is_null($environment)) { + return $this->handler[$environment]; + } + + return $this->handler; + } + + /** + * @param string $environment + * @param HandlerInterface $handler + */ + public function setHandler($environment, HandlerInterface $handler) + { + $this->handler[$environment] = $handler; + } + + /** + * @return Request + */ + public function getRequest() + { + return $this->request; + } + + /** + * @param Request $request + */ + public function setRequest(Request $request) + { + $this->request = $request; + } +} diff --git a/src/Exceptions/Handlers/HandlerInterface.php b/src/Exceptions/Handlers/HandlerInterface.php new file mode 100644 index 00000000..9de33e1f --- /dev/null +++ b/src/Exceptions/Handlers/HandlerInterface.php @@ -0,0 +1,21 @@ +<?php + +namespace Engelsystem\Exceptions\Handlers; + +use Engelsystem\Http\Request; +use Throwable; + +interface HandlerInterface +{ + /** + * @param Request $request + * @param Throwable $e + */ + public function render($request, Throwable $e); + + /** + * @param Throwable $e + * @return + */ + public function report(Throwable $e); +} diff --git a/src/Exceptions/Handlers/Legacy.php b/src/Exceptions/Handlers/Legacy.php new file mode 100644 index 00000000..39683f72 --- /dev/null +++ b/src/Exceptions/Handlers/Legacy.php @@ -0,0 +1,42 @@ +<?php + +namespace Engelsystem\Exceptions\Handlers; + +use Engelsystem\Http\Request; +use Throwable; + +class Legacy implements HandlerInterface +{ + /** + * @param Request $request + * @param Throwable $e + */ + public function render($request, Throwable $e) + { + echo 'An <del>un</del>expected error occurred, a team of untrained monkeys has been dispatched to deal with it.'; + } + + /** + * @param Throwable $e + */ + public function report(Throwable $e) + { + error_log(sprintf('Exception: Code: %s, Message: %s, File: %s:%u, Trace: %s', + $e->getCode(), + $e->getMessage(), + $this->stripBasePath($e->getFile()), + $e->getLine(), + json_encode($e->getTrace()) + )); + } + + /** + * @param string $path + * @return string + */ + protected function stripBasePath($path) + { + $basePath = realpath(__DIR__ . '/../../..') . '/'; + return str_replace($basePath, '', $path); + } +} diff --git a/src/Exceptions/Handlers/LegacyDevelopment.php b/src/Exceptions/Handlers/LegacyDevelopment.php new file mode 100644 index 00000000..86f86f4c --- /dev/null +++ b/src/Exceptions/Handlers/LegacyDevelopment.php @@ -0,0 +1,57 @@ +<?php + +namespace Engelsystem\Exceptions\Handlers; + +use Engelsystem\Http\Request; +use Throwable; + +class LegacyDevelopment extends Legacy +{ + /** + * @param Request $request + * @param Throwable $e + */ + public function render($request, Throwable $e) + { + $file = $this->stripBasePath($e->getFile()); + + echo '<pre style="background-color:#333;color:#ccc;z-index:1000;position:fixed;bottom:1em;padding:1em;width:97%;max-height: 90%;overflow-y:auto;">'; + echo sprintf('%s: (%s)' . PHP_EOL, get_class($e), $e->getCode()); + $data = [ + 'string' => $e->getMessage(), + 'file' => $file . ':' . $e->getLine(), + 'stacktrace' => $this->formatStackTrace($e->getTrace()), + ]; + var_dump($data); + echo '</pre>'; + } + + /** + * @param array $stackTrace + * @return array + */ + protected function formatStackTrace($stackTrace) + { + $return = []; + $stackTrace = array_reverse($stackTrace); + + foreach ($stackTrace as $trace) { + $path = ''; + $line = ''; + + if (isset($trace['file']) && isset($trace['line'])) { + $path = $this->stripBasePath($trace['file']); + $line = $trace['line']; + } + + $functionName = $trace['function']; + + $return[] = [ + 'file' => $path . ':' . $line, + $functionName => isset($trace['args']) ? $trace['args'] : null, + ]; + } + + return $return; + } +} diff --git a/src/Exceptions/Handlers/Whoops.php b/src/Exceptions/Handlers/Whoops.php new file mode 100644 index 00000000..807f5eb0 --- /dev/null +++ b/src/Exceptions/Handlers/Whoops.php @@ -0,0 +1,85 @@ +<?php + +namespace Engelsystem\Exceptions\Handlers; + +use Engelsystem\Application; +use Engelsystem\Container\Container; +use Engelsystem\Http\Request; +use Throwable; +use Whoops\Handler\JsonResponseHandler; +use Whoops\Handler\PrettyPageHandler; +use Whoops\Run as WhoopsRunner; + +class Whoops extends Legacy implements HandlerInterface +{ + /** @var Application */ + protected $app; + + public function __construct(Container $app) + { + $this->app = $app; + } + + /** + * @param Request $request + * @param Throwable $e + */ + public function render($request, Throwable $e) + { + $whoops = $this->app->make(WhoopsRunner::class); + $handler = $this->getPrettyPageHandler($e); + $whoops->pushHandler($handler); + + if ($request->isXmlHttpRequest()) { + $handler = $this->getJsonResponseHandler(); + $whoops->pushHandler($handler); + } + + echo $whoops->handleException($e); + } + + /** + * @param Throwable $e + * @return PrettyPageHandler + */ + protected function getPrettyPageHandler(Throwable $e) + { + $handler = $this->app->make(PrettyPageHandler::class); + + $handler->setPageTitle('Just another ' . get_class($e) . ' to fix :('); + $handler->setApplicationPaths([realpath(__DIR__ . '/../..')]); + + $data = $this->getData(); + $handler->addDataTable('Application', $data); + + return $handler; + } + + /** + * @return JsonResponseHandler + */ + protected function getJsonResponseHandler() + { + $handler = $this->app->make(JsonResponseHandler::class); + $handler->setJsonApi(true); + $handler->addTraceToOutput(true); + + return $handler; + } + + /** + * Aggregate application data + * + * @return array + */ + protected function getData() + { + global $user; + + $data = []; + $data['user'] = $user; + $data['Booted'] = $this->app->isBooted(); + + return $data; + } +} diff --git a/src/Http/Request.php b/src/Http/Request.php new file mode 100644 index 00000000..c6a9e5ad --- /dev/null +++ b/src/Http/Request.php @@ -0,0 +1,67 @@ +<?php + +namespace Engelsystem\Http; + +use Symfony\Component\HttpFoundation\Request as SymfonyRequest; + +class Request extends SymfonyRequest +{ + /** + * Get POST input + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function postData($key, $default = null) + { + return $this->request->get($key, $default); + } + + /** + * Get input data + * + * @param string $key + * @param mixed $default + * @return mixed + */ + public function input($key, $default = null) + { + return $this->get($key, $default); + } + + /** + * Checks if the input exists + * + * @param string $key + * @return bool + */ + public function has($key) + { + $value = $this->input($key); + + return !is_null($value); + } + + /** + * Get the requested path + * + * @return string + */ + public function path() + { + $pattern = trim($this->getPathInfo(), '/'); + + return $pattern == '' ? '/' : $pattern; + } + + /** + * Return the current URL + * + * @return string + */ + public function url() + { + return rtrim(preg_replace('/\?.*/', '', $this->getUri()), '/'); + } +} diff --git a/src/Http/RequestServiceProvider.php b/src/Http/RequestServiceProvider.php new file mode 100644 index 00000000..077e9ecc --- /dev/null +++ b/src/Http/RequestServiceProvider.php @@ -0,0 +1,14 @@ +<?php + +namespace Engelsystem\Http; + +use Engelsystem\Container\ServiceProvider; + +class RequestServiceProvider extends ServiceProvider +{ + public function register() + { + $request = $this->app->call([Request::class, 'createFromGlobals']); + $this->app->instance('request', $request); + } +} diff --git a/src/Http/SessionServiceProvider.php b/src/Http/SessionServiceProvider.php new file mode 100644 index 00000000..55e3f48b --- /dev/null +++ b/src/Http/SessionServiceProvider.php @@ -0,0 +1,52 @@ +<?php + +namespace Engelsystem\Http; + +use Engelsystem\Container\ServiceProvider; +use Symfony\Component\HttpFoundation\Session\Session; +use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; +use Symfony\Component\HttpFoundation\Session\Storage\SessionStorageInterface; + +class SessionServiceProvider extends ServiceProvider +{ + public function register() + { + $sessionStorage = $this->getSessionStorage(); + $this->app->instance('session.storage', $sessionStorage); + $this->app->bind(SessionStorageInterface::class, 'session.storage'); + + $session = $this->app->make(Session::class); + $this->app->instance('session', $session); + + /** @var Request $request */ + $request = $this->app->get('request'); + $request->setSession($session); + + $session->start(); + } + + /** + * Returns the session storage + * + * @return SessionStorageInterface + */ + protected function getSessionStorage() + { + if ($this->isCli()) { + return $this->app->make(MockArraySessionStorage::class); + } + + return $this->app->make(NativeSessionStorage::class, ['options' => ['cookie_httponly' => true]]); + } + + /** + * Test if is called from cli + * + * @return bool + */ + protected function isCli() + { + return PHP_SAPI == 'cli'; + } +} diff --git a/src/Logger/EngelsystemLogger.php b/src/Logger/EngelsystemLogger.php new file mode 100644 index 00000000..1f255b69 --- /dev/null +++ b/src/Logger/EngelsystemLogger.php @@ -0,0 +1,74 @@ +<?php + +namespace Engelsystem\Logger; + +use Psr\Log\AbstractLogger; +use Psr\Log\InvalidArgumentException; +use Psr\Log\LogLevel; + +class EngelsystemLogger extends AbstractLogger +{ + protected $allowedLevels = [ + LogLevel::ALERT, + LogLevel::CRITICAL, + LogLevel::DEBUG, + LogLevel::EMERGENCY, + LogLevel::ERROR, + LogLevel::INFO, + LogLevel::NOTICE, + LogLevel::WARNING, + ]; + + /** + * Logs with an arbitrary level. + * + * @TODO: Implement $context['exception'] + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @throws InvalidArgumentException + */ + public function log($level, $message, array $context = []) + { + if (!$this->checkLevel($level)) { + throw new InvalidArgumentException(); + } + + $message = $this->interpolate($message, $context); + + LogEntry_create($level, $message); + } + + /** + * Interpolates context values into the message placeholders. + * + * @param string $message + * @param array $context + * @return string + */ + protected function interpolate($message, array $context = []) + { + foreach ($context as $key => $val) { + // check that the value can be casted to string + if (is_array($val) || (is_object($val) && !method_exists($val, '__toString'))) { + continue; + } + + // replace the values of the message + $message = str_replace('{' . $key . '}', $val, $message); + } + + return $message; + } + + /** + * @param string $level + * @return bool + */ + protected function checkLevel($level) + { + return in_array($level, $this->allowedLevels); + } +} diff --git a/src/Logger/LoggerServiceProvider.php b/src/Logger/LoggerServiceProvider.php new file mode 100644 index 00000000..cf22f383 --- /dev/null +++ b/src/Logger/LoggerServiceProvider.php @@ -0,0 +1,18 @@ +<?php + +namespace Engelsystem\Logger; + +use Engelsystem\Container\ServiceProvider; +use Psr\Log\LoggerInterface; + +class LoggerServiceProvider extends ServiceProvider +{ + public function register() + { + $logger = $this->app->make(EngelsystemLogger::class); + $this->app->instance('logger', $logger); + + $this->app->bind(LoggerInterface::class, 'logger'); + $this->app->bind(EngelsystemLogger::class, 'logger'); + } +} diff --git a/src/Renderer/EngineInterface.php b/src/Renderer/EngineInterface.php new file mode 100644 index 00000000..ca468db5 --- /dev/null +++ b/src/Renderer/EngineInterface.php @@ -0,0 +1,21 @@ +<?php + +namespace Engelsystem\Renderer; + +interface EngineInterface +{ + /** + * Render a template + * + * @param string $path + * @param mixed[] $data + * @return string + */ + public function get($path, $data = []); + + /** + * @param string $path + * @return bool + */ + public function canRender($path); +} diff --git a/src/Renderer/HtmlEngine.php b/src/Renderer/HtmlEngine.php new file mode 100644 index 00000000..75343bbd --- /dev/null +++ b/src/Renderer/HtmlEngine.php @@ -0,0 +1,34 @@ +<?php + +namespace Engelsystem\Renderer; + +class HtmlEngine implements EngineInterface +{ + /** + * Render a template + * + * @param string $path + * @param mixed[] $data + * @return string + */ + public function get($path, $data = []) + { + $template = file_get_contents($path); + if (is_array($data)) { + foreach ($data as $name => $content) { + $template = str_replace('%' . $name . '%', $content, $template); + } + } + + return $template; + } + + /** + * @param string $path + * @return bool + */ + public function canRender($path) + { + return strpos($path, '.htm') && file_exists($path); + } +} diff --git a/src/Renderer/Renderer.php b/src/Renderer/Renderer.php new file mode 100644 index 00000000..de31ca74 --- /dev/null +++ b/src/Renderer/Renderer.php @@ -0,0 +1,47 @@ +<?php + +namespace Engelsystem\Renderer; + +use Psr\Log\LoggerAwareTrait; + +class Renderer +{ + use LoggerAwareTrait; + + /** @var EngineInterface[] */ + protected $renderer = []; + + /** + * Render a template + * + * @param string $template + * @param mixed[] $data + * @return string + */ + public function render($template, $data = []) + { + foreach ($this->renderer as $renderer) { + if (!$renderer->canRender($template)) { + continue; + } + + return $renderer->get($template, $data); + } + + if ($this->logger) { + $this->logger->error('Unable to find a renderer for template file "{file}"', ['file' => $template]); + } + + return ''; + } + + /** + * Add a new renderer engine + * + * @param EngineInterface $renderer + */ + public function addRenderer(EngineInterface $renderer) + { + $this->renderer[] = $renderer; + } +} diff --git a/src/Renderer/RendererServiceProvider.php b/src/Renderer/RendererServiceProvider.php new file mode 100644 index 00000000..3e8d69bc --- /dev/null +++ b/src/Renderer/RendererServiceProvider.php @@ -0,0 +1,36 @@ +<?php + +namespace Engelsystem\Renderer; + +use Engelsystem\Container\ServiceProvider; + +class RendererServiceProvider extends ServiceProvider +{ + public function register() + { + $this->registerRenderer(); + $this->registerHtmlEngine(); + } + + public function boot() + { + $renderer = $this->app->get('renderer'); + + foreach ($this->app->tagged('renderer.engine') as $engine) { + $renderer->addRenderer($engine); + } + } + + protected function registerRenderer() + { + $renderer = $this->app->make(Renderer::class); + $this->app->instance('renderer', $renderer); + } + + protected function registerHtmlEngine() + { + $htmlEngine = $this->app->make(HtmlEngine::class); + $this->app->instance('renderer.htmlEngine', $htmlEngine); + $this->app->tag('renderer.htmlEngine', ['renderer.engine']); + } +} diff --git a/src/Routing/RoutingServiceProvider.php b/src/Routing/RoutingServiceProvider.php new file mode 100644 index 00000000..b7db1383 --- /dev/null +++ b/src/Routing/RoutingServiceProvider.php @@ -0,0 +1,14 @@ +<?php + +namespace Engelsystem\Routing; + +use Engelsystem\Container\ServiceProvider; + +class RoutingServiceProvider extends ServiceProvider +{ + public function register() + { + $urlGenerator = $this->app->make(UrlGenerator::class); + $this->app->instance('routing.urlGenerator', $urlGenerator); + } +} diff --git a/src/Routing/UrlGenerator.php b/src/Routing/UrlGenerator.php new file mode 100644 index 00000000..6df52425 --- /dev/null +++ b/src/Routing/UrlGenerator.php @@ -0,0 +1,25 @@ +<?php + +namespace Engelsystem\Routing; + +class UrlGenerator +{ + /** + * @param string $path + * @param array $parameters + * @return string + */ + public function to($path, $parameters = []) + { + $path = '/' . ltrim($path, '/'); + $request = app('request'); + $uri = $request->getUriForPath($path); + + if (!empty($parameters) && is_array($parameters)) { + $parameters = http_build_query($parameters); + $uri .= '?' . $parameters; + } + + return $uri; + } +} diff --git a/src/helpers.php b/src/helpers.php new file mode 100644 index 00000000..5a48498a --- /dev/null +++ b/src/helpers.php @@ -0,0 +1,144 @@ +<?php +// Some useful functions + +use Engelsystem\Application; +use Engelsystem\Config\Config; +use Engelsystem\Http\Request; +use Engelsystem\Renderer\Renderer; +use Engelsystem\Routing\UrlGenerator; +use Symfony\Component\HttpFoundation\Session\SessionInterface; + +/** + * Get the global app instance + * + * @param string $id + * @return mixed + */ +function app($id = null) +{ + if (is_null($id)) { + return Application::getInstance(); + } + + return Application::getInstance()->get($id); +} + +/** + * @param string $path + * @return string + */ +function base_path($path = '') +{ + return app('path') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path); +} + +/** + * Get or set config values + * + * @param string|array $key + * @param mixed $default + * @return mixed|Config + */ +function config($key = null, $default = null) +{ + $config = app('config'); + + if (empty($key)) { + return $config; + } + + if (is_array($key)) { + $config->set($key); + return true; + } + + return $config->get($key, $default); +} + +/** + * @param string $path + * @return string + */ +function config_path($path = '') +{ + return app('path.config') . (empty($path) ? '' : DIRECTORY_SEPARATOR . $path); +} + +/** + * @param string $key + * @param mixed $default + * @return mixed + */ +function env($key, $default = null) +{ + $value = getenv($key); + if ($value === false) { + return $default; + } + + return $value; +} + +/** + * @param string $key + * @param mixed $default + * @return Request|mixed + */ +function request($key = null, $default = null) +{ + $request = app('request'); + + if (is_null($key)) { + return $request; + } + + return $request->input($key, $default); +} + +/** + * @param string $key + * @param mixed $default + * @return SessionInterface|mixed + */ +function session($key = null, $default = null) +{ + $session = app('session'); + + if (is_null($key)) { + return $session; + } + + return $session->get($key, $default); +} + +/** + * @param string $path + * @param array $parameters + * @return UrlGenerator|string + */ +function url($path = null, $parameters = []) +{ + $urlGenerator = app('routing.urlGenerator'); + + if (is_null($path)) { + return $urlGenerator; + } + + return $urlGenerator->to($path, $parameters); +} + +/** + * @param string $template + * @param mixed[] $data + * @return Renderer|string + */ +function view($template = null, $data = null) +{ + $renderer = app('renderer'); + + if (is_null($template)) { + return $renderer; + } + + return $renderer->render($template, $data); +} |