emsifa / block
PHP原生模板系统
Requires
- php: >=5.5.0
Requires (Dev)
- phpunit/phpunit: 4.*
README
Block 是受 Laravel Blade 启发的 PHP 原生模板系统。Block 不是模板语言,所以不需要像 blade、twig、smarty 等那样编译和缓存。
要求
Block 需要 PHP 5.5 或更高版本
安装
使用 Composer
如果你的项目使用 composer,你可以通过在命令行中输入以下命令来通过 composer 安装 Block:
composer require "emsifa/block"
不使用 Composer
Block 是一个单文件库,所以你可以通过以下步骤轻松安装它,无需任何自动加载器:
- 下载此仓库或仅下载原始
src/Block.php
文件。 - 将其放置在你的项目中的某个位置。例如,在
yourproject/lib/Block.php
。 - 然后将其包含/引入到你的代码中。
入门指南
初始化 Block
<?php use Emsifa\Block; require('vendor/autoload.php'); $view_dir = 'path/to/views'; $view_extension = 'block.php'; $block = new Block($view_dir, $view_extension);
默认情况下,$view_extension
是 php
。我们更倾向于使用自定义扩展名。自定义扩展名使你更容易在编辑器中识别视图文件,而无需打开该文件。
在这个例子中,我们使用 block.php
,所以我们的视图文件名必须以 .block.php
结尾,而不是仅仅 .php
。
你的第一个模板
创建文件 path/to/views/hello.block.php
。
<h1><?= $title ?></h1> <p> <?= $message ?> </p>
渲染它
然后,在代码的某个位置,你可以通过 render
方法来渲染它,如下所示:
echo $block->render('hello', [ 'title' => 'Hello World' 'message' => 'Lorem ipsum dolor sit amet' ]);
是的,你不需要在 Block 中放置文件扩展名。
结果
现在结果应该看起来像这样
<h1>Hello World</h1> <p> Lorem ipsum dolor sit amet </p>
扩展和阻塞
实际上,在大多数模板引擎或模板系统中,有两种主要的视图类型。即 主视图 和 页面视图。
主视图 是包含基本 HTML 标签(如 <doctype>
、<html>
、<head>
、<body>
等)的视图。 页面视图 是一个扩展 主视图 并包含在 主视图 中定义的一些块的视图。
注意:主视图 不是 由
render
方法渲染的。 主视图 只是用于被任何 页面视图 扩展。
如果你熟悉 Laravel Blade 语法,以下是一些差异。
以下是一个关于扩展和阻塞的简单真实世界案例。
创建主视图
<!-- Stored in path/to/views/master.block.php --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><?= $title ?></title> <?= $this->section('stylesheets') ?> <link rel="stylesheet" href="bootstrap.css"> <?= $this->show() ?> </head> <body> <header> <h1>App Name</h1> </header> <div id="content"> <?= $this->get('content') ?> </div> <footer> © 2016 - my app </footer> <?= $this->section('scripts') ?> <script src="jquery.js"></script> <script src="bootstrap.js"></script> <?= $this->show() ?> </body> </html>
在上面的例子中,我们使用
<?=
而不是<?php
来表示$this->section
和$this->show
。这是可以的,因为这些方法不返回任何值。
创建页面视图
在上面的主视图中,有 stylesheets
、content
和 scripts
块。因此,你需要在页面视图中定义它们。
<!-- Stored in path/to/views/pages/lorem-ipsum.block.php --> <?= $this->extend('master') ?> <?= $this->section('stylesheets') ?> <?= $this->parent() ?> <!-- senpai!! \(^o^) --> <link rel="stylesheet" href="lorem.css"> <?= $this->stop() ?> <?= $this->section('scripts') ?> <?= $this->parent() ?> <!-- notice me too senpai!! (^o^)/ --> <script src="lorem.js"></script> <script> initPage(); </script> <?= $this->stop() ?> <?= $this->section('content') ?> <!-- notice me senpai!! \(^o^)/ --> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Officiis, mollitia ad commodi. Eligendi saepe unde iusto quis, praesentium deleniti eos incidunt quas vero, voluptatem, reiciendis inventore, aliquam expedita et rerum. </p> <?= $this->stop() ?>
上述所有块都是可选的。
渲染它!
echo $block->render('pages.lorem-ipsum', [ 'title' => 'Lorem Ipsum' ]);
结果应该看起来像这样
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Lorem Ipsum</title> <link rel="stylesheet" href="bootstrap.css"> <!-- senpai!! \(^o^) --> <link rel="stylesheet" href="lorem.css"> </head> <body> <header> <h1>App Name</h1> </header> <div id="content"> <!-- notice me senpai!! \(^o^) --> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Officiis, mollitia ad commodi. Eligendi saepe unde iusto quis, praesentium deleniti eos incidunt quas vero, voluptatem, reiciendis inventore, aliquam expedita et rerum. </p> </div> <footer> © 2016 - my app </footer> <script src="jquery.js"></script> <script src="bootstrap.js"></script> <!-- notice me too senpai!! (^o^)/ --> <script src="lorem.js"></script> <script> initPage(); </script> </body> </html>
其他有用的功能
HTML 转义
与其他模板引擎一样,Block 也有 HTML 转义的快捷方式。在 Block 中,你可以使用 $this->escape($html)
或 $e($html)
来转义 HTML。
示例
渲染
$block->render('pages/sample-escaping', [ 'title' => 'Title <script>XSS.attack()</script>' ]);
视图
<!-- Stored in path/to/views/pages/sample-escaping.block.php --> <div> <h4><?= $e($title) ?></h4> </div>
然后,标题将像这样转义
<div> <h4>Title <script>XSS.attack()</script></h4> </div>
$get($key, $default = NULL)
在渲染视图时,我们添加了一个包含匿名函数的变量 $get
。此函数允许你获取通过 render
方法传递的值。如果键存在,它将返回该值;如果不存在,它将返回默认值(NULL)。
例如,在上面的主视图中,如果你没有在数组中设置 title
,将显示一个错误“未定义变量 title”。因此,要修复此问题,而不是使用 isset
如此:
<title><?= isset($title) ? $title : 'Default Title' ?></title>
你可以这样使用 $get
:
<title><?= $get('title', 'Default Title') ?></title>
注意:$get
还支持点表示法。这意味着,你可以使用点作为分隔符在 $key
中访问数组。
例如,你可以这样渲染一个包含数组数据的视图
$block->render('pages/profile', [ 'user' => [ 'name' => 'John Doe' ] ])
你可以这样使用 $get
:
<div class='profile'> Name: <?= $get('user.name') ?> City: <?= $get('user.city.name', 'Unknown') ?> </div>
在上面的示例中,user.city.name
将返回 'Unknown',因为你没有在 user
数组中设置 city
。
包含部分视图
还有一种名为 部分视图 的视图类型。 部分视图 是一个包含部分布局的视图文件,你可以在一些 页面 或 母版视图 中使用它,例如小部件、侧边栏、导航栏、主菜单等。 部分视图 类似于 母版视图,它不是通过 render
方法渲染的。但是,你可以通过将它们放入 母版 或 页面视图 中,通过 put
方法来渲染它们。
在 Blade 中,你可以使用 @include('partial', $data)
来包含部分视图。在块中,你可以使用 <?= $this->put('partial', $data) ?>
代替。
例如,让我们创建一个包含小部件滑块的新的 页面视图。
首先你需要为小部件滑块创建 部分视图。
<!-- Stored in path/to/views/partials/slider.block.php --> <!-- notice me senpai!! \(^o^)/ --> <div class="widget widget-slider"> <div class="slider-wrapper"> <div class="slide-1">Slide 1</div> <div class="slide-2">Slide 2</div> <div class="slide-3">Slide 3</div> </div> </div> <?= $this->section('stylesheets') ?> <?= $this->parent() ?> <!-- senpai!! \(^o^) --> <link rel="stylesheet" href="slider.css"> <?= $this->stop() ?> <?= $this->section('scripts') ?> <?= $this->parent() ?> <!-- senpai!! (^o^)/ --> <script src="slider.js"></script> <?= $this->stop() ?>
然后你可以使用 put
方法将其包含在 home
页面视图 中。
<!-- Stored in path/to/views/pages/home.block.php --> <?= $this->extend('master') ?> <?= $this->section('stylesheets') ?> <?= $this->parent() ?> <link rel="stylesheet" href="home.css"> <?= $this->stop() ?> <?= $this->section('scripts') ?> <?= $this->parent() ?> <script src="home.js"></script> <script> initHomepage() </script> <?= $this->stop() ?> <?= $this->section('content') ?> <div class="container"> <?= $this->put('partials.slider') ?> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Officiis, mollitia ad commodi. Eligendi saepe unde iusto quis, praesentium deleniti eos incidunt quas vero, voluptatem, reiciendis inventore, aliquam expedita et rerum. </p> </div> <?= $this->stop() ?>
现在,如果你 echo $block->render('pages.home')
,输出应该如下所示
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Default Title</title> <link rel="stylesheet" href="bootstrap.css"> <!-- senpai!! \(^o^) --> <link rel="stylesheet" href="slider.css"> <link rel="stylesheet" href="home.css"> </head> <body> <header> <h1>App Name</h1> </header> <div id="content"> <div class="container"> <!-- notice me senpai!! \(^o^)/ --> <div class="widget widget-slider"> <div class="slider-wrapper"> <div class="slide-1">Slide 1</div> <div class="slide-2">Slide 2</div> <div class="slide-3">Slide 3</div> </div> </div> <p> Lorem ipsum dolor sit amet, consectetur adipisicing elit. Officiis, mollitia ad commodi. Eligendi saepe unde iusto quis, praesentium deleniti eos incidunt quas vero, voluptatem, reiciendis inventore, aliquam expedita et rerum. </p> </div> </div> <footer> © 2016 - my app </footer> <script src="jquery.js"></script> <script src="bootstrap.js"></script> <!-- senpai!! (^o^)/ --> <script src="slider.js"></script> <script src="home.js"></script> <script> initHomepage(); </script> </body> </html>
注意: slider.css
和 slider.js
是按此顺序放置的。
添加目录命名空间
你可以在 setDirectory
方法中将第二个参数放入设置命名空间目录。
例如,你有一个具有自己的视图目录的模块 admin。
$block->setDirectory('path/to/admin/views', 'admin'); // then you can render it like this $block->render('admin::pages.dashboard'); // and extend or put something in your view files like this $this->extend('admin::master'); $this->put('admin::partials.some-chart');
视图组合器
我们之前告诉过您,块是受 Blade 启发的。所以,Block 也像 Blade 一样有视图组合器。
有时你可能有一个具有其自己数据的视图部分。例如,考虑一下导航栏。在导航栏中,你想要显示登录用户的名称。所以基本上,你需要将用户名称传递给所有渲染该导航栏的视图。或者,你也可以在导航栏视图中设置用户数据。但是,在视图文件中设置数据是一种不好的做法。
所以,解决方案是使用视图组合器。通过组合器,你可以在渲染视图之前向其中添加一些数据。
以下是一个示例
首先,你需要使用 composer
方法为导航栏注册视图组合器。
$block->composer('partials.navbar', function($data) { // $data is data you passed into `render` or `put` method return [ 'username' => Auth::user()->username ]; });
然后在你的导航栏中,你可以这样做
<!-- Stored in path/to/views/partials/navbar.block.php --> <nav> <li>Some menu</li> ... <li> <?= $username ?> </li> </nav>
所以现在,每次导航栏被渲染时,组合器都会设置变量 username
。
如果你想将组合器设置到多个视图中,你可以将第一个参数设置为数组。
组件和槽
这是 Laravel 5.4 中的新功能,它受 Vue.js 启发。有时你可能有一个包含动态 HTML 的部分视图。使用 put
方法,你可以在视图数据中添加 HTML 字符串(put
的第二个参数)。但是,将 HTML 代码放入字符串中是一种不好的做法,大多数文本编辑器都无法突出显示它。
所以,这个特性允许你编写将转换为部分视图中变量的 HTML。
考虑一下警告,你可能有一个包含动态 HTML 的警告,如下所示
<!-- Stored in path/to/views/partials/alert.block.php --> <div class="alert alert-<?= $type ?>"> <h4><?= $title ?></h4> <?= $slot ?> </div>
使用 put
方法,你需要传递 slot
和 title
变量,如下所示
<?php $this->put('partials.alert', [ 'title' => 'Validation Errors <strong class="close">×</strong>', 'type' => 'danger', 'slot' => ' <ul> <li>Email is required</li> <li>Password is required</li> </ul> ' ]); ?>
将 HTML 放入字符串中看起来很丑。使用组件和槽,你可以像这样放置 alert
视图
<?= $this->component('partials.alert', ['type' => 'danger']) ?> <?= $this->slot('title') ?> Validation Errors <strong class="close">×</strong> <?= $this->endslot() ?> <ul> <li>Email is required</li> <li>Password is required</li> </ul> <?= $this->endcomponent() ?>
现在 component
中的代码将转换为 slot
变量,而 slot('title')
中的代码将转换为 title
变量。
使用数据扩展
例如,你有一些情况,某些页面使用侧边栏,而某些页面则不使用它。你可以将数据传递给 extend
方法,这样你就不需要在控制器中传递它。
例如
母版视图
<!-- Stored in path/to/views/master.block.php --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title><?= $title ?></title> <?= $this->section('stylesheets') ?> <link rel="stylesheet" href="bootstrap.css"> <?= $this->show() ?> </head> <body> <header> <h1>App Name</h1> </header> <div id="content"> <?= false === $get('sidebar') ? $this->put('sidebar') : '' ?> <?= $this->get('content') ?> </div> <footer> © 2016 - my app </footer> <?= $this->section('scripts') ?> <script src="jquery.js"></script> <script src="bootstrap.js"></script> <?= $this->show() ?> </body> </html>
页面视图
<?= $this->extend('master', ['sidebar' => false]) ?> <?= $this->section('content') ?> <p> Your page content goes here </p> <?= $this->stop() ?>
追加部分
而不是这样做
<?= $this->section('scripts') ?> <?= $this->parent() ?> <script>alert('my scripts')</script> <?= $this->stop() ?>
你可以这样做
<?= $this->append('scripts') ?> <script>alert('my scripts')</script> <?= $this->stop() ?>
预置部分
而不是这样做
<?= $this->section('scripts') ?> <script>alert('my scripts')</script> <?= $this->parent() ?> <?= $this->stop() ?>
你可以这样做
<?= $this->prepend('scripts') ?> <script>alert('my scripts')</script> <?= $this->stop() ?>
一次编写代码
例如,您想为 datepicker
创建部分视图,即使您多次使用 put()
,也只想将日期选择器脚本放置一次,您可以使用 once()
方法。
示例
<!-- Stored in path/to/views/partials/datepicker.block.php --> <div class="input-group"> <div class="input-group-prepend"> <span class="input-group-text"><i class="fa fa-calendar"></i></span> </div> <input type="text" class="form-control datepicker" name="<?= $name ?>"/> </div> <?= $this->append('styles') ?> <?= $this->once('datepicker-style') ?> <link rel="stylesheet" href="path/to/datepicker.min.css"> <?= $this->endonce() ?> <?= $this->stop() ?> <?= $this->append('scripts') ?> <?= $this->once('datepicker-script') ?> <script src="path/to/datepicker.min.js"></script> <script> $('.datepicker').datepicker(); </script> <?= $this->endonce() ?> <?= $this->stop() ?>
采用这种方法,无论您多少次使用 put('partials.datepicker')
,日期选择器脚本和 CSS 只会渲染一次!
点还是斜杠?
我喜欢 blade 模板引擎,但并不是在所有项目中都能使用 blade,特别是在小型项目中。因此,我创建了此库,使其尽可能类似于 blade。
在 blade 中,您可以使用 /
或 .
来加载视图文件。所以块也是如此。但我们更倾向于您使用 .
而不是 /
,这样您更容易记住在块中不需要放置视图扩展名。