summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Application.php137
-rw-r--r--src/Config/Config.php100
-rw-r--r--src/Container/Container.php10
-rw-r--r--src/Container/ServiceProvider.php31
-rw-r--r--src/Database/Db.php188
-rw-r--r--src/Exceptions/Handler.php141
-rw-r--r--src/Http/Request.php67
-rw-r--r--src/Logger/EngelsystemLogger.php74
-rw-r--r--src/Renderer/EngineInterface.php21
-rw-r--r--src/Renderer/HtmlEngine.php34
-rw-r--r--src/Renderer/Renderer.php47
-rw-r--r--src/Routing/UrlGenerator.php25
-rw-r--r--src/helpers.php144
13 files changed, 1019 insertions, 0 deletions
diff --git a/src/Application.php b/src/Application.php
new file mode 100644
index 00000000..b62b28a9
--- /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->get($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/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/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/Handler.php b/src/Exceptions/Handler.php
new file mode 100644
index 00000000..95bcd132
--- /dev/null
+++ b/src/Exceptions/Handler.php
@@ -0,0 +1,141 @@
+<?php
+
+namespace Engelsystem\Exceptions;
+
+use Throwable;
+
+class Handler
+{
+ /** @var string */
+ protected $environment;
+
+ const ENV_PRODUCTION = 'prod';
+ const ENV_DEVELOPMENT = 'dev';
+
+ /**
+ * Handler constructor.
+ *
+ * @param string $environment production|development
+ */
+ public function __construct($environment = self::ENV_PRODUCTION)
+ {
+ $this->environment = $environment;
+
+ set_error_handler([$this, 'errorHandler']);
+ set_exception_handler([$this, 'exceptionHandler']);
+ }
+
+ /**
+ * @param int $number
+ * @param string $string
+ * @param string $file
+ * @param int $line
+ * @param array $context
+ */
+ public function errorHandler($number, $string, $file, $line, $context)
+ {
+ $trace = array_reverse(debug_backtrace());
+
+ $this->handle('error', $number, $string, $file, $line, $context, $trace);
+ }
+
+ /**
+ * @param Throwable $e
+ */
+ public function exceptionHandler($e)
+ {
+ $this->handle(
+ 'exception',
+ $e->getCode(),
+ get_class($e) . ': ' . $e->getMessage(),
+ $e->getFile(),
+ $e->getLine(),
+ ['exception' => $e]
+ );
+ }
+
+ /**
+ * @param string $type
+ * @param int $number
+ * @param string $string
+ * @param string $file
+ * @param int $line
+ * @param array $context
+ * @param array $trace
+ */
+ protected function handle($type, $number, $string, $file, $line, $context = [], $trace = [])
+ {
+ error_log(sprintf('%s: Number: %s, String: %s, File: %s:%u, Context: %s',
+ $type,
+ $number,
+ $string,
+ $file,
+ $line,
+ json_encode($context)
+ ));
+
+ $file = $this->stripBasePath($file);
+
+ if ($this->environment == self::ENV_DEVELOPMENT) {
+ 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, ucfirst($type), $number);
+ var_export([
+ 'string' => $string,
+ 'file' => $file . ':' . $line,
+ 'context' => $context,
+ 'stacktrace' => $this->formatStackTrace($trace),
+ ]);
+ echo '</pre>';
+ die();
+ }
+
+ echo 'An <del>un</del>expected error occurred, a team of untrained monkeys has been dispatched to deal with it.';
+ die();
+ }
+
+ /**
+ * @param array $stackTrace
+ * @return array
+ */
+ protected function formatStackTrace($stackTrace)
+ {
+ $return = [];
+
+ 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 => $trace['args'],
+ ];
+ }
+
+ return $return;
+ }
+
+ /**
+ * @param string $path
+ * @return string
+ */
+ protected function stripBasePath($path)
+ {
+ $basePath = realpath(__DIR__ . '/../..') . '/';
+ return str_replace($basePath, '', $path);
+ }
+
+ /**
+ * @param string $environment
+ */
+ public function setEnvironment($environment)
+ {
+ $this->environment = $environment;
+ }
+}
diff --git a/src/Http/Request.php b/src/Http/Request.php
new file mode 100644
index 00000000..e7850c8b
--- /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 !empty($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/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/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/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);
+}