diff options
Diffstat (limited to 'lib/flexi')
| -rw-r--r-- | lib/flexi/Factory.php | 243 | ||||
| -rw-r--r-- | lib/flexi/PhpTemplate.php | 106 | ||||
| -rw-r--r-- | lib/flexi/Template.php | 208 | ||||
| -rw-r--r-- | lib/flexi/TemplateNotFoundException.php | 12 |
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 +{ +} |
