Skip to content

Commit

Permalink
wip php 8.4, docs, and sanitization helpers
Browse files Browse the repository at this point in the history
  • Loading branch information
kevindees committed Nov 19, 2024
1 parent b2a7336 commit 13e21be
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 90 deletions.
151 changes: 149 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
<?php
/**
* views/layouts/main-layout.php
*
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
*/
?>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Site</title>
<?php $this->yield('scripts'); ?>
</head>

Expand All @@ -30,11 +71,15 @@ First create a layout file as `views/layouts/main-layout.php`:
</html>
```

### Home Page

Next, create a file that uses the layout `views/home.php`:

```php
<?php
/**
* views/home.php
*
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
* @var $content string
*/
Expand All @@ -45,15 +90,19 @@ echo $content;
?>

<?php $this->section('scripts'); ?>
<script src="my.js"></script>
<script>
console.log(<?= json_encode(['site' => '<My Site>'], JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) ?>);
</script>
<?php $this->end(); ?>
<?php $this->section('footer'); ?>
<footer>My footer</footer>
<?php $this->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';
Expand All @@ -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
<?php
/**
* views/layouts/main-layout.php
*
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
*/
?>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Site</title>
<?php $this->yield('scripts'); ?>
</head>
<body>
<?php $this->yield('main'); ?>
<?php $this->include('sidebar', ['menu' => ['Home', 'About', 'Contact']]); ?>
<?php $this->yield('footer'); ?>
</body>
</html>
```
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
<?php
/**
* Sidebar view file
* @var array $menu The array of menu items passed to this view
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
*/
?>
<aside class="sidebar">
<nav class="menu">
<ul>
<?php foreach ($menu ?? [] as $item): ?>
<li>
<a href="#"><?= htmlspecialchars($item, ENT_QUOTES, 'UTF-8') ?></a>
</li>
<?php endforeach; ?>
</ul>
</nav>
</aside>
```
## Sanitization: Raw HTML, Attributes, Slugs, and JSON
Sanitize HTML from using the rendering engine's `escHtml()`:
```php
<?php
/**
* Sidebar view file
* @var array $menu The array of menu items passed to this view
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
*/
?>
<aside class="sidebar">
<nav class="menu">
<ul>
<?php foreach ($menu ?? [] as $item): ?>
<li>
<a href="<?= $this->escAttr($item) ?>">
<?= $this->escHtml($item) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</nav>
</aside>
```
Sanitize data and encode it as JSON using thw rendering engine's `escJson()`:
```php
<?php
/**
* Sidebar view file
* @var array $menu The array of menu items passed to this view
* @var $this \Nullai\Vista\Engines\ViewRenderEngine
*/
?>
<?php $this->section('scripts'); ?>
<script>
// Safely embed JSON in a JavaScript variable
console.log(<?= $this->escJson(['site' => '<My Site>']) ?>);
</script>
<?php $this->end(); ?>
```
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
66 changes: 39 additions & 27 deletions src/Engines/ViewRenderEngine.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
Loading

0 comments on commit 13e21be

Please sign in to comment.