Spiral provides a spiral/views
component that serves as an abstraction layer for handling views. It provides a set of
interfaces for registering template engines and using them to render templates.
The Spiral\Views\ViewsInterface
interface provides methods for rendering a template, compiling a cache version of a
view, resetting the view cache, and obtaining a view instance associated with a given path.
Note
The component is also available via the prototyped propertyviews
. Read more about prototype trait in the The Basics — Prototyping section.
To render a view in the controller or other service, simply invoke the render
method. The view name does not need to
include an extension or namespace (default to be used).
You can use Dependency Injection to inject the Spiral\Views\ViewsInterface
interface into your controller.
namespace App\Endpoint\Web;
use Spiral\Views\ViewsInterface;
class HomeController
{
public function __construct(
private readonly ViewsInterface $views
) {
}
public function index(): string
{
return $this->views->render('home');
}
}
To render the view with the passed data, use the second array argument:
public function index(): string
{
return $this->views->render('home', [
'key' => 'value'
]);
}
In some cases it might be more performant to cache the view in a stateless component. To obtain an instance of a view,
you can use the get
method.
use Spiral\Views\ViewsInterface;
public function index(): string
{
/** @var \Spiral\Views\ViewInterface $view */
$view = $this->views->get('profile-card');
$card1 = $view->render('name' => 'John']);
$card2 = $view->render(['name' => 'Jane']);
return "<html><body>{$card1} {$card2}</body></html>";
}
The compile
method can be used to compile one of multiple cache versions of a view. This method takes a view path as
an argument and compiles the cache version of the view.
namespace App\Endpoint\Console;
use Spiral\Console\Command;
use Spiral\Views\LoaderInterface;
use Spiral\Views\ViewsInterface;
class WarmupViewsCommand extends Command
{
protected const NAME = 'views:compile';
protected const DESCRIPTION = 'Compile all views in default namespace';
public function __invoke(LoaderInterface $loader, ViewsInterface $views): void
{
$templates = $loader->withExtension('dark.php')->list();
// or
$templates = $loader->withExtension('twig')->list(namespace: 'my-package');
foreach ($templates as $template) {
$views->compile($template);
}
}
}
The reset
method can be used to reset the cache of a view. This method takes a view path as an argument and resets
the cache for the view.
use Spiral\Views\ViewsInterface;
$views->reset('home');
The Spiral\Views\LoaderInterface
is responsible for loading the source of a view. A view loader can be used to check
if a view exists, load its source, and list all available views.
Note
By default LoaderInterface is implemented bySpiral\Views\ViewLoader
class that provides file-based view loading.
In order to use the methods defined in the LoaderInterface
, you first need to specify an extension for the loader.
This can be done by calling the withExtension
method and passing in the desired extension.
Warning
ThewithExtension
method is immutable. It will return a new instance ofLoaderInterface
.
use Spiral\Views\LoaderInterface;
public function index(LoaderInterface $loader): string
{
$loader = $loader->withExtension('dark.php');
// ...
}
The exists
method can be used to check if a view is available for rendering.
if (!$loader->exists('home')) {
throw new \RuntimeException('View not found');
}
// ...
You can also check if a view exists in a specific namespace.
$loader->exists('my-package:home')
The load
method can be used to retrieve the source of a view. It will return the source of the view as
a Spiral\Views\ViewSource
instance.
/** @var \Spiral\Views\ViewSource $source */
$source = $loader->load('home');
$source->getNamespace(); // default
$source->getName(); // home
$source->getFilename(); // /app/views/home.dark.php
$source->getCode(); // <html>...</html>
In some cases, you want to replace the source code of a view. To do this, you can use the withCode
method.
/** @var \Spiral\Views\ViewSource $source */
$source = $loader->load('home');
$source = $source->withCode('<div>...</div>');
$source->getCode(); // <div>...</div>
Warning
ThewithCode
method is immutable. It will return a new instance ofViewSource
.
The list
method can be used to list all available views. It will return an array of view paths.
$views = $loader->list();
You can also list all available views in a specific namespace.
$views = $loader->list(namespace: 'my-package');
You can create your own loader by implementing the Spiral\Views\LoaderInterface
interface.
Here is an example of a loader that loads views from a database.
Danger
This is just an example that shows how to implement a custom loader. It is not tested and should not be used.
namespace App\Integration\Database;
use Spiral\Views\LoaderInterface;
use Spiral\Views\Loader\PathParser;
use Spiral\Views\ViewSource;
final class DatabaseLoader implements LoaderInterface
{
private ?PathParser $parser = null;
public function __construct(
private readonly DatabaseInterface $database,
private readonly string $defaultNamespace = self::DEFAULT_NAMESPACE,
) {}
public function withExtension(string $extension): LoaderInterface
{
$loader = clone $this;
$loader->parser = new PathParser($this->defaultNamespace, $extension);
return $loader;
}
public function getExtension(): ?string
{
return $this->parser?->getExtension();
}
public function exists(string $path, string &$filename = null, ViewPath &$parsed = null): bool
{
if ($this->parser === null) {
throw new LoaderException(
'Unable to locate view source, no extension has been associated.'
);
}
$parsed = $this->parser->parse($path);
if ($parsed === null) {
return false;
}
if (!isset($this->namespaces[$parsed->getNamespace()])) {
return false;
}
// Iterate over all registered namespaces and check if the view exists in
// any of them.
foreach ((array)$this->namespaces[$parsed->getNamespace()] as $namespace) {
$isExists = $this->getTemplate($namespace, $parsed->getBasename()) !== null;
if ($isExists) {
$filename = $parsed->getBasename();
return true;
}
}
return false;
}
public function load(string $path): ViewSource
{
if (!$this->exists($path, $filename, $parsed)) {
throw new LoaderException(
\sprintf('Unable to load view `%s`, file does not exist.', $path)
);
}
// View variable contains an array of record from the database with the
// following structure:
// ['html' => '<div>...</div>', 'path' => '...', 'namespace' => '...']
$view = $this->getTemplate($parsed->getNamespace(), $parsed->getBasename());
return (new ViewSource(
$filename,
$parsed->getNamespace(),
$parsed->getName()
))->withCode($view['html']);
}
public function list(string $namespace = null): array
{
$views = [];
foreach ($this->namespaces as $namespace) {
$templates = $this->database->select()
->from('views')
->where('namespace', $namespace)
->fetchAll();
foreach ($templates as $template) {
$views[] = $namespace . ':' . $template['path'];
}
}
return $views;
}
private function getTemplate(string $namespace, string $path): ?array
{
return $this->database->select()
->from('views')
->where('namespace', $namespace)
->where('path', $path)
->fetchOne();
}
}
To use this loader, you need to replace a default one in the container.
namespace App\Application\Bootloader;
use Spiral\Views\LoaderInterface;
use App\Integration\Database\DatabaseLoader;
class AppBootloader extends Bootloader
{
protected const SINGLETONS = [
LoaderInterface::class => [self::class, 'initLoader'],
];
protected function initLoader(DatabaseInterface $database): LoaderInterface
{
return new DatabaseLoader($database);
}
}
You can add global variables that will be available in all views.
There are several ways to add a global variable.
You can use the configuration file app/config/views.php
.
return [
// ...
'globalVariables' => [
'some_var' => env('SOME_VALUE'),
'other_var' => 'other_value'
]
];
After that, you can use the variables in any view template including inherited templates.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<?=$some_var?>
<?=$other_var?>
</body>
</html>
See more
For example, you can use global variables to pass CSRF token to the views. See HTTP — CSRF protection for more details.
In the Views component, namespaces are used to organize and categorize views. Views can be separated into different namespaces based on their purpose or associated feature. This makes it easier to manage views, avoid naming conflicts, and reuse common templates.
To register a namespace, you can use the addDirectory
method of the ViewsBootloader
class. This method takes two
arguments: the first is the namespace name, and the second is the directory path associated with that namespace.
For example, if you have a directory called views in the my-package
directory of your application, you can register
it with the my-package
namespace as follows:
namespace App\Application\Bootloader;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Views\Bootloader\ViewsBootloader;
class AppBootloader extends Bootloader
{
public function boot(ViewsBootloader $views): void
{
$views->addDirectory('my-package', directory('root') . 'my-package/views');
}
}
With this namespace registered, you can now refer to views in the views directory using the my-package
namespace.
For example, if you have a view file called home.dark.php
in the my-package/views
directory, you can render it as
follows:
$views->render('my-package:home');
By default, the Views component provides only one template engine - Plain PHP templating. But there are also other template engines available:
The Component provides the ability to register a custom template engine. To use a custom template engine you need to
implement the Spiral\Views\EngineInterface
.
The following is a brief explanation of EngineInterface
methods:
The withLoader
method is used to set and configure the views loader for the engine. This is the place where you can
define the extension of the view files that will be used by the engine.
use Spiral\Views\EngineInterface;
use Spiral\Views\LoaderInterface;
final class BladeEngine implements EngineInterface
{
private ?LoaderInterface $loader = null;
public function withLoader(LoaderInterface $loader): EngineInterface
{
$engine = clone $this;
$engine->loader = $loader->withExtension('blade.php');
return $engine;
}
public function getLoader(): LoaderInterface
{
return $this->loader;
}
}
The compile
and reset
methods compiles (and resets cache) for the given view path in a provided context. This
methods will be called each time the view needs to be recompiled.
use Spiral\Views\ContextInterface;
use Illuminate\View\Compilers\CompilerEngine;
private readonly CompilerEngine $blade;
public function compile(string $path, ContextInterface $context): mixed
{
$filepath = $this->getLoader()->load($path)->getFilename();
// Run the compilation process for the given view path
$this->blade->getCompiler()->compile($filepath);
}
public function reset(string $path, ContextInterface $context): void
{
$filepath = $this->getLoader()->load($path)->getFilename();
// Reset the cache for the given view path
\unlink($this->blade->getCompiler()->getCompiledPath($filepath));
}
The get
method returns an instance of the view class associated with the view path.
use Spiral\Views\ViewInterface;
use Illuminate\View\Engines\CompilerEngine;
private readonly CompilerEngine $blade;
public function get(string $path, ContextInterface $context): ViewInterface
{
$filepath = $this->getLoader()->load($path)->getFilename();
return new BladeView($this->blade, $filepath);
}
use Spiral\Views\ViewInterface;
use Illuminate\View\Engines\CompilerEngine;
class BladeView implements ViewInterface
{
public function __construct(
private readonly CompilerEngine $blade,
private readonly string $filepath
) {
}
public function render(array $data = []): string
{
return $this->blade->get($this->filepath, $data);
}
}
The addEngine
method of Spiral\Views\Bootloader\ViewsBootloader
allows you to add your custom template engine to the
Views component and make it available for use in your application.
namespace App\Application\Bootloader;
use Spiral\Boot\Bootloader\Bootloader;
use Spiral\Views\Bootloader\ViewsBootloader;
class AppBootloader extends Bootloader
{
public function boot(ViewsBootloader $views): void
{
$views->addEngine(MyEngine::class);
}
}
Event | Description |
---|---|
Spiral\Views\Event\ViewNotFound | The Event will be fired when the view is not found. |
Note
To learn more about dispatching events, see the Events section in our documentation.