aboutsummaryrefslogtreecommitdiff
path: root/lib/flexi
diff options
context:
space:
mode:
Diffstat (limited to 'lib/flexi')
-rw-r--r--lib/flexi/Factory.php243
-rw-r--r--lib/flexi/PhpTemplate.php106
-rw-r--r--lib/flexi/Template.php208
-rw-r--r--lib/flexi/TemplateNotFoundException.php12
4 files changed, 569 insertions, 0 deletions
diff --git a/lib/flexi/Factory.php b/lib/flexi/Factory.php
new file mode 100644
index 0000000..c33bd43
--- /dev/null
+++ b/lib/flexi/Factory.php
@@ -0,0 +1,243 @@
+<?php
+/**
+ * Using this factory you can create new Template objects.
+ *
+ * @copyright 2008 Marcus Lunzenauer <mlunzena@uos.de>
+ * @author Marcus Lunzenauer <mlunzena@uos.de>
+ * @license MIT
+ */
+
+namespace Flexi;
+
+class Factory
+{
+ /**
+ * mapping of file extensions to supported template classes
+ */
+ protected array $handlers = [
+ 'php' => [PhpTemplate::class, []],
+ ];
+
+ /**
+ * Constructor of TemplateFactory.
+ *
+ * @param string $path the template include path
+ */
+ public function __construct(protected string $path)
+ {
+ $this->set_path($path);
+ }
+
+ /**
+ * Sets a new include path for the factory and returns the old one.
+ *
+ * @param string $path the new path
+ *
+ * @return string the old path
+ */
+ public function set_path(string $path): string
+ {
+ $old_path = $this->get_path();
+
+ if (!str_ends_with($path, '/')) {
+ $path .= '/';
+ }
+
+ $this->path = $path;
+
+ return $old_path;
+ }
+
+ /**
+ * Returns the include path of the factory
+ *
+ * @return string the current include path
+ */
+ public function get_path(): string
+ {
+ return $this->path;
+ }
+
+ /**
+ * Open a template of the given name using the factory method pattern.
+ * If a string was given, the path of the factory is searched for a matching
+ * template.
+ * If this string starts with a slash or with /\w+:\/\//, the string is
+ * interpreted as an absolute path. Otherwise the path of the factory will be
+ * prepended.
+ * After that the factory searches for a file extension in this string. If
+ * there is none, the directory where the template is supposed to live is
+ * searched for a file starting with the template string and a supported
+ * file extension.
+ * At last the factory instantiates a template object of the matching template
+ * class.
+ *
+ * Examples:
+ *
+ * $factory->open('/path/to/template')
+ * does not prepend the factory's path but searches for "template.*" in
+ * "/path/to"
+ *
+ * $factory->open('template')
+ * prepends the factory's path and searches there for "template.*"
+ *
+ * $factory->open('template.php')
+ * prepends the factory's path but does not search and instantiates a
+ * PHPTemplate instead
+ *
+ * This method returns it's parameter, if it is not a string. This
+ * functionality is useful for helper methods like #render_partial
+ *
+ * @param Template|string $template A name of a template.
+ * @return Template the factored object
+ * @throws TemplateNotFoundException if the template could not be found
+ */
+ public function open(Template|string $template): Template
+ {
+ # if it is not a string, this method behaves like identity
+ if ($template instanceof Template) {
+ return $template;
+ }
+
+ # get file
+ $file = $this->get_template_file($template);
+
+ # retrieve handler
+ [$class, $options] = $this->get_template_handler($file);
+
+ return new $class($file, $this, $options);
+ }
+
+ /**
+ * This method returns the absolute filename of the template
+ *
+ * @param string $template0 a template string
+ *
+ * @return string an absolute filename
+ *
+ * @throws TemplateNotFoundException if the template could not be found
+ */
+ public function get_template_file(string $template0): string
+ {
+ $template = $this->get_absolute_path($template0);
+ $extension = $this->get_extension($template);
+
+ # extension defined, is there a matching template class?
+ if ($extension !== null) {
+ if (file_exists($template)) {
+ return $template;
+ }
+ } # no extension defined, find it
+ else {
+ $file = $this->find_template($template);
+ if ($file !== null) {
+ return $file;
+ }
+ }
+
+ # falling through to throw exception
+ throw new TemplateNotFoundException(sprintf(
+ 'Missing template "%s" in "%s".',
+ $template0,
+ $this->path
+ ));
+ }
+
+ /**
+ * Matches an extension to a template handler.
+ *
+ * @param string $template the template
+ *
+ * @return array|null an array containing the class name and an array of
+ * options of the matched extension;
+ * or NULL if the extension did not match
+ */
+ public function get_template_handler(string $template): ?array
+ {
+ $extension = $this->get_extension($template);
+ return $this->handlers[$extension] ?? null;
+ }
+
+ /**
+ * Registers a handler for templates with a matching extension.
+ *
+ * @param string $extension the extension of the templates to handle
+ * @param class-string<Template> $class the name of the already loaded class
+ * @param array $options optional; an array of options which is used
+ * when constructing a new instance
+ */
+ public function add_handler(
+ string $extension,
+ string $class,
+ array $options = []
+ ): void {
+ $this->handlers[$extension] = [$class, $options];
+ }
+
+ /**
+ * Returns the absolute path to the template. If the given argument starts
+ * with a slash or with a protocoll, this method just returns its arguments.
+ *
+ * @param string $template an incomplete template name
+ *
+ * @return string an absolute path to the incomplete template name
+ */
+ public function get_absolute_path(string $template): string
+ {
+ return preg_match('#^(/|\w+://)#', $template)
+ ? $template
+ : $this->get_path() . $template;
+ }
+
+
+ /**
+ * Find template given w/o extension.
+ *
+ * @param string $template the template's filename w/o extension
+ * @return string|null null if there no such file could be found, a string
+ * containing the complete file name otherwise
+ */
+ public function find_template(string $template): ?string
+ {
+ foreach ($this->handlers as $ext => $handler) {
+ $file = "$template.$ext";
+ if (file_exists($file)) {
+ return $file;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns the file extension if there is one.
+ *
+ * @param string $file an possibly incomplete template file name
+ * @return string|null a string containing the file extension if there is one,
+ * NULL otherwise
+ */
+ public function get_extension(string $file): ?string
+ {
+ return pathinfo($file, PATHINFO_EXTENSION) ?: null;
+ }
+
+ /**
+ * Class method to parse, render and return the presentation of a
+ * template.
+ *
+ * @param Template|string $template A name of a template or a template
+ * @param array $attributes An associative array of attributes and their
+ * associated values.
+ * @param Template|string|null $layout A name of a layout template or a template.
+ *
+ * @return string A string representing the rendered presentation.
+ *
+ * @throws TemplateNotFoundException
+ */
+ public function render(
+ Template|string $template,
+ array $attributes = [],
+ Template|string|null $layout = null
+ ): string {
+ return $this->open($template)->render($attributes, $layout);
+ }
+}
diff --git a/lib/flexi/PhpTemplate.php b/lib/flexi/PhpTemplate.php
new file mode 100644
index 0000000..4eb0da1
--- /dev/null
+++ b/lib/flexi/PhpTemplate.php
@@ -0,0 +1,106 @@
+<?php
+/**
+ * A template engine that uses PHP to render templates.
+ *
+ * @copyright 2008 Marcus Lunzenauer <mlunzena@uos.de>
+ * @author Marcus Lunzenauer <mlunzena@uos.de>
+ * @license MIT
+ */
+
+namespace Flexi;
+
+class PhpTemplate extends Template
+{
+ /**
+ * Parse, render and return the presentation.
+ *
+ * @return string A string representing the rendered presentation.
+ * @throws TemplateNotFoundException
+ */
+ protected function _render(): string
+ {
+ extract($this->get_attributes());
+
+ # include template, parse it and get output
+ ob_start();
+ require $this->template;
+ $content_for_layout = ob_get_contents();
+ ob_end_clean();
+
+ # include layout, parse it and get output
+ if (isset($this->layout)) {
+ $defined = get_defined_vars();
+ unset($defined['this']);
+ $content_for_layout = $this->layout->render($defined);
+ }
+
+ return $content_for_layout;
+ }
+
+ /**
+ * Parse, render and return the presentation of a partial template.
+ *
+ * @param Template|string $partial A partial name or template
+ * @param array $attributes An optional associative array of attributes
+ * and their associated values.
+ * @return string A string representing the rendered presentation.
+ * @throws TemplateNotFoundException
+ */
+ public function render_partial(Template|string $partial, array $attributes = []): string
+ {
+ return $this->factory->render($partial, $attributes + $this->attributes);
+ }
+
+ /**
+ * Renders a partial template with every member of a collection. This member
+ * can be accessed by a template variable with the same name as the name of
+ * the partial template.
+ *
+ * Example:
+ *
+ * # template entry.php contains:
+ * <li><?= $entry ?></li>
+ *
+ *
+ * $entries = ['lorem', 'ipsum'];
+ * $template->render_partial_collection('entry', $entries);
+ *
+ * # results in:
+ * <li>lorem</li>
+ * <li>ipsum</li>
+ *
+ * If you want to use specific content between the rendered partials, you
+ * may define a spacer partial that will be used for that. The spacer will
+ * be rendered with the given attributes.
+ *
+ * @param string $partial A name of a partial template.
+ * @param array $collection The collection to be rendered.
+ * @param Template|string|null $spacer Optional a name of a partial template
+ * used as spacer.
+ * @param array $attributes An optional associative array of attributes
+ * and their associated values.
+ *
+ * @return string A string representing the rendered presentation.
+ * @throws TemplateNotFoundException
+ */
+ public function render_partial_collection(
+ string $partial,
+ array $collection,
+ Template|string|null $spacer = null,
+ array $attributes = []
+ ): string {
+ $template = $this->factory->open($partial);
+ $template->set_attributes($this->attributes);
+ $template->set_attributes($attributes);
+
+ $collected = [];
+ $iterator_name = pathinfo($partial, PATHINFO_FILENAME);
+ foreach ($collection as $element) {
+ $collected[] = $template->render([$iterator_name => $element]);
+ }
+
+ $spacer = isset($spacer) ? $this->render_partial($spacer, $attributes) : '';
+
+ return implode($spacer, $collected);
+ }
+}
diff --git a/lib/flexi/Template.php b/lib/flexi/Template.php
new file mode 100644
index 0000000..44eefbe
--- /dev/null
+++ b/lib/flexi/Template.php
@@ -0,0 +1,208 @@
+<?php
+/**
+ * Abstract template class representing the presentation layer of an action.
+ * Output can be customized by supplying attributes, which a template can
+ * manipulate and display.
+ *
+ * @copyright 2008 Marcus Lunzenauer <mlunzena@uos.de>
+ * @author Marcus Lunzenauer <mlunzena@uos.de>
+ * @license MIT
+ */
+
+namespace Flexi;
+
+abstract class Template
+{
+ /**
+ * Parse, render and return the presentation.
+ *
+ * @return string A string representing the rendered presentation.
+ */
+ abstract protected function _render(): string;
+
+ protected array $attributes = [];
+ protected Template|null $layout = null;
+
+ /**
+ * Constructor
+ *
+ * @param string $template the path of the template.
+ * @param Factory $factory the factory creating this template
+ * @param array $options optional array of options
+ */
+ public function __construct(
+ protected string $template,
+ protected Factory $factory,
+ protected array $options = []
+ ) {
+ }
+
+ /**
+ * __set() is a magic method run when writing data to inaccessible members.
+ * In this class it is used to set attributes for the template in a
+ * comfortable way.
+ *
+ * @param string $name the name of the member field
+ * @param mixed $value the value for the member field
+ *
+ * @see http://php.net/__set
+ */
+ public function __set(string $name, mixed $value): void
+ {
+ $this->set_attribute($name, $value);
+ }
+
+ /**
+ * __get() is a magic method utilized for reading data from inaccessible
+ * members.
+ * In this class it is used to get attributes for the template in a
+ * comfortable way.
+ *
+ * @param string $name the name of the member field
+ *
+ * @return mixed the value for the member field
+ * @see http://php.net/__get
+ */
+ public function __get(string $name): mixed
+ {
+ return $this->get_attribute($name);
+ }
+
+ /**
+ * __isset() is a magic method triggered by calling isset() or empty() on
+ * inaccessible members.
+ * In this class it is used to check for attributes for the template in a
+ * comfortable way.
+ *
+ * @param string $name the name of the member field
+ *
+ * @return bool TRUE if that attribute exists, FALSE otherwise
+ * @see http://php.net/__isset
+ */
+ public function __isset(string $name): bool
+ {
+ return isset($this->attributes[$name]);
+ }
+
+ /**
+ * __unset() is a magic method invoked when unset() is used on inaccessible
+ * members.
+ * In this class it is used to check for attributes for the template in a
+ * comfortable way.
+ *
+ * @param string $name the name of the member field
+ *
+ * @see http://php.net/__set
+ */
+ public function __unset(string $name): void
+ {
+ $this->clear_attribute($name);
+ }
+
+ /**
+ * Parse, render and return the presentation.
+ *
+ * @param array $attributes An optional associative array of attributes and
+ * their associated values.
+ * @param string|Template|null $layout A name of a layout template.
+ *
+ * @return string A string representing the rendered presentation.
+ * @throws TemplateNotFoundException
+ */
+ public function render(array $attributes = [], string|Template $layout = null): string
+ {
+ if (isset($layout)) {
+ $this->set_layout($layout);
+ }
+
+ # merge attributes
+ $this->set_attributes($attributes);
+
+ return $this->_render();
+ }
+
+ /**
+ * Returns the value of an attribute.
+ *
+ * @param string $name An attribute name.
+ * @return mixed An attribute value.
+ */
+ public function get_attribute(string $name)
+ {
+ return $this->attributes[$name] ?? null;
+ }
+
+ /**
+ * Set an array of attributes.
+ *
+ * @return array An associative array of attributes and their associated
+ * values.
+ */
+ public function get_attributes(): array
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Set an attribute.
+ *
+ * @param string $name An attribute name.
+ * @param mixed $value An attribute value.
+ */
+ public function set_attribute(string $name, mixed $value): void
+ {
+ $this->attributes[$name] = $value;
+ }
+
+ /**
+ * Set an array of attributes.
+ *
+ * @param array $attributes An associative array of attributes and their
+ * associated values.
+ */
+ public function set_attributes(array $attributes): void
+ {
+ $this->attributes = $attributes + $this->attributes;
+ }
+
+
+ /**
+ * Clear all attributes associated with this template.
+ */
+ public function clear_attributes(): void
+ {
+ $this->attributes = [];
+ }
+
+ /**
+ * Clear an attribute associated with this template.
+ *
+ * @param string $name The name of the attribute to be cleared.
+ */
+ public function clear_attribute(string $name): void
+ {
+ unset($this->attributes[$name]);
+ }
+
+ /**
+ * Set the template's layout.
+ *
+ * @param Template|string|null $layout A name of a layout template or a
+ * layout template.
+ * @throws TemplateNotFoundException
+ */
+ public function set_layout(Template|string|null $layout): void
+ {
+ $this->layout = $layout ? $this->factory->open($layout) : null;
+ }
+
+ /**
+ * Returns the template's layout.
+ *
+ * @return Template|null
+ */
+ public function get_layout(): ?Template
+ {
+ return $this->layout;
+ }
+}
diff --git a/lib/flexi/TemplateNotFoundException.php b/lib/flexi/TemplateNotFoundException.php
new file mode 100644
index 0000000..dbef2c6
--- /dev/null
+++ b/lib/flexi/TemplateNotFoundException.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @copyright 2009 Marcus Lunzenauer <mlunzena@uos.de>
+ * @author Marcus Lunzenauer <mlunzena@uos.de>
+ * @license MIT
+ */
+
+namespace Flexi;
+
+class TemplateNotFoundException extends \Exception
+{
+}