olveneer/twig-components-bundle

一个轻量级的symfony扩展包,它提供了一种简单的方法将模块化、组件结构应用到您的twig模板中。

安装: 105

依赖者: 0

建议者: 0

安全: 0

星星: 30

关注者: 2

分支: 4

开放问题: 8

类型:symfony-bundle


README

Total Downloads

twig-components-bundle

一个轻量级的symfony扩展包,它提供了一种简单的方法将模块化、组件结构应用到您的twig模板中。

关于

当开发前端时,我发现我很快地使用Vue和React等框架。最近我更多地关注于使用Symfony的后端编程,很快我就发现我缺少Vue提供的组件结构。当然,你有了块和所有这些,但它们仍然缺乏某些Vue以其自身方式提供的核心功能。

因此,我制作了这个包来恢复这些功能,我认为它工作得很好。总是欢迎建议或反馈! olvenmage@live.nl

安装

 composer require olveneer/twig-components-bundle

使用

每个组件都需要一个特殊的服务,该服务扩展了类或实现了

 /**
  * Returns the parameters to be used when rendering the template.
  * Props can be provided when rendering the component to make it more dynamic.
  *
  * @param array $props
  * @return array
  */
 public function getParameters(array $props)
 {
     return [
       'test' => 123
     ];
 }

一个返回您twig组件参数的函数。你问这些$props是什么?嗯,组件的主要优势是它们可以被重复使用多次。然而,你很少需要完全相同的组件,所以props作为选项来使参数更加动态。困惑?不用担心,稍后一切都会变得更加清晰。

 /**
  * Configures the props using the Symfony OptionResolver
  *
  * @param OptionsResolver $resolver
  */
 public function configureProps(OptionsResolver $resolver)
 {
    $resolver->setDefaults(['someProp' => 'someValue']);
 }

可以使用configureProps方法确保组件始终按预期运行。如果你一直使用symfony,这应该不会是什么新鲜事。

让我们确定我们的组件名称:假设你的组件上的get_class()结果是App/Component/TestComponent。然后我们取类名而不带命名空间,所以在这个例子中是TestComponent,接下来,我们将其转换为snake_case,最终名称结果是test_component

如果你想自定义名称,使用

/**
 * Returns the string to use as a name for the component.
 *
 * @return String
 */
public function getName()
{
    return 'another_name';
}

现在,这些服务不是自动注册为Twig组件,你需要使用额外的olveneer.component标签来注册服务。示例

app.test_component:
        class: App\Component\TestComponent
        tags: ['olveneer.component']

如果你使用autoconfigure,这会自动为扩展类的任何服务发生!

这会将组件目录中的每个服务标记为Twig组件。

渲染

当你已经配置了组件类,是时候创建模板了。模板的位置应该在templates/components/<component_name>.html.twig中。(你也会得到一个错误消息说这个)。

'/components'部分可以在配置文件中进行配置,但稍后我们会谈到这一点。

文件应该叫什么名字?当然是组件名称!所以在这个例子中是test_component.html.twig

在这里,你可以访问你发送回的所有参数。

太好了!现在你怎么把模板放在屏幕上呢?很简单!

有两种方法可以做到这一点。

方法A:在你的模板中使用twig函数渲染它。
方法B:从你的控制器/服务中渲染它。

我们先看看方法A。

在任何模板中,你都可以使用以下语法

{% get name %} {% endget %}
这会渲染组件而不传递props,要这样做,使用这个

{% get name with { someProp: 'someValue' } %} {% endget %}

方法B是将Component注入到您的服务或控制器中,如果您只想获取HTML,则可以调用renderComponent( $props = [])函数,或者调用render($props = [])以立即获取浏览器响应。

示例

/**
 * @Route(path="/")
 * @param TestComponent $testComponent
 * @return \Symfony\Component\HttpFoundation\Response
 * @throws \Twig_Error_Loader
 * @throws \Twig_Error_Runtime
 * @throws \Twig_Error_Syntax
 */
public function test(TestComponent $testComponent)
{
    return $testComponent->render();
}

$component->render($props = []);

插槽系统

TwigComponents还内置了传递HTML而不是仅传递变量的功能。

有两种类型的{% slot %}标签。首先,那些存在于{% get %}标签内部的标签。这些标签之间的HTML将被插入到正在渲染的组件中。其次,那些存在于{% get %}标签中的{% slot %}标签。这些标签定义了一个HTML/Twig内容可以插入的位置,中间的值是默认值。

假设您有一个名为'card'的组件,使用card.html.twig模板文件渲染bootstrap卡片。

// card.html.twig

