From be507e0d4c8462177c3919abe6ad4b7ce5eaa3f4 Mon Sep 17 00:00:00 2001 From: Matthew Baggett Date: Fri, 12 Jun 2020 15:57:36 +0200 Subject: [PATCH] Hack n' slash rework. --- src/App.php | 478 ++++++------------ .../BenzineConfigurationException.php | 4 +- src/Exceptions/BenzineException.php | 4 +- src/Exceptions/DbConfigException.php | 4 +- src/Middlewares/AccessRequirements.php | 178 +++++++ src/Middlewares/CORSHeadersOnResponse.php | 60 +++ .../EnvironmentHeadersOnResponse.php | 104 ++++ src/Middlewares/ForceSSLMiddleware.php | 22 + src/Middlewares/JSONResponseLinter.php | 66 +++ src/Router/Route.php | 427 ++++++++++++++++ src/Router/Router.php | 78 +++ src/Services/ConfigurationService.php | 226 ++------- src/Services/EnvironmentService.php | 36 ++ src/Services/SessionService.php | 10 + .../Extensions/ArrayUniqueTwigExtension.php | 34 ++ .../Extensions/ArrayValuesTwigExtension.php | 29 ++ .../FilterAlphanumericOnlyTwigExtension.php | 29 ++ src/Twig/Extensions/InflectionExtension.php | 27 + src/Twig/Extensions/TransformExtension.php | 74 +++ .../TransformExtensionException.php | 7 + 20 files changed, 1390 insertions(+), 507 deletions(-) create mode 100644 src/Middlewares/AccessRequirements.php create mode 100644 src/Middlewares/CORSHeadersOnResponse.php create mode 100644 src/Middlewares/EnvironmentHeadersOnResponse.php create mode 100644 src/Middlewares/ForceSSLMiddleware.php create mode 100644 src/Middlewares/JSONResponseLinter.php create mode 100644 src/Router/Route.php create mode 100644 src/Router/Router.php create mode 100644 src/Services/EnvironmentService.php create mode 100644 src/Services/SessionService.php create mode 100644 src/Twig/Extensions/ArrayUniqueTwigExtension.php create mode 100644 src/Twig/Extensions/ArrayValuesTwigExtension.php create mode 100644 src/Twig/Extensions/FilterAlphanumericOnlyTwigExtension.php create mode 100644 src/Twig/Extensions/InflectionExtension.php create mode 100644 src/Twig/Extensions/TransformExtension.php create mode 100644 src/Twig/Extensions/TransformExtensionException.php diff --git a/src/App.php b/src/App.php index 2483c70..30fe958 100644 --- a/src/App.php +++ b/src/App.php @@ -2,226 +2,144 @@ namespace Benzine; +use Benzine\ORM\Laminator; use Benzine\Services\ConfigurationService; +use Benzine\Services\EnvironmentService; +use Benzine\Services\SessionService; +use Benzine\Twig\Extensions; use Cache\Adapter\Apc\ApcCachePool; use Cache\Adapter\Apcu\ApcuCachePool; use Cache\Adapter\Chain\CachePoolChain; use Cache\Adapter\PHPArray\ArrayCachePool; +use Cache\Adapter\Redis\RedisCachePool; use DebugBar\Bridge\MonologCollector; use DebugBar\DebugBar; use DebugBar\StandardDebugBar; +use DI\Container; +use DI\ContainerBuilder; use Faker\Factory as FakerFactory; use Faker\Provider; +use Monolog\Formatter\LineFormatter; +use Monolog\Handler\ErrorLogHandler; +use Monolog\Logger; use Monolog\Processor\PsrLogMessageProcessor; +use Psr\Container\ContainerInterface; use Psr\Http\Message\ResponseInterface; -use SebastianBergmann\Diff\Differ; use Slim; - +use Slim\Factory\AppFactory; +use Symfony\Component\HttpFoundation\Session\Session; +use Twig; +use Twig\Loader\FilesystemLoader; class App { public const DEFAULT_TIMEZONE = 'Europe/London'; - /** @var App */ - public static $instance; + private static bool $isInitialised = false; + public static App $instance; - /** @var ConfigurationService */ - protected $configuration; - /** @var \Slim\App */ - protected $app; - /** @var Container\Container */ - protected $container; - /** @var Log\Logger */ - protected $logger; + protected EnvironmentService $environmentService; + protected ConfigurationService $configurationService; + protected \Slim\App $app; + protected Logger $logger; + protected bool $isSessionsEnabled = true; + protected array $routePaths = []; + protected array $viewPaths = []; + protected bool $interrogateControllersComplete = false; - protected $isSessionsEnabled = true; - - protected $containerAliases = [ - 'view' => Slim\Views\Twig::class, - 'DatabaseInstance' => DbConfig::class, - 'Differ' => Differ::class, - 'HttpClient' => \GuzzleHttp\Client::class, - 'Faker' => \Faker\Generator::class, - 'Environment' => EnvironmentService::class, - 'Redis' => Redis\Redis::class, - 'Monolog' => Log\Logger::class, - 'Gone\AppCore\Logger' => Log\Logger::class, - 'Cache' => CachePoolChain::class, - ]; - - protected $routePaths = []; - - protected $viewPaths = []; - - protected $optionsDefaults = []; - - protected $interrogateControllersComplete = false; - - public function __construct($options = []) + public function __construct() { - $this->configuration = new Configuration(); +# $this->environmentService = new EnvironmentService(); +# $this->configurationService = new ConfigurationService($this->environmentService); + // Configure Dependency Injector + $container = $this->setupContainer(); + AppFactory::setContainer($container); + + // Configure Router $this->routePaths = [ - $this->configuration->get(Configuration::KEY_APP_ROOT).'/src/Routes.php', - $this->configuration->get(Configuration::KEY_APP_ROOT).'/src/RoutesExtra.php', + APP_ROOT.'/src/Routes.php', + APP_ROOT.'/src/RoutesExtra.php', ]; - $options = array_merge($this->optionsDefaults, $options); + $this->setup($container); + + // Configure Slim + $this->app = AppFactory::create(); + $this->app->add(Slim\Views\TwigMiddleware::createFromContainer($this->app)); + $this->app->addRoutingMiddleware(); + $errorMiddleware = $this->app->addErrorMiddleware(true, true, true); - if (isset($options['config'])) { - if (is_string($options['config'])) { - $configRealpath = $options['config']; - if (!file_exists($configRealpath)) { - throw new Exceptions\BenzineConfigurationException("Cant find {$configRealpath}."); - } - $this->configuration->configureFromYaml($options['config']); - } - } - $this->setup(); } - public function setup(): void + protected function setup(ContainerInterface $container): void { - // Create Slim app - $this->app = new \Slim\App( - new Container\Container([ - 'settings' => [ - 'debug' => $this->getConfiguration()->get(Configuration::KEY_DEBUG_ENABLE), - 'displayErrorDetails' => $this->getConfiguration()->get(Configuration::KEY_DEBUG_ENABLE), - 'determineRouteBeforeAppMiddleware' => true, - ], - ]) - ); - - // Fetch DI Container - $this->container = $this->app->getContainer(); - // @todo remove this depenency on getting the container from Slim. - //$this->container = new Container\Container(); - - $this->populateContainerAliases($this->container); - - $this->setupDependencies(); - - $this->logger = $this->getContainer()->get(Log\Logger::class); - - if (file_exists($this->configuration->get(Configuration::KEY_APP_ROOT).'/src/AppContainer.php')) { - require $this->configuration->get(Configuration::KEY_APP_ROOT).'/src/AppContainer.php'; - } - if (file_exists($this->configuration->get(Configuration::KEY_APP_ROOT).'/src/AppContainerExtra.php')) { - require $this->configuration->get(Configuration::KEY_APP_ROOT).'/src/AppContainerExtra.php'; - } - - $this->addRoutePathsRecursively($this->configuration->get(Configuration::KEY_APP_ROOT).'/src/Routes'); + $this->logger = $container->get(Logger::class); if ('cli' != php_sapi_name() && $this->isSessionsEnabled) { - $session = $this->getContainer()->get(Session\Session::class); + $session = $container->get(SessionService::class); } - $this->setupMiddlewares(); - - if (class_exists(Controllers\Controller::class)) { - $this->addViewPath($this->getContainer()->get(Controllers\Controller::class)->getViewsPath()); - if (file_exists($this->configuration->get(Configuration::KEY_APP_ROOT).'/views/')) { - $this->addViewPath($this->configuration->get(Configuration::KEY_APP_ROOT).'/views/'); - } - if (file_exists($this->configuration->get(Configuration::KEY_APP_ROOT).'/src/Views')) { - $this->addViewPath($this->configuration->get(Configuration::KEY_APP_ROOT).'/src/Views'); - } - - $this->interrogateControllers(); - } + $this->setupMiddlewares($container); + $this->viewPaths[] = APP_ROOT.'/views/'; + $this->viewPaths[] = APP_ROOT.'/src/Views/'; + $this->interrogateControllers(); } - public function getConfiguration(): Configuration + public function setupContainer(): Container { - return $this->configuration; - } + $container = + (new ContainerBuilder()) + ->useAutowiring(true) + ->useAnnotations(true) + #->enableCompilation(APP_ROOT . "/cache") + #->writeProxiesToFile(true, APP_ROOT . "/cache/injection-proxies") + ->build(); - public function getLogger(): Log\Logger - { - return $this->logger; - } - - public function setupDependencies(): void - { - // add PSR-15 support shim - $this->container['callableResolver'] = function ($container) { - return new \Bnf\Slim3Psr15\CallableResolver($container); - }; - - // Register Twig View helper - $this->container[Slim\Views\Twig::class] = function ($c) { + $container->set(Slim\Views\Twig::class, function(ContainerInterface $container) { foreach ($this->viewPaths as $i => $viewLocation) { if (!file_exists($viewLocation) || !is_dir($viewLocation)) { unset($this->viewPaths[$i]); } } + $settings = ['cache' => 'cache/twig']; + $loader = new FilesystemLoader(); - $view = new \Slim\Views\Twig( - $this->viewPaths, - [ - 'cache' => false, - 'debug' => true, - ] - ); - - // Instantiate and add Slim specific extension - $view->addExtension( - new Slim\Views\TwigExtension( - $c['router'], - $c['request']->getUri() - ) - ); - - $view->addExtension(new Extensions\ArrayUniqueTwigExtension()); - $view->addExtension(new Extensions\FilterAlphanumericOnlyTwigExtension()); - - // Add coding string transform filters (ie: camel_case to StudlyCaps) - $view->addExtension(new Extensions\TransformExtension()); - - // Add pluralisation/depluralisation support with singularize/pluralize filters - $view->addExtension(new Extensions\InflectionExtension()); - - // Added Twig_Extension_Debug to enable twig dump() etc. - $view->addExtension(new \Twig_Extension_Debug()); - $view->addExtension(new \Twig_Extensions_Extension_Date()); - $view->addExtension(new \Twig_Extensions_Extension_Text()); - - $view->offsetSet('app_name', $this->configuration->get(Configuration::KEY_APP_NAME)); - $view->offsetSet('year', date('Y')); - - return $view; - }; - - $this->container[Configuration::class] = function (Slim\Container $c) { - $benzineYamlFile = '/app/.benzine.yml'; // @todo this shouldn't be hardcoded into /app - return Configuration::InitFromFile($benzineYamlFile); - }; - - $this->container[Db::class] = function (Slim\Container $c) { - return new Db($c->get(DatabaseConfig::class)); - }; - - $this->container[DatabaseConfig::class] = function (Slim\Container $c) { - /** @var Configuration $configuration */ - $configuration = $c->get(Configuration::class); - $dbConfig = new DatabaseConfig(); - foreach ($configuration->getArray('benzine/databases') as $dbName => $database) { - $dbConfig->set($dbName, [ - 'driver' => DatabaseConfig::DbTypeToDriver($database['type']), - 'hostname' => gethostbyname($database['host']), - 'port' => $database['port'] ?? DatabaseConfig::DbTypeToDefaultPort($database['type']), - 'username' => $database['username'], - 'password' => $database['password'], - 'database' => $database['database'], - 'charset' => $database['charset'] ?? 'UTF8', - ]); + foreach ($this->viewPaths as $path) { + $loader->addPath($path); } - return $dbConfig; - }; + $twig = new Slim\Views\Twig($loader, $settings); - $this->container[\Faker\Generator::class] = function (Slim\Container $c) { + $twig->addExtension(new Extensions\ArrayUniqueTwigExtension()); + $twig->addExtension(new Extensions\FilterAlphanumericOnlyTwigExtension()); + + // Add coding string transform filters (ie: camel_case to StudlyCaps) + $twig->addExtension(new Extensions\TransformExtension()); + + // Add pluralisation/depluralisation support with singularize/pluralize filters + $twig->addExtension(new Extensions\InflectionExtension()); + + // Added Twig_Extension_Debug to enable twig dump() etc. + $twig->addExtension(new Twig\Extension\DebugExtension()); + + $twig->offsetSet('app_name', APP_NAME); + $twig->offsetSet('year', date('Y')); + + return $twig; + }); + $container->set('view', function(ContainerInterface $container){ + return $container->get(Slim\Views\Twig::class); + }); + + $container->set(EnvironmentService::class, function(ContainerInterface $container){ + return new EnvironmentService(); + }); + + $container->set(ConfigurationService::class, function(ContainerInterface $container){ + return new ConfigurationService($container->get(EnvironmentService::class)); + }); + $container->set(\Faker\Generator::class, function(ContainerInterface $c) { $faker = FakerFactory::create(); $faker->addProvider(new Provider\Base($faker)); $faker->addProvider(new Provider\DateTime($faker)); @@ -234,48 +152,8 @@ class App $faker->addProvider(new Provider\en_US\Company($faker)); return $faker; - }; - - $this->container[\GuzzleHttp\Client::class] = function (Slim\Container $c) { - return new \GuzzleHttp\Client([ - // You can set any number of default request options. - 'timeout' => 2.0, - ]); - }; - - $this->container[Services\EnvironmentService::class] = function (Slim\Container $c) { - return new Services\EnvironmentService(); - }; - - $this->container[Predis::class] = function (Slim\Container $c) { - /** @var EnvironmentService $environmentService */ - $environmentService = $c->get(EnvironmentService::class); - if ($environmentService->isSet('REDIS_HOST')) { - $redisMasterHosts = explode(',', $environmentService->get('REDIS_HOST')); - } - if ($environmentService->isSet('REDIS_HOST_MASTER')) { - $redisMasterHosts = explode(',', $environmentService->get('REDIS_HOST_MASTER')); - } - if ($environmentService->isSet('REDIS_HOST_SLAVE')) { - $redisSlaveHosts = explode(',', $environmentService->get('REDIS_HOST_SLAVE')); - } - - $options = []; - - $options['profile'] = function ($options) { - $profile = $options->getDefault('profile'); - $profile->defineCommand('setifhigher', SetIfHigherLuaScript::class); - - return $profile; - }; - - return new Predis( - $redisMasterHosts[0], - $options - ); - }; - - $this->container[CachePoolChain::class] = function (Slim\Container $c) { + }); + $container->set(CachePoolChain::class, function(ContainerInterface $c) { $caches = []; // If apc/apcu present, add it to the pool @@ -286,13 +164,14 @@ class App } // If Redis is configured, add it to the pool. - $caches[] = new PredisCachePool($c->get(Redis\Redis::class)); + $caches[] = new RedisCachePool($c->get(\Redis::class)); $caches[] = new ArrayCachePool(); return new CachePoolChain($caches); - }; + }); + + $container->set('MonologFormatter', function(ContainerInterface $c) { - $this->container['MonologFormatter'] = function (Slim\Container $c) { /** @var Services\EnvironmentService $environment */ $environment = $c->get(Services\EnvironmentService::class); @@ -301,55 +180,63 @@ class App // the default output format is "[%datetime%] %channel%.%level_name%: %message% %context% %extra%" $environment->get('MONOLOG_FORMAT', '[%datetime%] %channel%.%level_name%: %message% %context% %extra%')."\n", 'Y n j, g:i a' - ) - ; - }; + ); + }); - $this->container[\Monolog\Logger::class] = function (Slim\Container $c) { - /** @var Configuration $configuration */ - $configuration = $c->get(Configuration::class); - $appName = $configuration->get(Configuration::KEY_APP_NAME); + $container->set(Logger::class, function(ContainerInterface $c) { + /** @var ConfigurationService $configuration */ + $configuration = $c->get(ConfigurationService::class); - $monolog = new \Monolog\Logger($appName); - - $monolog->pushHandler(new \Monolog\Handler\ErrorLogHandler(), \Monolog\Logger::DEBUG); + $monolog = new Logger($configuration->get(ConfigurationService::KEY_APP_NAME)); + $monolog->pushHandler(new ErrorLogHandler(), Logger::DEBUG); $monolog->pushProcessor(new PsrLogMessageProcessor()); return $monolog; - }; + }); - $this->container[DebugBar::class] = function (Slim\Container $container) { + $container->set(DebugBar::class, function(ContainerInterface $container) { $debugBar = new StandardDebugBar(); /** @var Logger $logger */ - $logger = $container->get(Log\Logger::class); - /** @var \Monolog\Logger $monolog */ - $monolog = $logger->getMonolog(); - $debugBar->addCollector(new MonologCollector($monolog)); + $logger = $container->get(Logger::class); + $debugBar->addCollector(new MonologCollector($logger)); return $debugBar; - }; + }); - $this->container[\Middlewares\Debugbar::class] = function (Slim\Container $container) { + $container->set(\Middlewares\Debugbar::class, function(ContainerInterface $container) { $debugBar = $container->get(DebugBar::class); return new \Middlewares\Debugbar($debugBar); - }; + }); - $this->container[Session\Session::class] = function (Slim\Container $container) { - return Session\Session::start($container->get(Redis\Redis::class)); - }; + $container->set(\Redis::class, function(ContainerInterface $container){ + $environmentService = $container->get(EnvironmentService::class); - $this->container[Differ::class] = function (Slim\Container $container) { - return new Differ(); - }; + $redis = new \Redis(); + $redis->connect( + $environmentService->get('REDIS_HOST', 'redis'), + $environmentService->get('REDIS_PORT', 6379) + ); - $this->container[Profiler\Profiler::class] = function (Slim\Container $container) { - return new Profiler\Profiler($container->get(Log\Logger::class)); - }; + return $redis; + }); + + $container->set(SessionService::class, function(ContainerInterface $container){ + return new SessionService( + $container->get(\Redis::class) + ); + }); + + $container->set(Laminator::class, function(ContainerInterface $container){ + return new Laminator( + APP_ROOT, + $container->get(ConfigurationService::class) + ); + }); /** @var Services\EnvironmentService $environmentService */ - $environmentService = $this->getContainer()->get(Services\EnvironmentService::class); - if ($environmentService->isSet('TIMEZONE')) { + $environmentService = $container->get(Services\EnvironmentService::class); + if ($environmentService->has('TIMEZONE')) { date_default_timezone_set($environmentService->get('TIMEZONE')); } elseif (file_exists('/etc/timezone')) { date_default_timezone_set(trim(file_get_contents('/etc/timezone'))); @@ -357,23 +244,23 @@ class App date_default_timezone_set(self::DEFAULT_TIMEZONE); } - $debugBar = $this->getContainer()->get(DebugBar::class); + $debugBar = $container->get(DebugBar::class); + + return $container; } - public function setupMiddlewares(): void + public function setupMiddlewares(ContainerInterface $container): void { // Middlewares - //$this->app->add($this->container->get(Middleware\EnvironmentHeadersOnResponse::class)); - //#$this->app->add($this->container->get(\Middlewares\ContentType(["text/html", "application/json"]))); - $this->app->add($this->container->get(\Middlewares\Debugbar::class)); - //#$this->app->add($this->container->get(\Middlewares\Geolocation::class)); - //$this->app->add($this->container->get(\Middlewares\TrailingSlash::class)); - //$this->app->add($this->container->get(Middleware\JSONResponseLinter::class)); - //$this->app->add($this->container->get(\Middlewares\Whoops::class)); - //$this->app->add($this->container->get(\Middlewares\CssMinifier::class)); - //$this->app->add($this->container->get(\Middlewares\JsMinifier::class)); - //$this->app->add($this->container->get(\Middlewares\HtmlMinifier::class)); - //$this->app->add($this->container->get(\Middlewares\GzipEncoder::class)); + #$this->app->add($container->get(Middleware\EnvironmentHeadersOnResponse::class)); + #$this->app->add($container->get(\Middlewares\ContentLength::class)); + #$this->app->add($container->get(\Middlewares\Debugbar::class)); + #$this->app->add($container->get(\Middlewares\Geolocation::class)); + #$this->app->add($container->get(\Middlewares\TrailingSlash::class)); + #$this->app->add($container->get(Middleware\JSONResponseLinter::class)); + #$this->app->add($container->get(\Middlewares\Whoops::class)); + #$this->app->add($container->get(\Middlewares\Minifier::class)); + #$this->app->add($container->get(\Middlewares\GzipEncoder::class)); } /** @@ -383,32 +270,14 @@ class App */ public static function Instance(array $options = []) { - if (!self::$instance) { + if (!self::$isInitialised) { $calledClass = get_called_class(); self::$instance = new $calledClass($options); } - $expectedClass = self::$instance->getConfiguration()->get(Configuration::KEY_CLASS); - if (get_class(self::$instance) != $expectedClass) { - self::$instance = new $expectedClass($options); - } - return self::$instance; } - /** - * @return Container\Container - */ - public static function Container() - { - return self::Instance()->getContainer(); - } - - public function getContainer(): Container\Container - { - return $this->container; - } - public function getApp() { return $this->app; @@ -430,30 +299,6 @@ class App return $this; } - /** - * @param $directory - * - * @return int number of Paths added - */ - public function addRoutePathsRecursively($directory) - { - $count = 0; - if (file_exists($directory)) { - foreach (new \DirectoryIterator($directory) as $file) { - if (!$file->isDot()) { - if ($file->isFile() && 'php' == $file->getExtension()) { - $this->addRoutePath($file->getRealPath()); - ++$count; - } elseif ($file->isDir()) { - $count += $this->addRoutePathsRecursively($file->getRealPath()); - } - } - } - } - - return $count; - } - public function addViewPath($path) { if (file_exists($path)) { @@ -471,17 +316,6 @@ class App return $this; } - public function populateContainerAliases(&$container) - { - foreach ($this->containerAliases as $alias => $class) { - if ($alias != $class) { - $container[$alias] = function (Slim\Container $c) use ($class) { - return $c->get($class); - }; - } - } - } - public static function Log(int $level = Logger::DEBUG, $message) { return self::Instance() @@ -542,9 +376,9 @@ class App $environmentService->rebuildEnvironmentVariables(); } - public function runHttp(): ResponseInterface + public function runHttp(): void { - return $this->app->run(); + $this->app->run(); } protected function interrogateControllers() @@ -555,16 +389,16 @@ class App $this->interrogateControllersComplete = true; $controllerPaths = [ - $this->getConfiguration()->get(Configuration::KEY_APP_ROOT).'/src/Controllers', - $this->getConfiguration()->get(Configuration::KEY_APP_ROOT).'/vendor/benzine/benzine-controllers/src', + APP_ROOT . '/src/Controllers', ]; + foreach ($controllerPaths as $controllerPath) { //$this->logger->debug("Route Discovery - {$controllerPath}"); if (file_exists($controllerPath)) { foreach (new \DirectoryIterator($controllerPath) as $controllerFile) { if (!$controllerFile->isDot() && $controllerFile->isFile() && $controllerFile->isReadable()) { //$this->logger->debug(" > {$controllerFile->getPathname()}"); - $appClass = new \ReflectionClass($this->getConfiguration()->get(Configuration::KEY_CLASS)); + $appClass = new \ReflectionClass(get_called_class()); $expectedClasses = [ $appClass->getNamespaceName().'\\Controllers\\'.str_replace('.php', '', $controllerFile->getFilename()), '⌬\\Controllers\\'.str_replace('.php', '', $controllerFile->getFilename()), @@ -576,7 +410,7 @@ class App if (!$rc->isAbstract()) { foreach ($rc->getMethods() as $method) { /** @var \ReflectionMethod $method */ - if (1 == 1 || ResponseInterface::class == ($method->getReturnType() instanceof \ReflectionType ? $method->getReturnType()->getName() : null)) { + if (true || ResponseInterface::class == ($method->getReturnType() instanceof \ReflectionType ? $method->getReturnType()->getName() : null)) { $docBlock = $method->getDocComment(); foreach (explode("\n", $docBlock) as $docBlockRow) { if (false === stripos($docBlockRow, '@route')) { diff --git a/src/Exceptions/BenzineConfigurationException.php b/src/Exceptions/BenzineConfigurationException.php index 98dc0dd..3cfff5f 100644 --- a/src/Exceptions/BenzineConfigurationException.php +++ b/src/Exceptions/BenzineConfigurationException.php @@ -1,7 +1,7 @@ hasSession = true; + } + } + + public function __invoke(Request $request, Response $response, $next) + { + if ($this->hasSession) { + /** @var \Slim\Route $route */ + $route = $request->getAttribute('route'); + if ($route) { + $requirements = $this->getRequirementsFromRoute($route); + $var = $this->getVarFromRoute($route); + if (!empty($requirements)) { + if ($var) { + foreach ($requirements as $requirement) { + if (!$this->testRequirement($requirement, $var)) { + return $this->rejectRequest($request, $response, $requirement); + } + } + } else { + return $this->rejectRequest($request, $response, 'NO_VAR'); + } + } + } + } + + return $next($request, $response); + } + + public static function Factory(): AccessRequirements + { + return new self(); + } + + public function setDefaultRequirements($requirements = []): AccessRequirements + { + if (is_array($requirements)) { + $requirements = $this->cleanRequirementsArray($requirements); + $requirements = implode(',', $requirements); + } + $this->defaultRequirements = $requirements; + + return $this; + } + + public function setDefaultVar(string $var): AccessRequirements + { + $this->defaultVar = $var; + + return $this; + } + + /** + * @param array|string $requirements + * @param int $status + * @param string $body + * + * @return AccessRequirements + */ + public function mapRejection($requirements, int $status, string $body): AccessRequirements + { + if (!is_array($requirements)) { + $requirements = [$requirements]; + } + + foreach ($requirements as $requirement) { + $this->rejectMap[strtoupper($requirement)] = [ + 'status' => $status, + 'body' => $body, + ]; + } + + return $this; + } + + private function getRejection(string $requirement): array + { + return $this->rejectMap[strtoupper($requirement)] ?? []; + } + + private function cleanRequirementsArray($requirements) + { + $requirements = array_map('trim', $requirements); + $requirements = array_unique($requirements); + + return array_filter($requirements); + } + + private function rejectRequest(Request $request, Response $response, string $requirement) + { + $rejection = $this->getRejection($requirement); + $status = $rejection['status'] ?? $this->defaultRejectStatus; + $body = $rejection['body'] ?? $this->defaultRejectBody; + if ($status >= 300 && $status < 400) { + return $response->withRedirect($body); + } + + return $response->withStatus($status)->write($body); + } + + /** + * @param Route $route + * + * @return string[] + */ + private function getRequirementsFromRoute(Route $route): array + { + $requirements = $route->getArgument('_accessRequirements') ?? $this->defaultRequirements; + $requirements = explode(',', $requirements); + + $plus = array_search('+', $requirements, true); + if (false !== $plus) { + unset($requirements[$plus]); + $requirements = array_merge($this->defaultRequirements, $requirements); + } + + return $this->cleanRequirementsArray($requirements); + } + + /** + * @param Route $route + * + * @return mixed + */ + private function getVarFromRoute(Route $route) + { + $varname = $route->getArgument('_accessVar') ?? $this->defaultVar; + + return \⌬\Session\Session::get($varname); + } + + /** + * @param string $requirement + * @param $var + * + * @return bool + */ + private function testRequirement(string $requirement, $var): bool + { + $_requirement = $requirement; + $not = false; + if (0 === strpos($_requirement, '!')) { + $_requirement = ltrim($_requirement, '!'); + $not = true; + } + $methodName = 'is'.ucfirst($_requirement); + $result = $var->{$methodName}(); + if ($not) { + $result = !$result; + } + + return $result; + } +} diff --git a/src/Middlewares/CORSHeadersOnResponse.php b/src/Middlewares/CORSHeadersOnResponse.php new file mode 100644 index 0000000..430d1a1 --- /dev/null +++ b/src/Middlewares/CORSHeadersOnResponse.php @@ -0,0 +1,60 @@ +domainRegexs = $domainRegexs; + } + + public function __invoke(Request $request, Response $response, $next) + { + /** @var Response $response */ + $response = $next($request, $response); + if (!empty($request->getHeader('HTTP_ORIGIN')[0])) { + $origin = $request->getHeader('HTTP_ORIGIN')[0]; + $pass = false; + foreach ($this->domainRegexs as $regex) { + if (preg_match($regex, $origin)) { + $pass = true; + + break; + } + } + if ($pass) { + return $response + ->withHeader('Access-Control-Allow-Origin', $origin) + ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization, Ticket') + ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS') + ; + } + } + + return $response; + } +} diff --git a/src/Middlewares/EnvironmentHeadersOnResponse.php b/src/Middlewares/EnvironmentHeadersOnResponse.php new file mode 100644 index 0000000..d22dc77 --- /dev/null +++ b/src/Middlewares/EnvironmentHeadersOnResponse.php @@ -0,0 +1,104 @@ +configuration = $configuration; + $this->profiler = $profiler; + } + + public function __invoke(Request $request, Response $response, $next) + { + /** @var Response $response */ + $response = $next($request, $response); + if (isset($response->getHeader('Content-Type')[0]) + and false !== stripos($response->getHeader('Content-Type')[0], 'application/json') + ) { + $body = $response->getBody(); + $body->rewind(); + + $json = json_decode($body->getContents(), true); + + $gitVersion = null; + if (file_exists($this->configuration->get(Configuration\Configuration::KEY_APP_ROOT).'/version.txt')) { + $gitVersion = trim(file_get_contents($this->configuration->get(Configuration\Configuration::KEY_APP_ROOT).'/version.txt')); + $gitVersion = explode(' ', $gitVersion, 2); + $gitVersion = reset($gitVersion); + } + + $json['Extra'] = array_filter([ + '_Warning' => 'Do not depend on any variables inside this block - This is for debug only!', + 'Hostname' => gethostname(), + 'DebugEnabled' => defined('DEBUG') && DEBUG ? 'Yes' : 'No', + 'GitVersion' => defined('DEBUG') && DEBUG ? $gitVersion : null, + 'Time' => defined('DEBUG') && DEBUG ? [ + 'TimeZone' => date_default_timezone_get(), + 'CurrentTime' => [ + 'Human' => date('Y-m-d H:i:s'), + 'Epoch' => time(), + ], + 'Exec' => number_format(microtime(true) - $this->configuration->get(Configuration\Configuration::KEY_APP_START), 4).' sec', + ] : null, + 'Memory' => defined('DEBUG') && DEBUG ? [ + 'Used' => number_format(memory_get_usage(false) / 1024 / 1024, 2).'MB', + 'Allocated' => number_format(memory_get_usage(true) / 1024 / 1024, 2).'MB', + 'Limit' => ini_get('memory_limit'), + ] : null, + 'SQL' => defined('DEBUG') && DEBUG ? $this->profiler->getQueriesArray() : null, + 'API' => defined('DEBUG') && DEBUG && class_exists('\Gone\SDK\Common\Profiler') ? \Gone\SDK\Common\Profiler::debugArray() : null, + ]); + + if (isset($json['Status'])) { + if ('okay' != strtolower($json['Status'])) { + $response = $response->withStatus(400); + } else { + $response = $response->withStatus(200); + } + } + + if (($request->hasHeader('Content-type') && false !== stripos($request->getHeader('Content-type')[0], 'application/json')) || + ($request->hasHeader('Accept') && false !== stripos($request->getHeader('Accept')[0], 'application/json')) || + false === $this->apiExplorerEnabled + ) { + $response = $response->withJson($json, null, JSON_PRETTY_PRINT); + } else { + /** @var Twig $twig */ + $twig = ⌬::Container()->get('view'); + $response->getBody()->rewind(); + $response = $twig->render($response, 'api/explorer.html.twig', [ + 'page_name' => 'API Explorer', + 'json' => $json, + 'json_pretty_printed_rows' => explode("\n", json_encode($json, JSON_PRETTY_PRINT)), + 'inline_css' => $this->renderInlineCss([ + $this->configuration->get(Configuration\Configuration::KEY_APP_ROOT).'/vendor/benzine/benzine-http-assets/css/reset.css', + $this->configuration->get(Configuration\Configuration::KEY_APP_ROOT).'/vendor/benzine/benzine-http-assets/css/api-explorer.css', + ]), + ]); + $response = $response->withHeader('Content-type', 'text/html'); + } + } + + return $response; + } +} diff --git a/src/Middlewares/ForceSSLMiddleware.php b/src/Middlewares/ForceSSLMiddleware.php new file mode 100644 index 0000000..15bfbf2 --- /dev/null +++ b/src/Middlewares/ForceSSLMiddleware.php @@ -0,0 +1,22 @@ +getServerParam('SERVER_PORT') + && 'https' != $request->getServerParam('HTTP_X_FORWARDED_PROTO') + && 'yes' == strtolower($request->getServerParam('FORCE_HTTPS')) + ) { + return $response->withRedirect('https://'.$request->getServerParam('HTTP_HOST').'/'.ltrim($request->getServerParam('REQUEST_URI'), '/')); + } + + return $next($request, $response); + } +} diff --git a/src/Middlewares/JSONResponseLinter.php b/src/Middlewares/JSONResponseLinter.php new file mode 100644 index 0000000..d6b4d4f --- /dev/null +++ b/src/Middlewares/JSONResponseLinter.php @@ -0,0 +1,66 @@ +hasHeader('CompactJson'); + + if (!$jsonCompact) { + $encodingOptions = JSON_PRETTY_PRINT; + } + + try { + $response = $next($request, $response); + $jsonMode = (isset($response->getHeader('Content-Type')[0]) and false !== stripos($response->getHeader('Content-Type')[0], 'application/json')); + if ($jsonMode) { + $body = $response->getBody(); + $body->rewind(); + $json = json_decode($body->getContents(), true); + if ($jsonCompact) { + if (isset($json['Extra'])) { + unset($json['Extra']); + } + } + if (isset($json['Status'])) { + $json['Status'] = ucfirst(strtolower($json['Status'])); + } + $response = $response->withJson($json, null, $encodingOptions); + } + + return $response; + } catch (\Exception $exception) { + $trace = explode("\n", $exception->getTraceAsString()); + array_walk($trace, function (&$elem) { + $pieces = explode(' ', $elem, 2); + $elem = $pieces[1]; + $highlightLocations = [ + '/app/src/', + '/app/tests/', + ]; + foreach ($highlightLocations as $highlightLocation) { + if (substr($elem, 0, strlen($highlightLocation)) == $highlightLocation) { + $elem = "*** {$elem}"; + } + } + }); + + return $response->withJson( + [ + 'Status' => 'Fail', + 'Exception' => get_class($exception), + 'Reason' => $exception->getMessage(), + 'Trace' => $trace, + ], + 500, + $encodingOptions + ); + } + } +} diff --git a/src/Router/Route.php b/src/Router/Route.php new file mode 100644 index 0000000..f863292 --- /dev/null +++ b/src/Router/Route.php @@ -0,0 +1,427 @@ + self::ACCESS_PUBLIC, + ]; + + public static function Factory(): Route + { + return new Route(); + } + + public function getCallbackProperties(): array + { + return $this->callbackProperties; + } + + public function setCallbackProperties(array $callbackProperties): Route + { + $this->callbackProperties = []; + foreach ($callbackProperties as $name => $property) { + $this->populateCallbackProperty($name, $property); + } + + return $this; + } + + /** + * @param $name + * @param null $default + * + * @return $this + */ + public function addCallbackProperty(string $name, bool $mandatory = false, $default = null) + { + return $this->populateCallbackProperty($name, [ + 'isMandatory' => $mandatory, + 'default' => $default, + ]); + } + + public function populateCallbackProperty(string $name, array $property) + { + $property['name'] = $name; + $this->callbackProperties[$name] = array_merge( + [ + 'in' => null, + 'description' => null, + 'isMandatory' => null, + 'default' => null, + 'type' => null, + 'examples' => [], + ], + $property + ); + + return $this; + } + + public function getSDKTemplate(): string + { + return $this->SDKTemplate; + } + + public function setSDKTemplate(string $SDKTemplate): Route + { + $this->SDKTemplate = $SDKTemplate; + + return $this; + } + + public function getUniqueIdentifier() + { + return implode( + '::', + [ + $this->getRouterPattern(), + $this->getHttpMethod(), + "Weight={$this->getWeight()}", + ] + ); + } + + /** + * @return mixed + */ + public function getHttpMethod() + { + return $this->httpMethod; + } + + /** + * @param mixed $httpMethod + */ + public function setHttpMethod($httpMethod): Route + { + $this->httpMethod = $httpMethod; + + return $this; + } + + public function getWeight(): int + { + return $this->weight; + } + + public function setWeight(int $weight): Route + { + $this->weight = $weight; + + return $this; + } + + /** + * @return mixed + */ + public function getRouterPattern() + { + return $this->routerPattern; + } + + /** + * @param mixed $routerPattern + */ + public function setRouterPattern($routerPattern): Route + { + $this->routerPattern = $routerPattern; + + return $this; + } + + public function setExampleEntityFindFunction(callable $finderFunction): Route + { + $this->exampleEntityFinderFunction = $finderFunction; + + return $this; + } + + /** + * @return mixed + */ + public function getExampleEntity() + { + if (!$this->exampleEntity && $this->exampleEntityFinderFunction) { + $function = $this->exampleEntityFinderFunction; + $this->exampleEntity = $function(); + } + + return $this->exampleEntity; + } + + /** + * @param mixed $exampleEntity + */ + public function setExampleEntity($exampleEntity): Route + { + $this->exampleEntity = $exampleEntity; + + return $this; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + */ + public function setName($name): Route + { + $this->name = $name; + + return $this; + } + + /** + * @return mixed + */ + public function getSDKClass() + { + return $this->SDKClass; + } + + /** + * @param mixed $SDKClass + */ + public function setSDKClass($SDKClass): Route + { + $this->SDKClass = $SDKClass; + + return $this; + } + + /** + * @return mixed + */ + public function getSDKFunction() + { + return $this->SDKFunction; + } + + /** + * @param mixed $function + */ + public function setSDKFunction($function): Route + { + $this->SDKFunction = $function; + + return $this; + } + + /** + * @return mixed + */ + public function getSingular() + { + return $this->singular; + } + + /** + * @param mixed $singular + */ + public function setSingular($singular): Route + { + $this->singular = $singular; + + return $this; + } + + /** + * @return mixed + */ + public function getPlural() + { + return $this->plural; + } + + /** + * @param mixed $plural + */ + public function setPlural($plural): Route + { + $this->plural = $plural; + + return $this; + } + + public function getPropertyData() + { + return $this->propertyData; + } + + /** + * @return mixed + */ + public function getProperties() + { + return $this->properties; + } + + /** + * @param mixed $properties + */ + public function setProperties($properties): Route + { + $this->properties = []; + foreach ($properties as $name => $type) { + if (is_numeric($name)) { + $this->properties[] = $type; + } else { + $this->properties[] = $name; + $this->propertyData[$name]['type'] = $type; + } + } + + return $this; + } + + /** + * @return mixed + */ + public function getPropertyOptions() + { + return $this->propertyOptions; + } + + /** + * @param mixed $propertyOptions + * + * @return Route + */ + public function setPropertyOptions($propertyOptions) + { + $this->propertyOptions = []; + foreach ($propertyOptions as $name => $options) { + $this->propertyOptions[$name] = $options; + $this->propertyData[$name]['options'] = $options; + } + + return $this; + } + + public function populateRoute(App $app): App + { + //echo "Populating: {$this->getHttpMethod()} {$this->getRouterPattern()}\n"; + $mapping = $app->map( + [$this->getHttpMethod()], + $this->getRouterPattern(), + $this->getCallback() + ); + + $mapping->setName($this->getName() ? $this->getName() : 'Unnamed Route'); + + foreach ($this->arguments as $key => $value) { + $mapping->setArgument($key, $value); + } + + return $app; + } + + /** + * @return mixed + */ + public function getCallback() + { + return $this->callback; + } + + /** + * @param mixed $callback + */ + public function setCallback($callback): Route + { + $this->callback = $callback; + + return $this; + } + + /** + * @return mixed + */ + public function getHttpEndpoint() + { + return $this->httpEndpoint; + } + + /** + * @param mixed $httpEndpoint + */ + public function setHttpEndpoint($httpEndpoint): Route + { + $this->httpEndpoint = $httpEndpoint; + + return $this; + } + + /** + * @return string + */ + public function getAccess() + { + return $this->getArgument(self::ARGUMENT_ACCESS); + } + + /** + * @param string $access + */ + public function setAccess($access = self::ACCESS_PUBLIC): Route + { + return $this->setArgument(self::ARGUMENT_ACCESS, $access); + } + + public function getArgument(string $argument) + { + $argument = $this->prefixArgumentKey($argument); + + return $this->arguments[$argument] ?? null; + } + + public function setArgument(string $argument, $value): Route + { + $argument = $this->prefixArgumentKey($argument); + $this->arguments[$argument] = $value; + + return $this; + } + + private function prefixArgumentKey(string $key) + { + if (0 !== strpos($key, '_')) { + $key = "_{$key}"; + } + + return $key; + } +} diff --git a/src/Router/Router.php b/src/Router/Router.php new file mode 100644 index 0000000..c629bd5 --- /dev/null +++ b/src/Router/Router.php @@ -0,0 +1,78 @@ +routes) && count($this->routes) > 0) { + uasort($this->routes, function (Route $a, Route $b) { + return $a->getWeight() > $b->getWeight(); + }); + + foreach ($this->routes as $index => $route) { + if (!isset($allocatedRoutes[$route->getHttpMethod().$route->getRouterPattern()])) { + $allocatedRoutes[$route->getHttpMethod().$route->getRouterPattern()] = true; + } else { + unset($this->routes[$index]); + } + } + } + + return $this; + } + + public function populateRoutes(App $app) + { + $this->weighRoutes(); + if (count($this->routes) > 0) { + foreach ($this->routes as $route) { + $app = $route->populateRoute($app); + } + } + + return $app; + } + + public function addRoute(Route $route) + { + $this->routes[$route->getUniqueIdentifier()] = $route; + + return $this; + } + + /** + * @return Route[] + */ + public function getRoutes() + { + ksort($this->routes); + + return $this->routes; + } +} diff --git a/src/Services/ConfigurationService.php b/src/Services/ConfigurationService.php index 4e5aea3..fb36e7c 100644 --- a/src/Services/ConfigurationService.php +++ b/src/Services/ConfigurationService.php @@ -2,75 +2,60 @@ namespace Benzine\Services; +use Symfony\Component\Yaml\Yaml; + class ConfigurationService { - public function __construct(array $config = null) - { - $this->config = $config; + public const KEY_APP_NAME = 'application/name'; + public const KEY_APP_ROOT = 'application/root'; + public const KEY_CLASS = 'application/class'; + public const KEY_DEBUG_ENABLE = 'application/debug'; + public const KEY_DEFAULT_ACCESS = 'application/default_access'; + public const KEY_TIMEZONE = 'application/timezone'; + public const KEY_LOCALE = 'application/locale'; + public const KEY_SESSION_ENABLED = 'application/session_enabled'; + public const KEY_LOG_FORMAT_DATE = 'logging/format_date'; + public const KEY_LOG_FORMAT_MESSAGE = 'logging/format_message'; - if (null === $this->config) { - $this->config = [ - 'benzine' => [ - 'application' => [ - 'name' => 'Benzine App', - 'debug' => false, - 'default_access' => 'public', - 'timezone' => 'UTC', - 'locale' => 'en_US.UTF-8', - 'session_enabled' => true, - 'time_start' => microtime(true), - ], - 'logging' => [ - 'format_date' => 'Y-m-d H:i:s', - 'format_message' => '%datetime% > %level_name% > %message% %context% %extra%', - ], - ], - ]; - $this->findConfigs(); + protected EnvironmentService $environmentService; + protected string $appRoot; + protected array $config; + + public function __construct(EnvironmentService $environmentService) + { + $this->environmentService = $environmentService; + $this->findConfig(); + $this->setupDefines(); + } + + protected function setupDefines() : void { + define("APP_ROOT", $this->appRoot); + define("APP_NAME", $this->get('application/name')); + } + + /** + * Locate .benzine.yml + * @param string|null $path + */ + protected function findConfig(string $path = null) : bool { + if(!$path){ + $path = getcwd(); + //$path = dirname($this->environmentService->get('SCRIPT_FILENAME')); + } + if(!file_exists($path . "/.benzine.yml")){ + $currentDirElem = explode(DIRECTORY_SEPARATOR, $path); + array_pop($currentDirElem); + $parentPath = implode(DIRECTORY_SEPARATOR, $currentDirElem); + return $this->findConfig($parentPath); } - $this->handleEnvvars(); + $this->config = Yaml::parseFile($path . "/.benzine.yml"); + $this->appRoot = $path; + + return true; } - public function __toArray(): array - { - return $this->arrayFlatten($this->config); - } - - public static function Init(array $config): Configuration - { - return new Configuration($config); - } - - public static function InitFromFile(string $filePath): Configuration - { - return self::Init(Yaml::parseFile($filePath)); - } - - public function dump(): array - { - return $this->__toArray(); - } - - public function set(string $key, $value): self - { - $scope = &$this->config; - foreach (explode('/', strtolower($key)) as $keyBit) { - $scope = &$scope[$keyBit]; - } - $scope = $value; - - return $this; - } - - public function has(string $key): bool - { - return false != $this->get($key, false) - || (is_array($this->getArray($key)) && count($this->getArray($key)) >= 1); - } - - public function get(string $key, $defaultValue = null) - { + public function get(string $key, string $defaultValue = null){ $scope = $this->config; foreach (explode('/', strtolower($key)) as $keyBit) { $scope = &$scope[$keyBit]; @@ -86,121 +71,4 @@ class ConfigurationService return trim($scope); } - - public function getArray(string $key) - { - $scope = $this->config; - foreach (explode('/', strtolower($key)) as $keyBit) { - $scope = &$scope[$keyBit]; - } - - return $scope; - } - - public function defineAs(string $defineTarget, string $key): self - { - if (!defined($defineTarget)) { - define($defineTarget, $this->get($key)); - } - - return $this; - } - - public function handleEnvvars(): void - { - $envvars = array_merge($_SERVER, $_ENV); - - array_walk_recursive($this->config, function (&$value, $key) use ($envvars) { - foreach ($envvars as $envvar => $envvarValue) { - if (is_array($envvarValue)) { - continue; - } - $value = str_replace("\${$envvar}", $envvarValue, $value); - } - }); - } - - public function findConfigs($currentDir = null): void - { - if (null == $currentDir) { - $currentDir = dirname(realpath($_SERVER['SCRIPT_FILENAME'])); - } - - if (file_exists($currentDir.'/.benzine.yml')) { - $this->configureFromYaml($currentDir.'/.benzine.yml'); - - return; - } - - $currentDirElem = explode(DIRECTORY_SEPARATOR, $currentDir); - array_pop($currentDirElem); - $this->findConfigs(implode(DIRECTORY_SEPARATOR, $currentDirElem)); - } - - public function configureFromYaml(string $file): self - { - $this->set('benzine/application/root', realpath(dirname($file))); - //\Kint::dump($this->config, Yaml::parseFile($file)); - $this->config = array_merge_recursive($this->config, Yaml::parseFile($file)); - //$this->ksortRecursive($this->config); - $this->process(); - - return $this; - } - - public function process(): void - { - error_reporting(E_ALL); - ini_set('display_errors', true == $this->get('benzine/application/debug')); - ini_set('display_startup_errors', true == $this->get('benzine/application/debug')); - date_default_timezone_set($this->get('benzine/application/timezone', 'UTC')); - setlocale(LC_ALL, $this->get('benzine/application/locale')); - - $this - ->defineAs('DEBUG', self::KEY_DEBUG_ENABLE) - ->defineAs('APP_START', self::KEY_APP_START) - ->defineAs('APP_ROOT', self::KEY_APP_ROOT) - ->defineAs('DEFAULT_ROUTE_ACCESS_MODE', self::KEY_DEFAULT_ACCESS) - ; - } - - public function getDatabases(): DatabaseConfig - { - $dbConfig = new DatabaseConfig(); - foreach ($this->config['benzine']['databases'] as $name => $config) { - $dbConfig->set($name, [ - 'driver' => DatabaseConfig::DbTypeToDriver($config['type']), - 'hostname' => $config['host'], - 'port' => $config['port'] ?? DatabaseConfig::DbTypeToDefaultPort($config['type']), - 'username' => $config['username'] ?? null, - 'password' => $config['password'] ?? null, - 'database' => $config['database'], - ]); - } - - return $dbConfig; - } - - public function getNamespace(): string - { - return $this->config['benzine']['application']['namespace'] - ?? $this->config['benzine']['application']['name']; - } - - public function getAppName(): string - { - return $this->config['benzine']['application']['name']; - } - - public function getAppContainer(): string - { - return $this->config['benzine']['application']['app_container_class'] - ?? ⌬\⌬::class; - } - - public function getLaminatorTemplates(): array - { - return $this->config['benzine']['laminator']['templates'] - ?? ['Models', 'Services', 'Controllers', 'Endpoints', 'Routes']; - } } \ No newline at end of file diff --git a/src/Services/EnvironmentService.php b/src/Services/EnvironmentService.php new file mode 100644 index 0000000..bc72632 --- /dev/null +++ b/src/Services/EnvironmentService.php @@ -0,0 +1,36 @@ +environmentVariables = array_merge($_SERVER, $_ENV); + ksort($this->environmentVariables); + } + + public function has(string $key) : bool { + return $this->get($key) !== null; + } + + public function get(string $key, string $default = null){ + if(isset($this->environmentVariables[$key])){ + return $this->environmentVariables[$key]; + } + return $default; + } + + public function set(string $key, string $value) : self { + $this->environmentVariables[$key] = $value; + ksort($this->environmentVariables); + return $this; + } + + public function delete(string $key) : self { + unset($this->environmentVariables[$key]); + return $this; + } +} \ No newline at end of file diff --git a/src/Services/SessionService.php b/src/Services/SessionService.php new file mode 100644 index 0000000..392cb5f --- /dev/null +++ b/src/Services/SessionService.php @@ -0,0 +1,10 @@ +transformers as $fromTransformer) { + foreach ($this->transformers as $toTransformer) { + $name = 'transform_'.strtolower($fromTransformer).'_to_'.strtolower($toTransformer); + $context = $this; + $filters[$name] = + new \Twig_SimpleFilter($name, function ($word) use ($context, $fromTransformer, $toTransformer) { + return $context->transform($word, $fromTransformer, $toTransformer); + }); + } + } + + return $filters; + } + + public function transform($string, $from, $to) + { + $fromTransformer = $this->getTransformer($from); + $toTransformer = $this->getTransformer($to); + + $transformer = new CaseTransformer($fromTransformer, $toTransformer); + + return $transformer->transform($string); + } + + public function getName() + { + return 'transform_extension'; + } + + protected function getTransformer($name) + { + switch (strtolower($name)) { + case 'camel': + case 'camelcase': + return new Format\CamelCase(); + case 'screaming': + case 'screamingsnake': + case 'screamingsnakecase': + return new Format\ScreamingSnakeCase(); + case 'snake': + case 'snakecase': + return new Format\SnakeCase(); + case 'spinal': + case 'spinalcase': + return new Format\SpinalCase(); + case 'studly': + return new Format\StudlyCaps(); + default: + throw new TransformExtensionException("Unknown transformer: \"{$name}\"."); + } + } +} diff --git a/src/Twig/Extensions/TransformExtensionException.php b/src/Twig/Extensions/TransformExtensionException.php new file mode 100644 index 0000000..e43e344 --- /dev/null +++ b/src/Twig/Extensions/TransformExtensionException.php @@ -0,0 +1,7 @@ +