dakujem / latter
PSR-7框架和堆栈的Latte视图层。
Requires
- php: >=7.4
- psr/http-message: ^1.0
Requires (Dev)
- dakujem/sleeve: ^1.1
- latte/latte: ^2.5
- nette/tester: ^2.3.1
- nyholm/psr7: ^1.2
- slim/slim: ^4
This package is auto-updated.
Last update: 2024-08-29 10:21:25 UTC
README
PSR-7框架和堆栈的Latte视图层。这不是一个打字错误。
💿
composer require dakujem/latter
要使用PSR-7兼容框架(如Slim)与出色的Latte模板语言一起使用,一个人可以选择自己设置所有内容或使用Latter。
后者是一个更好的选择
- Latter旨在减少代码重复(渲染管道、渲染例程)
- Latter是一个灵活的薄层,可以根据需求进行调整和驯服
- 一次设置,以后不用管
📖
查看Latte文档以熟练掌握Latte。
将Latte模板渲染为PSR-7响应
以下是一个非常基本的示例,用于渲染类似以下内容的Latte模板
{*} hello.latte {*} Hello {$name}!
在Slim框架中使用Latter将会是
// app.php $app = AppFactory::create(); // ... $app->get('/hello/{name}', function (Request $request, Response $response, array $args) { $params = [ 'name' => $args['name'], ]; return (new Dakujem\Latter\View)->render( $response, __DIR__ . '/templates/hello.latte', $params, new Latte\Engine ); }); // ... $app->run();
当然,在上述情况下使用Latte会有些过度。
大多数情况下,一个人将使用Latte来处理更复杂的模板,其中包含多个变量、过滤器宏。
在这种情况下,在服务容器中定义一个Latter\View
服务进行依赖注入是有意义的。
配置Latte\Engine工厂服务
首先,让我们创建一个服务容器。
我将使用Sleeve,这是Symfony Pimple容器的一个简单扩展。
$container = new Dakujem\Sleeve();
在大多数情况下,应该为每个模板渲染创建一个新的Latte\Engine
实例。这就是为什么应该定义一个工厂服务。也就是说,每次从服务容器请求服务时,都会返回一个新的实例。
💡
请查看您所使用的服务容器或框架的文档,以正确配置此步骤。
$container->set('latte', $container->factory(function () use ($container) { $engine = new Latte\Engine(); // Configure the file loader to search for templates in a dedicated directory. $loader = new Latte\Loaders\FileLoader(__DIR__ . '/templates'); $engine->setLoader($loader); // Set a temporary directory, where compiled Latte templates will be stored. $engine->setTempDirectory($container->settings['view-temp-dir']); return $engine; }));
定义应包含
现在每次我们调用$container->get('latte')
时,都会返回一个配置好的Latte\Engine
的新实例
(new Dakujem\Latter\View)->render( $response, 'hello.latte', $params, $container->get('latte') );
请注意,由于FileLoader
的配置,我们不再需要将模板名称的前缀设置为完整路径。
配置Latter\View服务
现在让我们定义一个Latter\View
服务。
$container->set('view', function () use ($container) { $view = new Dakujem\Latter\View(); // optionally set an engine factory (recommended) $view->setEngine(function () use ($container): Latte\Engine { return $container->get('latte'); }); return $view; });
Latter\View
实例现在将在服务容器中可用
$view = $container->get('view');
如果向View
服务提供了引擎工厂,则可以省略为每个渲染的模板提供Engine
实例。
// the render calls have gotten shorter: $view->render($response, 'hello.latte', $params);
View
服务定义可以包含以下可选定义
- 模板别名
- 渲染例程(模板渲染)
- 渲染管道
- 引擎工厂
- 默认参数
- 默认渲染例程
这些都是可选的。
模板别名
可以创建模板别名,以便可以使用不同的名称引用模板。
$view->alias('hello', 'hello.latte'); $view->alias('index', 'ClientModule/Index/default.latte');
使用别名渲染模板
$view->render($response, 'hello', $params); $view->render($response, 'index', $params);
渲染例程
渲染例程应该用于在不重复代码的情况下应用特定模板的设置。
它们可以用于
- 定义过滤器
- 定义标签(宏)
- 修改输入参数
- 修改模板名称
- 或者甚至使用完全不同的引擎实例或渲染自己的响应
渲染例程是一个 可调用的,它接收一个 Runtime
上下文对象,并返回一个 响应,其签名如下
function(Dakujem\Latter\Runtime $context): Psr\Http\Message\ResponseInterface | Dakujem\Latter\Runtime
示例
$view->register('shopping-cart', function (Runtime $context) { // This callable is the place to register filters, // variables and stuff for template named "shopping-cart" // Do any setup of the Engine that is needed for the template to render correctly $latte = $context->getEngine(); $latte->addFilter('count', function(){ // return the count of items in the shopping cart here }); // Template name can be set or changed freely. // Note that if one only needs to set a nice name for the template to be rendered, // aliases are a simpler option to do so $template = 'ClientModule/Cart/list.latte'; // The params can be modified at will, for example to provide defaults $params = array_merge(['default' => 'value'], $context->getParams()); // the Runtime::toResponse helper method can be used for default rendering return $context->withTarget($template)->withParams($params); });
可以像渲染别名一样渲染例程
$view->render($response, 'shopping-cart', $params);
默认渲染例程
可以可选地注册一个默认渲染例程,该例程将用于所有未注册的模板。
$view->registerDefault( function (Runtime $context) { ... } );
默认渲染例程的签名与命名例程完全相同。
在渲染未注册的模板时将使用它。
$view->render($response, 'a-template-with-no-registered-routine', $params);
默认参数
默认参数将与每个渲染调用提供的参数合并。
如果想要为每个模板定义默认参数,可以使用渲染例程。
$view->setParam('userName', 'Guest'); // a single parameter $view->setParams([ 'userName' => 'Guest', 'projectName' => 'My Awesome Project', ]); // all parameters at once
渲染管道
管道允许在最终渲染之前依次调用多个 预渲染 例程。
例程可以在具有共同布局、共同包含文件(include)、共同设置(过滤器、变量)或其他渲染逻辑的多个模板渲染调用之间共享。
最明显的例子是 布局 或常见的 文件 / 块 包含。
首先,必须注册适当的预渲染例程
$view->register('base-layout', function (Runtime $context) { // do setup needed for templates using the base layout $context->getEngine()->addFilter( ... ); // return a context object (!) return $context; }); $view->register('--withUser--', function (Runtime $context) { // do setup common for templates using a `$user` variable $defaults = [ 'user' => get_user( 'somehow' ), ]; // return a context object (!) return $context->withParams($defaults); });
对于在管道中使用的预渲染例程,返回一个 Runtime
上下文对象非常重要。如果返回了一个 Response
,管道将提前结束(尽管在某些情况下这可能是有意的)。任何其他类型的返回值都将被忽略。
使用管道进行渲染的调用可能如下所示
// calling a pipeline with 2 _pre-render_ routines and a registered render routine $view ->pipeline('base-layout', '--withUser--') ->render($response, 'shopping-cart', $params); // rendering a file with a common _pre-render_ routine $view ->pipeline('--withUser--') ->render($response, 'userProfile.latte', $params);
当处理包含的模板(页眉、页脚)或需要特定变量或过滤器进行渲染的布局模板时,管道尤其有用。
示例
{*} home.latte {*} {layout 'base.latte'} {block #content} <p>Greetings, stranger!</p>
{*} about.latte {*} {layout 'base.latte'} {block #content} <p>Stay awhile and listen.</p>
上述两种情况都使用相同的布局,该布局需要在 'base-layout'
预渲染例程中进行特定的设置
$view->pipeline('base-layout')->render($response, 'home.latte'); $view->pipeline('base-layout')->render($response, 'about.latte');
这种渲染可以与在渲染前对模板进行标记或装饰进行比较。
或者,也可以将管道定义为渲染例程的一部分
$view->register('contacts.latte', $view->pipeline('base-layout', function (Runtime $context) { // ... do whatever setup needed for rendering the contacts page return $context->withParams(['foo' => 'bar']); }));
{*} contacts.latte {*} {layout 'base.latte'} {block #content} <p>Contact: {$foo}</p>
$view->render($response, 'contacts.latte');
显式链式调用
有时希望在另一个渲染例程中调用一个渲染例程。这可以通过使用 View::another
或 View::execute
实现。
// register a routine named 'ahoy', that will render `hello.latte` $view->register('ahoy', function (Runtime $context) { return $context->withTarget('hello.latte'); }); // register a routine that will internally invoke it $view->register('foo', function (Runtime $context) use ($view) { return $view->another($context, $view->getRoutine('ahoy')); }); // render 'hello.latte' using 'foo' routine that internally uses 'ahoy' routine $view->render($response, 'foo');
请注意,这些方法不仅限于使用已注册的例程,它们可以执行任何签名匹配的可调用对象。
技巧 & 小贴士
性能
Latte 模板在第一次渲染时编译。所有后续渲染都将使用编译和优化的 PHP 代码。
为了在生产服务器上略微提高性能,可以在引擎工厂中关闭自动刷新
$engine->setAutoRefresh($container->settings['dev'] ?? true);
这有其局限性,请事先阅读 Latte 文档。
使用 {link} 和 n:href 宏与 Slim 框架配合使用
可以定义类似于 Nette 框架中宏的 {link}
和 n:href
宏。这些宏将为 命名路由 生成 URL。
首先确保在 Latte 引擎中注册了一个生成 URL 的 过滤器,然后创建一个使用该过滤器的 宏。
$app = Slim\Factory\AppFactory::create(); $engine = new Latte\Engine(); // The section below configures the `{link}` and `n:href` macros and the `urlFor` filter. $engine->addFilter('urlFor', function ($route, ...$args) use ($app) { // the filter will call the `urlFor` method of the route parser return $app->getRouteCollector()->getRouteParser()->urlFor($route, ...$args); // Note: if you are using slim v3, use `$container->get('router')->pathFor( ... )` instead. }); $macroSet = new MacroSet($engine->getCompiler()); $linkMacro = function (MacroNode $node, PhpWriter $writer) { return $writer->using($node)->write('echo ($this->filters->urlFor)(%node.word, %node.args?);'); }; $macroSet->addMacro('link', $linkMacro); $macroSet->addMacro('href', null, null, function (MacroNode $node, PhpWriter $writer) use ($linkMacro) { return ' ?> href="<?php ' . $linkMacro($node, $writer) . ' ?>"<?php '; });
以上操作最好在
'latte'
服务定义期间完成。查看更多详情,请参见 测试。
然后可以使用宏
{*} named routes without route parameters {*} {link home} {*} named routes wit route parameters {*} {link hello [name => $name]} {*} named routes with route parameters and query parameters {*} {link rc [resource => apple, action => eat], [param1 => val1, param2 => val2]} {*} n:href macro has the same syntax {*} <a n:href='home'>go home</a> <a n:href='hello [name => hugo], [a => b]'>polite hello</a> {*} using the filter is of course possible too {*} {='home'|urlFor}
💡
注意与 Nette 框架的区别 - 第一个宏参数必须是一个数组。
为了使查询参数生效,在宏中使用%node.args?
。可以通过将%node.args?
替换为%node.array?
来去除查询参数,转而使用精确的 Nette {link} 语法。
在渲染前设置引擎
渲染例程的意图是在渲染特定模板之前提供一个设置 Latte\Engine
实例的位置,然而,可以使用管道快速设置引擎。
$view->pipeline(function(Runtime $context) { $context->getEngine()->addFilter( ... ); })->render($response, 'myTemplate.latte', ['param' => 'value']);
贡献
... 是受欢迎的。🍵
请继续分叉仓库,做出你的更改,然后提交你的 PR。