emsifa/block

PHP原生模板系统

v2.4.0 2018-03-28 14:09 UTC

This package is auto-updated.

Last update: 2024-09-22 01:43:37 UTC


README

Build Status License

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_extensionphp。我们更倾向于使用自定义扩展名。自定义扩展名使你更容易在编辑器中识别视图文件,而无需打开该文件。

在这个例子中,我们使用 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>
    &copy; 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。这是可以的,因为这些方法不返回任何值。

创建页面视图

在上面的主视图中,有 stylesheetscontentscripts 块。因此,你需要在页面视图中定义它们。

<!-- 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>
    &copy; 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 &lt;script&gt;XSS.attack()&lt;/script&gt;</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>
    &copy; 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.cssslider.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 方法,你需要传递 slottitle 变量,如下所示

<?php

$this->put('partials.alert', [
    'title' => 'Validation Errors <strong class="close">&times;</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">&times;</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>
    &copy; 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 中,您可以使用 /. 来加载视图文件。所以块也是如此。但我们更倾向于您使用 . 而不是 /,这样您更容易记住在块中不需要放置视图扩展名。