- Work on Language Handler and extension.

- Added an URL extension which allows routes and URLs.
- Fixed some bug within ClassTemplate
This commit is contained in:
Dave M. 2020-01-23 15:28:05 -05:00
parent a64989af9f
commit ddc3ae704a
10 changed files with 267 additions and 82 deletions

View File

@ -19,11 +19,12 @@ class Builder
$replace = [
'%NAMESPACE%' => $context->namespace,
'%USE%' => $context->renderUses(),
'%USE%' => ( $uses = $context->renderUses() ) ? "use $uses;" : false,
'%CLASSNAME%' => $context->className,
'%EXTENDS%' => $context->extendFrom ? "extends " . static::TEMPLATE_CLASSNAME_PREFIX . static::generateClassUID($context->extendFrom) : '',
'%EXTENDS_TEMPLATE%' => $context->extendFrom,
'%CONTENT%' => $compiledSource,
'%FUNCTIONS%' => $context->functionStack ? $context->renderFunctions() : "",
'%PARENT_OUTPUT%' => $context->extendFrom ? "parent::output(\$variablesList);" : "",
];

View File

@ -8,29 +8,34 @@ class %CLASSNAME% %EXTENDS% {
public array $sectionList = [];
public array $variableList = [];
public array $variableList = [];
public ?object $thisProxy = null;
public \Picea\Picea $picea;
public static $context;
public function __construct(\Picea\Picea $picea, array $variablesList = [], ?object $thisProxy = null) {
$this->picea = $picea;
$this->variableList = $variablesList;
$this->thisProxy = $thisProxy;
$this->exportFunctions();
static::$context = $picea->context;
}
public function output(array $variablesList = []) : void
{
( function($___class__template, $___global_variables, $___variables, $___picea) {
extract($___global_variables); unset($___global_variables);
extract($___variables, \EXTR_OVERWRITE); unset($___variables);
public function output(array $variablesList = []) : void
{
( function($___class__template, $___global_variables, $___variables, $picea) {
extract($___global_variables);
extract($___variables, \EXTR_OVERWRITE);
?>%CONTENT%<?php
} )->call($this->thisProxy ?? new class(){}, $this, $this->variableList, $variablesList, $this->picea);
%PARENT_OUTPUT%
}
}
public function renderSection($name) : void
public function renderSection($name) : void
{
foreach([ 'prepend', 'default', 'append' ] as $item) {
usort($this->sectionList[$name][$item], fn($a, $b) => $a['order'] <=> $b['order']);
@ -44,8 +49,15 @@ class %CLASSNAME% %EXTENDS% {
}
}
}
public function exportFunctions() : void
{
static $caching = [];
%FUNCTIONS%
}
public function __invoke(array $variablesList = []) {
public function __invoke(array $variablesList = []) : string
{
ob_start();
$this->output($variablesList);
return ob_get_clean();

View File

@ -23,11 +23,11 @@ class Compiler
public function __construct(?LanguageRegistration $languageRegistration = null)
{
$this->languageRegistration = $languageRegistration;
$this->languageRegistration->registerAll($this);
}
public function compile(Compiler\Context $context) : string
{
$this->languageRegistration->registerAll($this);
$context->compiler = $this;
/**
@ -82,7 +82,7 @@ class Compiler
public function registerControlStructure(ControlStructure\ControlStructure $controlStructureObject) : self
{
foreach($controlStructureObject->tokens ?? (array) $controlStructureObject->token as $token) {
foreach($controlStructureObject->tokens ?? (array) ( $controlStructureObject->token ?? [] ) as $token) {
$this->tagList[$token] = $controlStructureObject;
}
@ -91,7 +91,7 @@ class Compiler
public function registerExtension(Extension\Extension $extension) : self
{
foreach($extension->tokens ?? (array) $extension->token as $token) {
foreach($extension->tokens ?? (array) ( $extension->token ?? [] ) as $token) {
$this->extensionList[$token] = $extension;
}
@ -108,4 +108,11 @@ class Compiler
return $this->extensionList[$name];
}
public function exportFunctions() : void
{
static $caching = [];
eval( $this->context->renderFunctions() );
}
}

View File

@ -22,6 +22,8 @@ abstract class Context {
public array $useStack = [];
public array $functionStack = [];
public array $hooks = [];
public Compiler $compiler;
@ -45,13 +47,38 @@ abstract class Context {
public function renderUses() : string
{
return implode(",", $context->useStack ?? []);
return implode(",", $this->useStack ?? []);
}
#public function variables(array $variables) : void
#{
# $this->variables = $variables;
#}
public function renderFunctions() : string
{
$cls = $this->compiledClassPath();
$ns = $this->namespace ? "\\{$this->namespace}\\" : "";
foreach($this->functionStack as $name => $function) {
$list[] = <<<FUNC
if ( false === ( ( \$caching['$ns$name'] ?? false) || function_exists( '$ns$name' ) ) ) {
\$caching['$ns$name'] = true;
function $name(...\$arguments) {
return $cls::\$context->functionStack['$name'](...\$arguments);
}
}
FUNC;
}
return implode(PHP_EOL, $list);
}
public function exportFunctions() : void
{
}
public function pushFunction($name, Callable $callable) : void
{
$this->functionStack[$name] = $callable;
}
public function compiledClassPath() : string
{
@ -61,4 +88,9 @@ abstract class Context {
return $this->className;
}
public function cacheFilename() : string
{
return strtolower(str_replace("\\", DIRECTORY_SEPARATOR, $this->namespace)) . ".context";
}
}

View File

@ -31,7 +31,7 @@ class SectionToken implements ControlStructure {
$order = $options['order'] ?? "count(\$___class__template->sectionList[$name]['$action'])";
return "<?php \$___class__template->sectionList[$name] ??= [ 'prepend' => [], 'append' => [], 'default' => [] ];
\$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$___class__template, \$___global_variables, \$___variables) {
\$___class__template->sectionList[$name]['$action'][] = [ 'order' => $order, 'callback' => function() use (\$picea, \$___class__template, \$___global_variables, \$___variables) {
extract(\$___global_variables); extract(\$___variables, \EXTR_OVERWRITE); ?>";
}
}

View File

@ -7,6 +7,6 @@ class ViewToken implements ControlStructure {
public string $token = "view";
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
return "<?php echo \$___class__template->picea->renderHtml($arguments, get_defined_vars()); ?>";
return "<?php echo \$___class__template->picea->renderHtml($arguments, get_defined_vars(), \$this); ?>";
}
}

View File

@ -2,12 +2,50 @@
namespace Picea\Extension;
use Picea\Compiler\Context;
class LanguageExtension implements Extension {
public array $tokens = [ "lang", "_" ];
public array $tokens = [ "lang", "_", "language.set" ];
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) {
return "<?php /* echo lang($arguments) */ ?>";
public string $currentLanguage = "";
protected LanguageHandler $languageHandler;
public function __construct(Context $context, LanguageHandler $handler) {
$this->register($context);
$this->languageHandler = $handler;
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : string
{
switch($token) {
case "language.set":
return "<?php \$picea->compiler->getExtensionFromToken('$token')->currentLanguage = $arguments; ?>";
case "lang":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->absoluteLang($arguments) ?>";
case "_":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->relativeLang($arguments) ?>";
}
return "";
}
public function register(Context $context) : void
{
$context->pushFunction("_", [ $this, 'relativeLang' ]);
$context->pushFunction("lang", [ $this, 'absoluteLang' ]);
}
public function relativeLang(string $key, array $variables = []) : string
{
return $this->languageHandler->languageFromKey("{$this->currentLanguage}.{$key}", $variables);
}
public function absoluteLang(string $key, array $variables = []) : string
{
return $this->languageHandler->languageFromKey($key, $variables);
}
}

View File

@ -0,0 +1,8 @@
<?php
namespace Picea\Extension;
interface LanguageHandler
{
public function languageFromKey(string $key, array $variables = []) : string;
}

View File

@ -2,50 +2,81 @@
namespace Picea\Extension;
use Picea\Compiler\Context;
class UrlExtension implements Extension {
protected string $urlBase;
protected string $assetToken;
protected array $routes;
public array $tokens = [ "url" , "route", "asset" ];
public function __construct(string $urlBase = "", string $assetToken = "") {
public function __construct(Context $context, string $urlBase = "", string $assetToken = "") {
$this->urlBase = $urlBase;
$this->assetToken = $assetToken;
}
$this->register($context);
}
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : ?string
public function parse(/*\Picae\Compiler\Context*/ &$context, ?string $arguments, string $token) : ?string
{
switch($token) {
case "asset":
return "<?php echo \$___picea->compiler->getExtensionFromToken('$token')->buildAssetUrl($arguments) ?>";
case "route":
return "<?php echo \$___picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
case "url":
return "<?php echo \$___picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
case "asset":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildAssetUrl($arguments) ?>";
case "route":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildRouteUrl($arguments) ?>";
case "url":
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
}
return null;
}
public function buildUrl(string $uri, array $parameters = []) : string
public function register(Context $context) : void
{
return $this->url() . $uri . ( $parameters ? "?" . http_build_query($parameters) : "" );
$context->pushFunction("url", [ $this, 'buildUrl' ]);
$context->pushFunction("asset", [ $this, 'buildAssetUrl' ]);
$context->pushFunction("route", [ $this, 'buildRouteUrl' ]);
}
public function buildUrl(string $uri = "", array $parameters = []) : string
{
return $this->url() . "/" . ltrim($uri, "/") . ( $parameters ? "?" . http_build_query($parameters) : "" );
}
public function buildAssetUrl(string $uri, array $parameters = []) : string
{
return $this->url() . $uri . "?" . http_build_query( array_replace([ 'token' => $this->assetToken ], $parameters) );
return $this->buildUrl($uri, array_replace([ 'v' => $this->assetToken ], $parameters));
}
public function buildRouteUrl(string $name, array $parameters = []) : string
{
if ( false !== ( $route = $this->routes[$name] ?? false ) ) {
return $this->buildUrl($this->prepareRoute($route, $parameters), $parameters);
}
$routeList = json_encode($this->routes, \JSON_PRETTY_PRINT);
throw new \RuntimeException("
Given route `$name` could not be found within current route list:
$routeList
");
}
public function url() : string
{
return $this->scheme() . $this->domain() . $this->base();
}
public function registerRoute(string $name, string $route) : void
{
$this->routes[$name] = $route;
}
/**
* Return URI formatted
*/
@ -60,33 +91,33 @@ class UrlExtension implements Extension {
if ( ($base = $this->config['base'] ?? false) && ( stripos($uri, $base) === 0 ) ) {
$uri = substr($uri, strlen($base));
}
return '/' . ltrim($uri, '/');
}
/**
* Return query string
*/
protected function queryString() : string
protected function queryString() : string
{
return isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : "";
}
protected function scheme() : string
protected function scheme() : string
{
return ( $this->isHttps() ? "https" : "http" ) . "://";
}
protected function base() : string
protected function base() : string
{
return $this->urlBase ? $this->urlBase . "/" : "";
}
/**
* Check if Uri's segments are valid
* @param array $segments The uri's segments
*/
protected function secure($segments = []) : array
protected function secure($segments = []) : array
{
return array_diff($segments, [ '..', '://' ]);
}
@ -95,25 +126,46 @@ class UrlExtension implements Extension {
{
return strtolower($_SERVER['HTTP_HOST']);
}
protected function isDefaultPort() : bool
protected function isDefaultPort() : bool
{
return in_array($_SERVER['SERVER_PORT'], [ 80, 443 ]);
}
protected function isHttps() : bool
protected function isHttps() : bool
{
$header = in_array('https', array_map("strtolower", [
$header = in_array('https', array_map("strtolower", [
$_SERVER['X-Url-Scheme'] ?? "",
$_SERVER['Front-End-Https'] ?? "",
$_SERVER['Front-End-Https'] ?? "",
$_SERVER['X-Forwarded-Ssl'] ?? "",
$_SERVER['X-Forwarded-Proto'] ?? "",
$_SERVER['X-Forwarded-Proto'] ?? "",
$_SERVER['X-Forwarded-Protocol'] ?? "",
]));
return $header
|| ( "443" === $_SERVER['SERVER_PORT'] ?? "" )
|| ( "https" === $_SERVER['REQUEST_SCHEME'] ?? "http" )
return $header
|| ( "443" === $_SERVER['SERVER_PORT'] ?? "" )
|| ( "https" === $_SERVER['REQUEST_SCHEME'] ?? "http" )
|| ( "off" !== strtolower($_SERVER['HTTPS'] ?? "off") );
}
}
protected function prepareRoute(string $route, array &$arguments)
{
if ( preg_match_all('~{(.*?)}~si', $route, $matches, PREG_SET_ORDER) ) {
$search = [];
foreach($matches as $item) {
if ( ! array_key_exists($item[1], $arguments) ) {
throw new \InvalidArgumentException("Argument `{$item[1]}` is missing within required route parameter(s)");
}
$search[ $item[0] ] = $arguments[ $item[1] ];
unset($arguments[ $item[1] ]);
}
$route = str_replace(array_keys($search), array_values($search), $route);
}
return $route;
}
}

View File

@ -27,13 +27,13 @@ class Picea implements LanguageRegistration
public bool $debug;
public function __construct(
?Closure $responseHtml = null,
?Compiler\Context $context = null,
?Caching\Cache $cache = null,
?Compiler $compiler = null,
?LanguageRegistration $languageRegistration = null,
?FileFetcher $fileFetcher = null,
?string $builderTemplatePath = null,
? Closure $responseHtml = null,
? Compiler\Context $context = null,
? Caching\Cache $cache = null,
? Compiler $compiler = null,
? LanguageRegistration $languageRegistration = null,
? FileFetcher $fileFetcher = null,
? string $builderTemplatePath = null,
bool $debug = false
){
$this->response = $responseHtml;
@ -44,6 +44,9 @@ class Picea implements LanguageRegistration
$this->compiler = $compiler ?? $this->instanciateCompiler();
$this->fileFetcher = $fileFetcher ?? $this->instanciateFileFetcher();
$this->debug = $debug;
#$this->context->exportFunctions();
$this->renderContext($this->context);
}
public function renderHtml(string $viewPath, array $variables = [], ?object $proxy = null) : string
@ -55,21 +58,31 @@ class Picea implements LanguageRegistration
return $object();
}
public function outputHtml(string $viewPath, array $variables) : ResponseInterface
public function renderContext(Compiler\Context $context) : object
{
if ( $this->response ?? false ) {
if ( false === $content = $this->cache->handle($viewPath) ) {
}
$source = $this->compileSource($source ?? "test");
$response = $this->response;
return $response("abc");
if ( null === $object = $this->contextFromCache($context) ) {
throw new \RuntimeException("An error occured while trying to save a compiled template.");
}
throw new \InvalidArgumentException("No \Psr\Http\Message\ResponseInterface closure provided. Please provide one using the constructor or assigning it to the class variable `responseHtml`.");
return $object;
}
#public function outputHtml(string $viewPath, array $variables) : ResponseInterface
#{
# if ( $this->response ?? false ) {
# if ( false === $content = $this->cache->handle($viewPath) ) {
#
# }
# $source = $this->compileSource($source ?? "test");
# $response = $this->response;
# return $response("abc");
# }
# throw new \InvalidArgumentException("No \Psr\Http\Message\ResponseInterface closure provided. Please provide one using the constructor or assigning it to the class variable `responseHtml`.");
#}
public function compileSource(string $source) : array
{
$this->compiler->loadSourceCode($source);
@ -94,7 +107,7 @@ class Picea implements LanguageRegistration
$context = clone $this->context;
$context->viewPath = $viewPath;
$context->source = $this->compiler->compile($context);
return $context;
}
@ -129,6 +142,16 @@ class Picea implements LanguageRegistration
return $this->cache->load($viewPath, $this, $variables, $proxy);
}
public function contextFromCache(Compiler\Context $context) : ?object
{
if ( $this->debug || ! $this->cache->compiled( $context->cacheFilename() ) ) {
$context = $this->compileContext($context);
$this->cache->save($context);
}
return $this->cache->load($context->cacheFilename(), $this);
}
public function instanciateCompiler() : Compiler
{
return new Compiler($this->languageRegistration);
@ -182,4 +205,16 @@ class Picea implements LanguageRegistration
return $context;
}
protected function compileContext(Compiler\Context $context) : Compiler\Context
{
$tmpFolder = sys_get_temp_dir();
$builder = $this->instanciateBuilder();
$compiled = $this->compileSource("");
$context->viewPath = $context->cacheFilename();
$builder->build($context, "");
$context->classPath = $tmpFolder . DIRECTORY_SEPARATOR . $context->cacheFilename() . ".php";
return $context;
}
}