View variables */ protected array $vars = []; private string $componentPrefix; private string $componentsPath; /** * @param string $path Path to templates directory * @param string $componentPrefix Prefix for component tags. * For example, if the prefix is `f`, then a component tag would look like `` * @param string $componentsPath Path to components directory. * If is a relative path, it will be relative to the `$path` property. * **We recomment that you always use absolute paths for explicitness**. */ public function __construct( string $path = ".", string $componentPrefix = 'f', string $componentsPath = 'components' ) { $this->path = $path; $this->componentPrefix = $componentPrefix; $this->componentsPath = $componentsPath; } /** * Gets a template variable * @return mixed Variable value or `null` if doesn't exists */ public function get(string $key) { return $this->vars[$key] ?? null; } /** * Sets a template variable * @param string|array $key * @param mixed $value Value * @return $this */ public function set($key, $value = null) { if (is_array($key)) { foreach ($key as $k => $v) { $this->vars[$k] = $v; } } else { $this->vars[$key] = $value; } return $this; } /** * Checks if a template variable is set * @return bool If key exists and is not `null` */ public function has(string $key): bool { return isset($this->vars[$key]); } /** * Unsets a template variable. If no key is passed in, clear all variables * @return $this */ public function clear(?string $key = null) { if ($key === null) { $this->vars = []; } else { unset($this->vars[$key]); } return $this; } /** * Renders a template * @param string $file Template file * @param ?array $templateData Template data * @throws Exception If template not found */ public function render(string $file, ?array $templateData = null): void { echo $this->fetch($file, $templateData); } /** * Gets the output of a template * @param string $file Template file * @param ?array $data Template data * @return string Output of template */ public function fetch(string $file, ?array $data = null): string { $template = $this->getTemplate($file); if (!$this->exists($file)) { throw new Exception("Template file not found: $template."); } extract($this->vars); if (is_array($data)) { extract($data); if ($this->preserveVars) { $this->vars += $data; } } ob_start(); $component = include $template; switch (true) { case is_callable($component): $view = $component(); ob_end_clean(); break; case $component instanceof Component: $view = $component->html(); $css = $component->css(); $js = $component->js(); if ($css) { $view .= << $css html; } if ($js) { $view .= << $js html; } ob_end_clean(); break; default: $view = ob_get_clean(); } preg_match_all( "/<$this->componentPrefix-(?[a-z-]+)\s*(?([a-z]+=\"[a-zA-Z]+\"\s*)*)?\s*\/>/", $view, $tagsMatches, ); $tagsMatches = array_filter($tagsMatches); foreach ($tagsMatches[0] ?? [] as $tagIndex => $match) { $tag = $match; $component = $tagsMatches['component'][$tagIndex]; $props = $tagsMatches['props'][$tagIndex] ?? ''; preg_match_all( '/(?[a-z]+)="(?[a-zA-Z]+)"/', $props, $propsMatches, ); $propsMatches = array_filter($propsMatches); if ($propsMatches) { $props = []; foreach (array_keys($propsMatches[0]) as $propIndex) { $name = $propsMatches['name'][$propIndex]; $value = $propsMatches['value'][$propIndex]; $props[$name] = $value; } } else { $props = []; } $component = $this->fetch("$this->componentsPath/$component", $props); $view = str_replace($tag, $component, $view); } return $view; } /** * Checks if a template file exists * @param string $file Template file * @return bool Template file exists */ public function exists(string $file): bool { return file_exists($this->getTemplate($file)); } /** * Gets the full path to a template file * @param string $file Template file * @return string Template file location */ public function getTemplate(string $file): string { $fileDoesNotHaveExtension = substr($file, -strlen($this->extension)) !== $this->extension; if ($fileDoesNotHaveExtension) { $file .= $this->extension; } $isLinuxAbsolutePath = $file[0] === '/'; $isWindowsAbsolutePath = PHP_OS === 'WINNT' && $file[1] === ':'; if ($isLinuxAbsolutePath || $isWindowsAbsolutePath) { return $this::normalizePath($file); } return $this::normalizePath("$this->path/$file"); } /** * Displays escaped output * @param string $str String to escape * @return string Escaped string */ public function e(string $str): string { $value = htmlentities($str); echo $value; return $value; } protected static function normalizePath( string $path, string $separator = DIRECTORY_SEPARATOR ): string { return str_replace(['\\', '/'], $separator, $path); } }