]> git.andy128k.dev Git - ipf.git/commitdiff
Template provider
authorAndrey Kutejko <andy128k@gmail.com>
Sat, 16 Mar 2019 09:46:45 +0000 (10:46 +0100)
committerAndrey Kutejko <andy128k@gmail.com>
Sat, 16 Mar 2019 10:50:06 +0000 (11:50 +0100)
ipf/admin/controllers/base.php
ipf/controller/function.php
ipf/core_services.php [new file with mode: 0644]
ipf/project.php
ipf/project_template.php [deleted file]
ipf/template.php [new file with mode: 0644]
ipf/twig.php [deleted file]

index c6a29705f8a8add2a7d4165e3e379e50d7c5525a..3eede9e8a1577dbece42cc3de4c287b5fa391efc 100644 (file)
@@ -13,7 +13,8 @@ abstract class IPF_Admin_Base_Controller extends IPF_Controller
 
     protected function render($template, $params)
     {
-        return new IPF_HTTP_Response(IPF_Twig::render($template, $params, $this->request));
+        $body = $this->container['template']->render($template, $params, $this->request);
+        return new IPF_HTTP_Response($body);
     }
 
     /**
index 0344c2826bd3744461a34596f1f8ab7c57c00e9d..598b37db1297ff0caa7e2f4ca73aa94f5a9c0778 100644 (file)
@@ -4,7 +4,12 @@ class IPF_Controller_Function extends IPF_Controller
 {
     function process($action, $request, $matches)
     {
-        return IPF::callFunction($action, array($request, $matches));
+        $get_callable = $this->container['get_callable'];
+        $callable = call_user_func($get_callable, $action);
+
+        if ($callable !== null)
+            return call_user_func_array($callable, array($request, $matches));
+        else
+            throw new \Exception('Impossible to load view function: ' . $action . '.');
     }
 }
-
diff --git a/ipf/core_services.php b/ipf/core_services.php
new file mode 100644 (file)
index 0000000..1b3ae9d
--- /dev/null
@@ -0,0 +1,54 @@
+<?php
+
+namespace IPF;
+
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+
+class CoreServicesProvider implements ServiceProviderInterface
+{
+    public function register(Container $container)
+    {
+        $container['get_callable'] = function ($c) {
+            $project_root = $c['settings']->get('project_root');
+            return function ($function) use ($project_root) {
+                return self::detectCallable($project_root, $function);
+            };
+        };
+    }
+
+    private static function detectCallable($project_root, $function)
+    {
+        if (is_array($function) && count($function) === 2) {
+            // object/class method
+            return $function;
+        }
+
+        if (is_string($function) && preg_match('/^([\\\\\w]+)::(\w+)$/i', $function, $m)) {
+            // static method
+            return array($m[1], $m[2]);
+        }
+
+        if (is_string($function) && preg_match('/^\w+_\w+$/i', $function)) {
+            // plain function
+
+            if (!function_exists($function)) {
+                $parts = explode('_', $function);
+                array_pop($parts);
+                $file = DIRECTORY_SEPARATOR . strtolower(implode(DIRECTORY_SEPARATOR, $parts)) . '.php';
+
+                $filename = $project_root . DIRECTORY_SEPARATOR . 'project' . $file;
+                if (is_file($filename)) {
+                    include_once $filename;
+                    if (function_exists($function)) {
+                        return $function;
+                    }
+                }
+            } else {
+                return $function;
+            }
+        }
+
+        return null;
+    }
+}
index 7df013c92013c41cbd6f61200296f0b035b9ecd8..bb9a7878f6b5fb88145b76a654d334af8b5eaf13 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 
+use IPF\CoreServicesProvider;
+use IPF\Template\TemplateProvider;
 use Pimple\Container;
 
 final class IPF_Project
@@ -68,6 +70,9 @@ final class IPF_Project
 
     public function run()
     {
+        $this->container->register(new CoreServicesProvider());
+        $this->container->register(new TemplateProvider());
+
         $this->container['databaseConnection'] = function ($c) {
             return IPF_Database::connect();
         };
diff --git a/ipf/project_template.php b/ipf/project_template.php
deleted file mode 100644 (file)
index 3cbb43d..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-<?php
-
-final class IPF_Project_Template
-{
-    public static function templateDirs()
-    {
-        $dirs = array();
-
-        $projectTemplates = IPF::get('project_root') . '/project/templates';
-        if (is_dir($projectTemplates))
-            $dirs[] = $projectTemplates;
-
-        foreach (IPF_Project::getInstance()->appList() as $app) {
-            $applicationTemplates = $app->getPath() . 'templates';
-            if (is_dir($applicationTemplates))
-                $dirs[] = $applicationTemplates;
-        }
-
-        return $dirs;
-    }
-
-    public static function context($params=array(), $request=null)
-    {
-        $params = array_merge(array(
-                'indexpage_url' => IPF::get('indexpage_url'),
-                'STATIC_URL'    => IPF::get('static_url'),
-                'MEDIA_URL'     => IPF::get('media_url'),
-                'UPLOAD_URL'    => IPF::getUploadUrl(),
-            ), $params);
-
-        if ($request) {
-            $params = array_merge(array(
-                    'request'       => $request,
-                    'user'          => $request->user,
-                    'CURRENT_URL'   => $request->query,
-                ), $params);
-
-            foreach (IPF::get('template_context_processors', array()) as $proc) {
-                $c = IPF::callFunction($proc, array($request));
-                $params = array_merge($c, $params);
-            }
-
-            foreach (IPF_Project::getInstance()->appList() as $app) {
-                $params = array_merge($app->templateContext($request), $params);
-            }
-        }
-        return $params;
-    }
-
-    public static function urlTag($args)
-    {
-        $count = count($args);
-        if ($count === 0)
-            throw new IPF_Exception('No view specified');
-
-        $view = array_shift($args);
-
-        if ($count === 2 && is_array($args[0])) {
-            return IPF_HTTP_URL::urlForView($view, $args[0]);
-        } elseif ($count === 3 && is_array($args[0]) && is_array($args[1])) {
-            return IPF_HTTP_URL::urlForView($view, $args[0], $args[1]);
-        } else {
-            return IPF_HTTP_URL::urlForView($view, $args);
-        }
-    }
-
-    public static function paramsTag($args)
-    {
-        $params = array();
-
-        $count = count($args);
-        for ($i = 0; $i < $count; ) {
-            if (is_array($args[$i])) {
-                foreach ($args[$i] as $key => $value) {
-                    if ($value === null)
-                        unset($params[$key]);
-                    else
-                        $params[$key] = $value;
-                }
-
-                ++$i;
-            } else {
-                $key = $args[$i];
-                $value = $args[$i+1];
-                if ($value === null)
-                    unset($params[$key]);
-                else
-                    $params[$key] = $value;
-
-                $i += 2;
-            }
-        }
-
-        return IPF_HTTP_URL::generateParams($params);
-    }
-}
diff --git a/ipf/template.php b/ipf/template.php
new file mode 100644 (file)
index 0000000..619b630
--- /dev/null
@@ -0,0 +1,220 @@
+<?php
+
+namespace IPF\Template;
+
+use Exception;
+use IPF_Application;
+use IPF_Router;
+use IPF_Settings;
+use Pimple\Container;
+use Pimple\ServiceProviderInterface;
+use Twig\Environment;
+use Twig\Loader\FilesystemLoader;
+use Twig\Loader\LoaderInterface;
+use Twig\TwigFunction;
+
+interface TemplateEnvironment
+{
+    function render($template, $context = [], $request = null);
+}
+
+class TemplateEnvironmentTwig implements TemplateEnvironment
+{
+    /** @var IPF_Settings */
+    private $settings;
+    /** @var array */
+    private $processors;
+    /** @var IPF_Application[] */
+    private $apps;
+    /** @var LoaderInterface */
+    private $loader;
+    /** @var IPF_Router */
+    private $router;
+
+    public function __construct(IPF_Settings $settings, array $processors, array $apps, LoaderInterface $loader, IPF_Router $router)
+    {
+        $this->settings = $settings;
+        $this->processors = $processors;
+        $this->apps = $apps;
+        $this->loader = $loader;
+        $this->router = $router;
+    }
+
+    public function render($template, $context = [], $request = null)
+    {
+        $twig = $this->createEnvironment();
+        $this->addCommonGlobals($twig);
+        $this->addRequestGlobals($twig, $request);
+        $this->addFunctions($twig);
+
+        $template = $twig->load($template);
+
+        return $template->render($context);
+    }
+
+    private function createEnvironment()
+    {
+        $options = array(
+            'cache' => $this->settings->get('tmp'),
+        );
+        if ($this->settings->get('debug')) {
+            $options['debug'] = true;
+            $options['auto_reload'] = true;
+        }
+
+        return new Environment($this->loader, $options);
+    }
+
+    private function addCommonGlobals(Environment $twig)
+    {
+        $twig->addGlobal('indexpage_url', $this->settings->get('indexpage_url'));
+        $twig->addGlobal('STATIC_URL', $this->settings->get('static_url'));
+        $twig->addGlobal('MEDIA_URL', $this->settings->get('media_url'));
+        $twig->addGlobal('UPLOAD_URL', $this->settings->get('upload_url'));
+    }
+
+    private function addRequestGlobals(Environment $twig, $request)
+    {
+        if (!$request)
+            return;
+
+        $twig->addGlobal('request', $request);
+        $twig->addGlobal('user', $request->user);
+        $twig->addGlobal('CURRENT_URL', $request->query);
+
+        foreach ($this->processors as $proc) {
+            $context = call_user_func_array($proc, array($request));
+            foreach ($context as $key => $value) {
+                $twig->addGlobal($key, $value);
+            }
+        }
+
+        foreach ($this->apps as $app) {
+            $context = $app->templateContext($request);
+            foreach ($context as $key => $value) {
+                $twig->addGlobal($key, $value);
+            }
+        }
+    }
+
+    private function addFunctions(Environment $twig)
+    {
+        $twig->addFunction(new TwigFunction('url', new UrlTag($this->router)));
+        $twig->addFunction(new TwigFunction('params', function (...$args) {
+            return paramsTag(...$args);
+        }));
+        $twig->addFunction(new TwigFunction('trans', function ($str) {
+            return __($str);
+        }));
+    }
+}
+
+final class TemplateProvider implements ServiceProviderInterface
+{
+    public function register(Container $container)
+    {
+        $container['template_dirs'] = function ($c) {
+            $project_root = $c['settings']->get('project_root');
+            $apps = $c['apps'];
+
+            $dirs = array();
+
+            $projectTemplates = $project_root . '/project/templates';
+            if (is_dir($projectTemplates))
+                $dirs[] = $projectTemplates;
+
+            foreach ($apps as $app) {
+                $applicationTemplates = $app->getPath() . 'templates';
+                if (is_dir($applicationTemplates))
+                    $dirs[] = $applicationTemplates;
+            }
+
+            return $dirs;
+        };
+
+        $container['template_loader'] = function ($c) {
+            $templateDirs = $c['template_dirs'];
+
+            $loader = new FilesystemLoader(array());
+            foreach ($templateDirs as $dir) {
+                $loader->addPath($dir);
+            }
+            return $loader;
+        };
+
+        $container['template_context_processors'] = function ($c) {
+            $get_callable = $c['get_callable'];
+            $processors = [];
+            foreach ($c['settings']->get('template_context_processors', array()) as $proc) {
+                $callable = call_user_func($get_callable, $proc);
+                if ($callable !== null)
+                    $processors[] = $callable;
+                else
+                    throw new Exception('Impossible to load template context processor: ' . $proc . '.');
+            }
+            return $processors;
+        };
+
+        $container['template'] = function ($c) {
+            return new TemplateEnvironmentTwig($c['settings'], $c['template_context_processors'], $c['apps'], $c['template_loader'], $c['router']);
+        };
+    }
+}
+
+class UrlTag
+{
+    /** @var IPF_Router */
+    private $router;
+
+    public function __construct(IPF_Router $router)
+    {
+        $this->router = $router;
+    }
+
+    public function __invoke($view, ...$args)
+    {
+        $count = count($args);
+
+        if ($count === 1 && is_array($args[0])) {
+            return $this->router->reverse($view, $args[0]);
+        } elseif ($count === 2 && is_array($args[0]) && is_array($args[1])) {
+            $query = count($args[1]) > 0 ? '?' . paramsTag(...$args[1]) : '';
+            return $this->router->reverse($view, $args[0]) . $query;
+        } else {
+            return $this->router->reverse($view, $args);
+        }
+    }
+}
+
+function paramsTag(...$args)
+{
+    $params = array();
+    $count = count($args);
+    for ($i = 0; $i < $count;) {
+        if (is_array($args[$i])) {
+            foreach ($args[$i] as $key => $value) {
+                if ($value === null)
+                    unset($params[$key]);
+                else
+                    $params[$key] = $value;
+            }
+
+            ++$i;
+        } else {
+            $key = $args[$i];
+            $value = $args[$i + 1];
+            if ($value === null)
+                unset($params[$key]);
+            else
+                $params[$key] = $value;
+
+            $i += 2;
+        }
+    }
+
+    $params_list = array();
+    foreach ($params as $key => $value)
+        $params_list[] = urlencode($key) . '=' . urlencode($value);
+
+    return implode('&amp;', $params_list);
+}
diff --git a/ipf/twig.php b/ipf/twig.php
deleted file mode 100644 (file)
index e68b6a2..0000000
+++ /dev/null
@@ -1,46 +0,0 @@
-<?php
-
-final class IPF_Twig
-{
-    public static function render($tplfile, $params=array(), $request=null)
-    {
-        $context = IPF_Project_Template::context($params, $request);
-        $twig = self::createEnvironment();
-        $template = $twig->loadTemplate($tplfile);
-        return $template->render($context);
-    }
-
-    private static function createEnvironment()
-    {
-        $options = array(
-            'cache' => IPF::get('tmp'),
-        );
-        if (IPF::get('debug')) {
-            $options['debug'] = true;
-            $options['auto_reload'] = true;
-        }
-
-        $twig = new Twig_Environment(self::createLoader(), $options);
-
-        $twig->addFunction(new Twig_SimpleFunction('url', function() {
-            return IPF_Project_Template::urlTag(func_get_args());
-        }));
-        $twig->addFunction(new Twig_SimpleFunction('params', function() {
-            return IPF_Project_Template::paramsTag(func_get_args());
-        }));
-        $twig->addFunction(new Twig_SimpleFunction('trans', function($str) {
-            return __($str);
-        }));
-
-        return $twig;
-    }
-
-    private static function createLoader()
-    {
-        $loader = new Twig_Loader_Filesystem(array());
-        foreach (IPF_Project_Template::templateDirs() as $dir) {
-            $loader->addPath($dir);
-        }
-        return $loader;
-    }
-}