Hack n' slash rework.

This commit is contained in:
Greyscale 2020-06-12 15:57:36 +02:00
parent 73a133097d
commit be507e0d4c
20 changed files with 1390 additions and 507 deletions

View file

@ -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')) {

View file

@ -1,7 +1,7 @@
<?php
namespace \Exceptions;
namespace Benzine\Exceptions;
class BenzineConfigurationException extends BenzineException
class BenzineConfigurationException extends Exception
{
}

View file

@ -1,7 +1,7 @@
<?php
namespace \Exceptions;
namespace Benzine\Exceptions;
class BenzineException extends \Exception
class Exception extends \Exception
{
}

View file

@ -1,7 +1,7 @@
<?php
namespace \Exceptions;
namespace Benzine\Exceptions;
class DbConfigException extends BenzineException
class DbConfigException extends Exception
{
}

View file

@ -0,0 +1,178 @@
<?php
namespace Benzine\Middleware;
use Faker\Factory;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Route;
class AccessRequirements
{
private $defaultRequirements = '';
private $defaultVar = 'User';
private $defaultRejectStatus = 401;
private $defaultRejectBody = 'Unauthorized';
private $rejectMap = [];
private $hasSession = false;
public function __construct()
{
if (class_exists(\⌬\Session\Session::class, false)) {
$this->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;
}
}

View file

@ -0,0 +1,60 @@
<?php
namespace Benzine\Middleware;
use Slim\Http\Request;
use Slim\Http\Response;
class CORSHeadersOnResponse
{
private $domainRegexs = '';
public function __construct(array $domainRegexs)
{
if (!is_array($domainRegexs)) {
if (is_string($domainRegexs)) {
$domainRegexs = [$domainRegexs];
} else {
$domainRegexs = null;
}
} else {
foreach ($domainRegexs as $regex) {
if (!is_string($regex)) {
$domainRegexs = null;
break;
}
}
}
if (empty($domainRegexs)) {
throw new \Exception('Invalid domainRegex for CORS Middleware. Expected string or array');
}
$this->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;
}
}

View file

@ -0,0 +1,104 @@
<?php
namespace Benzine\Middleware;
use Slim\Http\Request;
use Slim\Http\Response;
use Benzine\Configuration;
use Benzine\ORM\Profiler;
use Benzine\Traits\InlineCssTrait;
use Benzine\⌬;
class EnvironmentHeadersOnResponse
{
use InlineCssTrait;
protected $apiExplorerEnabled = true;
/** @var Configuration\Configuration */
protected $configuration;
/** @var Profiler\Profiler */
protected $profiler;
public function __construct(
Configuration\Configuration $configuration,
Profiler\Profiler $profiler
) {
$this->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;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Benzine\Middleware;
use Slim\Http\Request;
use Slim\Http\Response;
class ForceSSLMiddleware
{
public function __invoke(Request $request, Response $response, $next)
{
// @var Response $response
if ('80' == $request->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);
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Benzine\Middleware;
use Slim\Http\Request;
use Slim\Http\Response;
class JSONResponseLinter
{
public function __invoke(Request $request, Response $response, $next)
{
$encodingOptions = 0;
$jsonCompact = $request->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
);
}
}
}

427
src/Router/Route.php Normal file
View file

@ -0,0 +1,427 @@
<?php
namespace Benzine\Router;
use Slim\App;
class Route
{
public const ACCESS_PUBLIC = 'public';
public const ACCESS_PRIVATE = 'private';
public const ARGUMENT_ACCESS = '_access';
protected $name;
protected $callback;
protected $SDKClass;
protected $SDKFunction;
protected $SDKTemplate = 'callback';
protected $routerPattern;
protected $httpEndpoint;
protected $httpMethod = 'GET';
protected $weight = 0;
protected $singular;
protected $plural;
protected $properties;
protected $propertyData = [];
protected $propertyOptions;
protected $exampleEntity;
protected $exampleEntityFinderFunction;
protected $callbackProperties = [];
protected $arguments = [
self::ARGUMENT_ACCESS => 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;
}
}

78
src/Router/Router.php Normal file
View file

@ -0,0 +1,78 @@
<?php
namespace Benzine\Router;
use Slim\App;
use Monolog\Logger;
class Router
{
protected static $instance;
/** @var Route[] */
protected $routes = [];
/** @var Logger */
protected $logger;
/**
* @return Router
*/
public static function Instance()
{
if (!self::$instance instanceof Router) {
self::$instance = new Router();
}
return self::$instance;
}
public function weighRoutes(): Router
{
$allocatedRoutes = [];
if (is_array($this->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;
}
}

View file

@ -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'];
}
}

View file

@ -0,0 +1,36 @@
<?php
namespace Benzine\Services;
class EnvironmentService
{
private array $environmentVariables;
public function __construct()
{
$this->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;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace Benzine\Services;
class SessionService
{
public function __construct()
{
}
}

View file

@ -0,0 +1,34 @@
<?php
namespace Benzine\Twig\Extensions;
use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
class ArrayUniqueTwigExtension extends AbstractExtension
{
public function getName()
{
return 'ArrayUnique Twig Extension';
}
public function getFilters()
{
$filters = [];
$methods = ['unique'];
foreach ($methods as $method) {
$filters[$method] = new TwigFilter($method, [$this, $method]);
}
return $filters;
}
public function unique($array)
{
if (is_array($array)) {
return array_unique($array, SORT_REGULAR);
}
return $array;
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Benzine\Twig\Extensions;
use Twig\Extension\AbstractExtension;
class ArrayValuesTwigExtension extends AbstractExtension
{
public function getName()
{
return 'Array_Values Twig Extension';
}
public function getFilters()
{
$filters = [];
$methods = ['values'];
foreach ($methods as $method) {
$filters[$method] = new \Twig_Filter($method, [$this, $method]);
}
return $filters;
}
public function values($array)
{
return array_values($array);
}
}

View file

@ -0,0 +1,29 @@
<?php
namespace Benzine\Twig\Extensions;
use Twig\Extension\AbstractExtension;
class FilterAlphanumericOnlyTwigExtension extends AbstractExtension
{
public function getName()
{
return 'Filter Alphanumeric Only Twig Extension';
}
public function getFilters()
{
$filters = [];
$methods = ['filteralphaonly'];
foreach ($methods as $method) {
$filters[$method] = new \Twig_Filter($method, [$this, $method]);
}
return $filters;
}
public function filteralphaonly($string)
{
return preg_replace('/[^a-z0-9_]+/i', '', $string);
}
}

View file

@ -0,0 +1,27 @@
<?php
namespace Benzine\Twig\Extensions;
use Gone\Inflection\Inflect;
use Twig\Extension\AbstractExtension;
class InflectionExtension extends AbstractExtension
{
public function getFilters()
{
$filters = [];
$filters['pluralize'] = new \Twig_SimpleFilter('pluralize', function ($word) {
return Inflect::pluralize($word);
});
$filters['singularize'] = new \Twig_SimpleFilter('singularize', function ($word) {
return Inflect::singularize($word);
});
return $filters;
}
public function getName()
{
return 'inflection_extension';
}
}

View file

@ -0,0 +1,74 @@
<?php
namespace Benzine\Twig\Extensions;
use Benzine\Twig\Extensions\TransformExtensionException;
use Camel\CaseTransformer;
use Camel\Format;
use Twig\Extension\AbstractExtension;
class TransformExtension extends AbstractExtension
{
private $transformers = [
'Camel',
'ScreamingSnake',
'Snake',
'Spinal',
'Studly',
];
public function getFilters()
{
$filters = [];
foreach ($this->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}\".");
}
}
}

View file

@ -0,0 +1,7 @@
<?php
namespace Benzine\Twig\Extensions;
final class TransformExtensionException extends \Exception
{
}