<div class="card"> 
    <div class="card-header">
        {% slot header %}
             <h1> default header value </h1>
         {% endslot %}
    </div>
    <div class="card-body">
        {% slot body %}
             <h1> default body value </h1>
         {% endslot %}
    </div>
</div>

如果您熟悉Vue或React,您可能已经意识到发生了什么。这个组件正在请求某些标记块。您在插槽块之间看到的值是在没有插槽的情况下默认值。

现在我们想在我们的模板中使用这个卡片组件

// index.html.twig

<body>
    {% get card %}
        {% slot header %}
            <div> My own html! </div>
        {% endslot %}
    {% endget %}
</body>

这将导致渲染mainComponent,其中头部插槽将用<div> My own html! </div>填充,而主体默认为<h1> default body value </h1>

当然,我们也可以像这样将我们的主体插槽插入其中

// index.html.twig

<body>
    {% get card %}
        {% slot header %}
            <div> My own html! </div>
        {% endslot %}
        
        {% slot body %}
            <div> A cool body </div>
        {% endslot %}
    {% endget %}
</body>

不仅可以从{% get %}传递值到组件中,{% slot %}还可以暴露某些变量。以下是一个示例

// parent.html.twig
{% for item in items %}
    ...
{% endfor %}

{% slot body expose { products: items, hello: 'Hi!!!' } %} {% endslot %}

//child.html.twig
{% get parent %}
    {% slot body %}
        <div> ... </div>
        <h1> {{ hello }} </h1>
        
        {% for product in products %}
            ...
        {% endfor %}
    {% endslot  %}
{% endget %}

如您所见,您暴露的变量可以在{% slot %}标签中使用!请注意,您暴露的变量仅在那些标签之间存在!

混入

有时您有一些您希望在大多数组件中使用的特定属性或参数。为了避免代码重复,您可以使用像这样的混入

// SomeComponent.php

public funciton getParameters()
{
    ...
}

public function importMixins()
{
    return [SomeMixin::class];
}

如您所见,使用非常简单。现在让我们看看一个混入

// SomeMixin.php
class SomeMixin extends TwigComponentMixin
{

    /**
     * @return array
     *
     * Merges with the parameters.
     */
    public function getParameters()
    {
        return [];
    }

    /**
     * @return array
     *
     * Merges with the props.
     */
    public function getProps()
    {
        return [];
    }

    /**
     * @return int
     *
     * The execution order of all the mixins. Mixins with the same key override the earlier ones.
     * Lower goes first.
     */
    public function getPriority()
    {
        return 0;
    }
}

混入只是另一个服务,所以在这里您可以注入并使用您想要的任何东西。当混入被解析时,其返回的参数将与使用array_merge的组件合并。

混入必须在您的services.yaml中使用olveneer.mixin标签进行注册。如果您使用自动配置,则任何扩展TwigComponentMixin类的服务都会自动发生!

最佳实践

以下是一些最佳实践,以获得最佳长期更新支持和整体代码优化。

  • 尽可能使用默认的getName()(返回短类名)。
  • 如果适用,请使用configureProps()方法。
  • 使用一些特定配置处理边缘情况的同时,使用之前提到的组件自动配置。
  • 使用蛇形命名法为自定义组件命名。例如:some_component_name
  • 在/src下为混入和组件创建两个单独的文件夹。

配置

现在我们终于来到了配置部分,我将向您展示一个示例配置文件。

// config/packages/olveneer_twig_components.yaml

olveneer_twig_components:
    components_directory: '/components'

关于嵌入呢?一些人可能会想,“为什么不使用twig的嵌入呢?”

嗯,有一些明显的区别

嵌入使用块标签而不是我们酷炫的插槽标签,那里的区别在于,一个块只能被覆盖一次,这是合逻辑的,因为块相当静态。然而,插槽标签可以暴露变量,供覆盖的标签使用,因此它们可以非常动态。因此,这是可行的

 // parent.html.twig
 <table>
      <tr>
           <th></th>
           <th></th>
      </tr>
      <tr>
           {% for item in items %}
                {% slot body expose {item: item} %}
                {% endslot %}
           {% endfor %}
      </tr>
 </table>

 // child.html.twig

 {% get parent %}
      {% slot body %}
           <td> {{ item.name }} </td>
           <td> {{ item.text }} </td>
      {% endslot %}
 {% endget %}

注意:此示例灵感来自vuetify。他们在组件中大量使用这个。

此外,组件可以非常独立。当使用嵌入式组件时,您必须在{}中传递它们所需的参数。这可能导致您必须将它们注入并传递到每个希望嵌入模板的模板中。使用组件时,它们可以自行处理,只需请求props即可使用。

即将推出

  • 欢迎提出建议!