diff --git a/README.md b/README.md
index ad43700..3197455 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,24 @@
# Vista
+A blazing-fast and ultra-lightweight PHP view engine designed for modern PHP 8.4+ applications.
+
+## Overview
+
+Vista is a minimalist PHP view engine that focuses on performance and simplicity. It provides powerful tools to manage layouts, partials/includes, and dynamic content rendering, making it an ideal choice for modern PHP projects. Unlike traditional template engines like Blade or Twig, Vista does not rely on compiling or caching template files. This makes it an ideal choice for developers who prioritize performance and need a straightforward solution for managing views.
+
+## Key Features
+
+Vista is only two files and very capable:
+
+- **No Compilation Required**: Renders views directly which makes debugging simple.
+- **Lightning Fast**: Optimized for speed, with minimal overhead.
+- **Extremely Lightweight**: Small footprint, easy to integrate with any PHP application.
+- **Modern Syntax**: Intuitive, clean, and developer-friendly APIs.
+- **Layout Management**: Easily create reusable layouts and templates.
+- **Partial Rendering**: Modularize your views with include and section methods.
+- **Scoped Data Passing**: Pass variables to views with isolated scopes for security and clarity.
+- **Extensible**: Works seamlessly with other PHP frameworks or custom solutions.
+- **Sanitize**: Sanitize raw HTML, Attributes, and JSON.
## Installation
@@ -9,16 +28,38 @@ To get started with Vista, you need to have PHP 8.4 installed on your system. Yo
composer require nullaidev/vista
```
+## Project Example
+
+```text
+project-root/
+├── vendor/ # Composer dependencies
+│ └── autoload.php # Composer autoloader
+├── views/ # Views folder
+│ ├── layouts/ # Layouts folder
+│ │ └── main-layout.php # Main layout file
+│ └── home.php # View file using the layout
+│ └── sidebar.php # Site sidebar
+├── public # Public web folder
+│ └── index.php # Project main entry point
+
+```
+
+### Layout
+
First create a layout file as `views/layouts/main-layout.php`:
```php
+
+ My Site
yield('scripts'); ?>
@@ -30,11 +71,15 @@ First create a layout file as `views/layouts/main-layout.php`:
```
+### Home Page
+
Next, create a file that uses the layout `views/home.php`:
```php
section('scripts'); ?>
-
+
end(); ?>
section('footer'); ?>
@@ -53,7 +100,9 @@ echo $content;
end(); ?>
```
-Now, create a View and render it `index.php`:
+### Entry Point
+
+Now, create a View and render it from your project main entry point, such as a typical `public/index.php`:
```php
require_once __DIR__ . '/../vendor/autoload.php';
@@ -62,3 +111,101 @@ const NULLAI_VISTA_VIEWS_FOLDER = __DIR__ . '/views';
echo new \Nullai\Vista\View('home');
```
+### Includes
+
+You can use includes within any view relative to the root views folder. In this example, the layout file:
+
+```php
+
+
+
+
+ My Site
+ yield('scripts'); ?>
+
+
+
+ yield('main'); ?>
+
+ include('sidebar', ['menu' => ['Home', 'About', 'Contact']]); ?>
+
+ yield('footer'); ?>
+
+
+```
+
+The `include('sidebar', ['menu' => ['Home', 'About', 'Contact']])` method passes a menu variable to the `sidebar.php` view.
+Inside `sidebar.php`, the `$menu` array will be accessible.
+
+```php
+
+
+```
+
+## Sanitization: Raw HTML, Attributes, Slugs, and JSON
+
+Sanitize HTML from using the rendering engine's `escHtml()`:
+
+```php
+
+
+```
+
+Sanitize data and encode it as JSON using thw rendering engine's `escJson()`:
+
+```php
+
+
+section('scripts'); ?>
+
+end(); ?>
+```
diff --git a/composer.json b/composer.json
index caf4572..adba3bd 100644
--- a/composer.json
+++ b/composer.json
@@ -3,7 +3,9 @@
"description": "Vista view render engine.",
"license": "MIT",
"require": {
- "php": ">=8.4"
+ "php": ">=8.4",
+ "ext-mbstring": "*",
+ "ext-iconv": "*"
},
"require-dev": {
"phpunit/phpunit": "^11"
diff --git a/src/Engines/ViewRenderEngine.php b/src/Engines/ViewRenderEngine.php
index ee4ca42..5ddad61 100644
--- a/src/Engines/ViewRenderEngine.php
+++ b/src/Engines/ViewRenderEngine.php
@@ -4,15 +4,20 @@
use Nullai\Vista\View;
-class ViewRenderEngine
+class ViewRenderEngine implements \Stringable
{
- protected View $view;
+ public const string ENCODING = 'UTF-8';
+
+ protected View $view {
+ get => $this->view;
+ }
+
protected array $sections = [];
protected string $currentSection;
protected string $layout = '';
/**
- * TemplateEngine constructor.
+ * Engine constructor.
*
* @param View $view
*/
@@ -21,37 +26,44 @@ public function __construct(View $view)
$this->view = $view;
}
- public function data() : array
+ public function include(string|View $view, array $data = []) : void
{
- return $this->view->data;
+ if(str_starts_with($view, ':')) {
+ $view = $this->view->folder . '/' . pathinfo($this->view->file, PATHINFO_DIRNAME) . $view;
+ }
+
+ $_view = $view instanceof View ?: new View($view, $data);
+ $_data = $_view->data;
+ $_parent_view = $this->view;
+
+ // Use parent view's file extension if none is set
+ $_view->ext = $_view->ext ?: $this->view->ext;
+
+ $cb = \Closure::bind(function() use ($_view, $_data, $_parent_view) {
+ if(file_exists($_view->fullPath)) {
+ $parent = $_parent_view->data;
+ extract($_data);
+
+ include $_view->fullPath;
+ }
+ }, $this);
+
+ $cb();
}
- public function view() : View
+ public function escHtml($html, $flags = ENT_NOQUOTES) : string
{
- return $this->view;
+ return htmlspecialchars($html, $flags, static::ENCODING);
}
- public function sectionIs() : string
+ public function escAttr($html, $flags = ENT_QUOTES) : string
{
- return $this->currentSection;
+ return htmlspecialchars($html, $flags, static::ENCODING);
}
- public function include(string|View $view, array $data = []) : void
+ public function escJson($data, $flags = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) : string
{
- $_include_view = $view instanceof View ?: new View($view, $data);
- $_parent_view_data = $this->view->data;
- $_view_data = $_include_view->data;
-
- $cb = \Closure::bind(function() use ($_include_view, $_parent_view_data, $_view_data) {
- if(file_exists($_include_view->fullPath)) {
- extract($_parent_view_data);
- extract($_view_data);
-
- include $_include_view->fullPath;
- }
- }, $this);
-
- $cb();
+ return json_encode($data, $flags);
}
public function includeIf(bool $condition, mixed ...$args) : bool
@@ -87,9 +99,9 @@ public function layout(string $layout) : void
public function render() : void
{
- $_view_data = $this->view->data;
- extract($_view_data);
- /** @noinspection PhpIncludeInspection */
+ $_data = $this->view->data;
+
+ extract($_data);
include ( $this->view->fullPath );
if($this->layout) {
diff --git a/src/View.php b/src/View.php
index 8894691..e32715d 100644
--- a/src/View.php
+++ b/src/View.php
@@ -3,7 +3,7 @@
use Nullai\Vista\Engines\ViewRenderEngine;
-class View
+class View implements \Stringable
{
public array $data = [] {
get => $this->data;
@@ -12,7 +12,7 @@ class View
public string $folder {
get => $this->folder;
- set => $this->folder = $value;
+ set => $this->folder = rtrim($value, DIRECTORY_SEPARATOR);
}
public string $ext = 'php' {
@@ -39,13 +39,14 @@ class View
*
* Take a custom file location or dot notation of view location.
*
- * @param string $view dot syntax or specific file path
+ * @param string $view dot syntax (__DIR__:subfolder.file-name or subfolder.file-name) or specific file path
* @param array $data
*/
public function __construct(string $view, array $data = [])
{
if(str_contains($view, ':')) {
[$this->folder, $file] = explode(':', $view);
+ $this->folder = $this->folder ?: constant('NULLAI_VISTA_VIEWS_FOLDER');
$this->file = str_replace('.', DIRECTORY_SEPARATOR, $file);
}
elseif(str_contains($view, DIRECTORY_SEPARATOR)) {
@@ -60,42 +61,6 @@ public function __construct(string $view, array $data = [])
$this->data = $data ?: $this->data;
$this->engine = defined('NULLAI_VISTA_ENGINE') ? constant('NULLAI_VISTA_ENGINE') : ViewRenderEngine::class;
- $this->init();
- }
-
- protected function init() {}
-
- public function data(array $data = []) : static|array
- {
- $this->data = $data;
- return $this;
- }
-
- public function folder(string $folder) : static|string
- {
- $this->folder = rtrim($folder, DIRECTORY_SEPARATOR);
- return $this;
- }
-
- public function file(string $file) : static|string
- {
- $this->file ??= $file;
- return $this;
- }
-
- public function ext(string $ext) : static|string
- {
- if($ext) {
- $this->ext = $ext;
- }
-
- return $this;
- }
-
- public function engine(string $engine) : static|string
- {
- $this->engine = $engine;
- return $this;
}
protected function render(): void
@@ -104,7 +69,7 @@ protected function render(): void
new $templateEngine($this)->render();
}
- public function get(): string
+ public function content(): string
{
ob_start();
$this->render();
@@ -113,6 +78,6 @@ public function get(): string
public function __toString() : string
{
- return $this->get();
+ return $this->content();
}
}
\ No newline at end of file
diff --git a/tests/TestVista.php b/tests/TestVista.php
index 411463b..bc56f2a 100644
--- a/tests/TestVista.php
+++ b/tests/TestVista.php
@@ -41,7 +41,14 @@ public function testViewContent()
{
$view = new View('test');
- $this->assertStringContainsString('test file', $view->get());
+ $this->assertStringContainsString('test file &', $view->content());
+ }
+
+ public function testViewContentRelativeLookup()
+ {
+ $view = new View(':test');
+
+ $this->assertStringContainsString('test file &', $view->content());
}
public function testViewEngineClass()
@@ -55,13 +62,13 @@ public function testViewEngineContent()
{
$view = new View('engine-access');
- $this->assertEquals($view->fullPath, $view->get());
+ $this->assertEquals($view->fullPath, $view->content());
}
public function testViewEngineLayoutWithContent()
{
$view = new View('with-layout', ['content' => 'content body']);
- $content = $view->get();
+ $content = $view->content();
$this->assertStringStartsWith('
+end(); ?>
+
+section('footer');
+echo PHP_EOL . '