twigphp / Twig

Twig, the flexible, fast, and secure template language for PHP
https://twig.symfony.com/
BSD 3-Clause "New" or "Revised" License
8.13k stars 1.24k forks source link

What would you like to see as major changes that would lead to a BC break for the next major release of Twig? #3951

Open fabpot opened 8 months ago

fabpot commented 8 months ago

We've started working on the next major Twig release. As announced last year, this new version will probably land under the Symfony mono-repository.

For the first time in Twig history, I'd like to fix the major issues that would be BC breaks and that we've never done.

This issue acts as a community wish list for things you want to see in the next major version that could lead to BC breaks (hint: like operators precedence).

Feel free to comment and vote.

smnandre commented 8 months ago

We could drop TwigExtraBundle and move all integrations to TwigBundle.

Not sure if that would be a BC break, but i indeed would love to use html_classes without any extra bundle / package :)

pbowyer commented 8 months ago

Having all extensions (filters, functions etc) lazy loaded by default (RuntimeExtension ?) would remove a performance footgun. Combined with this increasing the "Twig Standard Library" by moving usuful features from TwigExtraBundle to TwigBundle would be great.

I second the call for a components. I know the UX team is doing good work, but tighter integration into Twig would be helpful. I like what Blade is doing with components, and Phoenix (Elixir).

This one is more tooling rather than Twig, but if PhpStorm could reliably detect what objects I'd passed into my template - and even better that I'm iterating over inside a {% for %} loop it would be a massive productivity boost.

pbowyer commented 8 months ago

Re the talk around twig.js et al:

Selfishly a grammar for Twig would be great because I'm sick of using alternative template engines in other languages (Liquid, Nunjucks etc) and finding them all inferior to Twig. It's a testament to how good we have it that Twig is a gold standard template engine.

tacman commented 8 months ago

I love Twig Components. If they were integrated directly in Twig, we could deprecate macros, or at least strongly encourage components over macros. Between wrapping a call to a block with a with: {} and Twig Components, I think I've removed all macros from my projects.

alexander-schranz commented 8 months ago

Based on @smnandre comment about html_classes I think that twig extension maybe could be removed when we have something native in Twig for PHP match Syntax.

75th commented 8 months ago

I forgot about my biggest request in this genre:

The |raw filter, instead of being magic, should convert its input into a TwigMarkup object. You should be able to do {% set var = var|raw %} or provide { var: var|raw } to an include or embed without doing |raw again on the receiving end.

Perhaps there are security concerns with this? But I can't see how those would be valid, because you can always just do this:

{% set var %}{{ var|raw }}{% endset %}

...which is identical in function but way too many characters.

alexander-schranz commented 7 months ago

I would like that twig supports comments inside twig expressions.

{% set class = [
    'border border-greys-200 h-9 text-greys-500 rounded py-2 px-3 block w-full text-sm shadow-sm focus:outline-none focus:ring-1 ring-secondary-300 focus:border-secondary-300 rounded bg-greys-100',
    'disabled:text-greys-200',
    type != 'search' ? '' : 'pl-9', {# same as w-5 + 2x mr-2 #}
]|join(' ') %}

Other examples:

{% if someCondition {# some comment #}
    or otherCondition {# some comment more #}
%}

{% endif %}
{% set object = {
    key: someValue, {# some comment #}
%}
{{ include('some.html.twig', {
    key: someValue, {# some comment #}
}) }}
ThomasLandauer commented 7 months ago

Just another wish on comments: A quick way to comment out many lines (that may contains comments themselves), e.g. by wrapping them inside {## ... ##}. I already suggested this at https://github.com/twigphp/Twig/issues/2897

alexander-schranz commented 7 months ago

What did come to my mind that I could think about that @VincentLanglet would have maybe some important Input abouts Twig Tokenizer be more reusable for awesome tools like https://github.com/VincentLanglet/Twig-CS-Fixer, which currently if I remember have to recreate there own Tokinizer.

VincentLanglet commented 7 months ago

What did come to my mind that I could think about that @VincentLanglet would have maybe some important Input abouts Twig Tokenizer be more reusable for awesome tools like VincentLanglet/Twig-CS-Fixer, which currently if I remember have to recreate there own Tokenizer.

In https://github.com/VincentLanglet/Twig-CS-Fixer I wrote my own

For the tokenizer/token, I'm more specific than the default Twig Lexer, for instance

If I want to use the default Lexer with some changed, I would need

Thanks for thinking about it @alexander-schranz but I'm not sure it would something easy to change in twig code.

But one great step would be to split the Lexer into smaller services/methods reusables. For instance the getOperatorRegex method or other similar regex constants/methods could be in separate service or provide static methods. This way I wouldn't need to duplicate them.

tacman commented 7 months ago

I would love to see a test for array_is_list instead of just iterable.

This would simply return the php array_is_list function result: https://www.php.net/manual/en/function.array-is-list.php

testing is object would (I think) be the same as is not array_is_list, and object / associative array in php/twig are really the same. But knowing that a value is a list would be very helpful.

xenocrat commented 7 months ago

I am the maintainer of chyrp-lite, which uses Twig for templating. I'd like to be able to use Twig 4.x without having to pull in many additional dependencies. Mention of the Symfony mono-repo concerns me, because having to pull in big chunks of Symfony to use Twig 4.x would make it unsuitable for my project.

fabpot commented 7 months ago

I am the maintainer of chyrp-lite, which uses Twig for templating. I'd like to be able to use Twig 4.x without having to pull in many additional dependencies. Mention of the Symfony mono-repo concerns me, because having to pull in big chunks of Symfony to use Twig 4.x would make it unsuitable for my project.

Moving Twig to the Symfony mono-repo would not change anything (just the package name).

tacman commented 7 months ago

Mention of the Symfony mono-repo concerns me,

As I understand it, the monorepo publishes each component / bundle in its own repo, with its own minimal dependencies. So it shouldn't make any difference for users of the twig package, only the twig developers.

alexander-schranz commented 7 months ago

At the end twig currently requires the packages which are currently defined in its composer.json: https://github.com/twigphp/Twig/blob/594c548b9224b06683806806875d1583dcb7a59b/composer.json#L28-L31. So when move twig, twig-extra, ... into monorepository twig core would still have the same dependencies as it is build on top of them, so the dependencies for twig are unrelated if twig is maintained over a monorepository or not.

So instead of twig/twig it maybe symfony/twig nothing will force you to install the whole symfony/framework to use twig.

khalwat commented 7 months ago

I'd love to see full unrestricted arrow function support as per: https://github.com/twigphp/Twig/issues/3402

This would be especially useful for Laravel, Craft, Drupal and other frameworks that use Twig & Collections, but it would be useful in non-Collections use-cases as well.

It would also eliminate the need for work-arounds like Craft Closure

khalwat commented 7 months ago

I'd like to see an Empty Coalescing operator added to Twig as well.

Empty Coalesce adds the ??? operator to Twig that will return the first thing that is defined, not null, and not empty. This is particularly useful when you're dealing with a number of fallback/default values that may or may not exist, and may or may not be empty.

The ??? Empty Coalescing operator is similar to the ?? null coalesce operator, but also ignores empty strings (""), 0, 0.0, null, false, and empty arrays ([]) as well.

Because this is an Empty Coalesce Operator, it functions identically to the PHP empty() function in terms of return values.

Example:

{% set bar = null %}
{% set foo = '' %}
{% set baz = [] %}
{% set foobar = woof ??? bar ??? foo ??? baz ??? 'bark' %}
{{ foobar }}

This will output:

bark

...because:

There is precedence for this operator in languages such as Swift:

https://medium.com/@JanLeMann/providing-meaningful-default-values-for-empty-or-absence-optionals-via-nil-or-empty-coalescing-379abd22ae77

stof commented 7 months ago

@khalwat there is already a |default filter that does almost that: it uses the twig definition of "empty" rather than the PHP one (so that the string '0' is not considered empty)

khalwat commented 7 months ago

@stof I'm aware of the |default filter, but it's the same argument as before as to why it isn't a good solution for the above example.

Try writing this:

{% set foobar = woof ??? bar ??? foo ??? baz ??? 'bark' %}

...using the |default filter.

smnandre commented 7 months ago

@khalwat by genuine curiosity, when would you use a chain of defaults like that ?

jan-dh commented 7 months ago

it would be great if twig had an up-to-date formatter (maybe with a prettier plugin maintained by core team)

An up-to-date formater for vsCode would be really, really welcome.

alexander-schranz commented 7 months ago

@smnandre

curiosity, when would you use a chain of defaults like that ?

In case of project I did review with @sulu there are some cases people require different fallbacks for optional fields in the system. Example like seo.title|default(excerpt.title)|default(content.title), is something which appeared there from time of time. You mostly don't need this in typical applications but for CMS based systems with dynamic content where such data isn't provided by a PHP class you require more usage of |default currenlt instead of have helper methods on your domain models.

davidhellmann commented 7 months ago

@khalwat by genuine curiosity, when would you use a chain of defaults like that?

I have used the ??? from Andrew for years now and I would say it has much better readability and DX.

c33s commented 7 months ago

i would love to see multiline comments but i am not sure if we can speak of a BC break. it is more of a "language break" as fabien mentionent in the PR (https://github.com/twigphp/Twig/pull/3986)

lyrixx commented 7 months ago

update the signature of getFilters(): array to getFilters(): iterable same for others extensions points

see https://github.com/twigphp/Twig/blob/3.x/src/Extension/ExtensionInterface.php#L47-L49

stof commented 7 months ago

@lyrixx I don't see how this would help actually. Twig is doing array_merge on those return values IIRC, so returning a generator would not provide any benefit.

alexander-schranz commented 7 months ago

One think come to my mind for getFilters, getFunctions that this requires that all twig extensions services are created.

I was thinking if we make this methods maybe static methods, we could maybe make all twig extension lazy in Symfony and only extension services are instantiated which were really used in that twig file, for getting the available function and filters no instiation is required.

Currently a lot of twig extension are created and the current workaround https://symfony.com/doc/current/templates.html#creating-lazy-loaded-twig-extensions is something every devs would require additional work which nobody doing in the projects.

GromNaN commented 7 months ago

@alexander-schranz using attributes would be a solution for lazy-loading the extensions by default (see https://github.com/twigphp/Twig/pull/3916). The ExtensionSet mecanism would need some deep changes to split the configuration from the runtime (see https://github.com/symfony/symfony/issues/53403 for Yaml configuration example) in a way that still invalidate cache when the configuration is modified.

stof commented 7 months ago

@alexander-schranz Twig already has the runtime loader system to allow lazy-loading the implementation of filters and functions. That's available since Twig 2.x

alexander-schranz commented 7 months ago

@stof As linked I'm aware of the Runtime way, but that something every devs need to take care of and is so not done by the developers I did review projects with @sulu. It would be nice if the lazyness of Extensions work out of the box atleast for Symfony based application where lazy services exists that additional work by the devs is not required.

@GromNaN that sounds kind of interesting.

ddimoia-wt commented 6 months ago

I would like to see support for theme in different dirs. Something like:

alexander-schranz commented 6 months ago

@ddimoia-wt you maybe interestd in using https://github.com/Sylius/SyliusThemeBundle

ericmorand commented 6 months ago

@ddimoia-wt isn't it just a matter of changing the filesystem loader root path?

pgorod commented 6 months ago

I would appreciate some syntactic sugar to simplify arrow functions. They're quite awkward when you first meet them - then you get used to it, ok, but it would be great to make them more friendly to start with.

I am not suggesting to remove the current syntax - it's fine. I am suggesting adding an alternative way to write it.

The part that I would like to get rid of is what lies before the =>. The reason that part is there is to establish the names that will be used in the function code. But for the simplest cases, mere convention could establish that, and there would be no arrows.

{{ sizes|filter(v => v > 38) }}

could be written as (several alternatives for your consideration, pick only one):

  1. {{ sizes|filter(value > 38)}} (well-known names value - and also key if it's a mapping)
  2. {{ sizes|filter(x > 38)}} or {{ sizes|filter(size > 38)}} (not well-known values, but compiler figures that an unknown identifier is the variable name - would work only for a single variable)
  3. {{ sizes|filter(> 38)}} (compiler figures that the missing operand is the variable we want - would only work with a single variable)
  4. {{ sizes|filter($1 > 38)}} (a numbered argument would allow multiple variables and more complex expressions)

This other example from the docs:

{% for k, v in sizes|filter((v, k) => v > 38 and k != "xl") -%}
    {{ k }} = {{ v }}
{% endfor %}

could be written with: {% for k, v in sizes|filter($1 > 38 and $2 != "xl") -%} or {% for k, v in sizes|filter(value > 38 and key != "xl") -%} which is much more readable.

I am sure that some of my suggestions are crap, and possibly even all of them, but I trust one of you who knows more than me can come up with a useful and feasible improvement for arrow functions.

foxtrotcz commented 6 months ago

What about named arguments for macros? See here https://github.com/twigphp/Twig/issues/929

mandrasch commented 6 months ago

@aszenz mentioned it already a bit:

I would like to see a focus on component based templating over overriding/inheritance.

What I miss most with a background from Svelte as frontend developer is Single File Components and the easy way you can develop with them, typehinting, etc. (I develop for Craft CMS now). A small example, it's just easy and everything is pre-defined:

https://github.com/twigphp/Twig/assets/777278/277edcbb-cfc6-4161-b9fe-e5594d9fe966

Setting your own Components Pattern from scratch in Twig is not that hard to be fair, but you need to learn about Includes vs. Embeds vs. Macros, Scoping, etc. etc. And then you need your build steps around it for .scss and .js files, etc. etc.

Big ask, I know - and also very CMS-specific of course + also a question of IDE support.

But a pre-defined, ready-to-use components pattern would be awesome for developers getting started with Twig imho. Thanks very much for providing Twig!

stof commented 6 months ago

@pgorod The issue is that your proposal creates an ambiguous syntax. {{ sizes|filter(value > 38)}} is already a valid syntax that calls a filter filter with a boolean argument being the result of evaluating value > 38. We cannot make the parsing of the syntax be dependent on the signature of filters and static analysis of the expression (anyway, static analysis generally requires having an AST, and so being done with parsing).

pgorod commented 6 months ago

@stof I see your point.

Do you think some simplified arrow function syntax would be possible? Something you come up with, not something directly found in my suggestions.

VincentLanglet commented 6 months ago

But one great step would be to split the Lexer into smaller services/methods reusables. For instance the getOperatorRegex method or other similar regex constants/methods could be in separate service or provide static methods. This way I wouldn't need to duplicate them.

At least what would be great is to introduce more Interface to allow easily overriding some behavior in the tokenization.

For instance https://github.com/twigphp/Twig/blob/b46e93c7257fb01b7c77768210997b1e00643b91/src/Environment.php#L476 https://github.com/twigphp/Twig/blob/b46e93c7257fb01b7c77768210997b1e00643b91/src/Environment.php#L493 https://github.com/twigphp/Twig/blob/3.x/src/Environment.php#L512 could become

public function setLexer(LexerInterface $lexer)
public function setParser(ParserInterface $parser)
public function setCompiler(CompilerInterface $compiler)
Nayte91 commented 6 months ago

Annd that's a nice list! I may have some other little things, but I think those 3 blocks are awesome directions to take. Append/style syntax is a perfect teammate for assetmapper, component oriented helpers are perfect teammates for symfonyUX, post processor helps a lot template designers.

My 2 cents are component oriented twig is futur, as anything you can do with twig to embrace this paradigm is future-proof. Major JS libs (angular react vue etc..) bring components to the world, official standards with web components are taking this path also, and CSS nesting and container queries follow this way either. Next 5 10 years are here.

dknx01 commented 6 months ago

A shorter version for translation would be great. Like {{ foo|trans('domain')}}. So that the domain is the second argument and not the last one. Mostly you need the domain, but no arguments/replacements.

alexander-schranz commented 6 months ago

@dknx01 As I remember correctly twig supports named arguments so you can do {{ foo|trans(domain = 'domain')}} mayb, but for the domain you mostly just want to define it once and not for all your trans calls so you can use the trans_default_domain block: {% trans_default_domain 'domain' %}. See here: https://symfony.com/doc/current/translation.html#translations-in-templates

alexander-schranz commented 6 months ago

@Nayte91

An append syntax that may come along with components, for example if I have a button template/component, I have a CSS file to style my buttons; So I could put on top of my component something like {% style %}{% endstyle%} then when serving the page, twig accumulate all the styles and remove duplicate ones (for example I have 10 buttons on a given page, so the style will be called 10 times, but this twig function knows that only one is OK).

You maybe interesting in some kind of Portal mechanism which is example common in react. See our Portal Twig Extension: https://github.com/sulu/web-twig/blob/2.5/docs/portal.md

Nayte91 commented 6 months ago

@alexander-schranz

You maybe interesting in some kind of Portal mechanism which is example common in react. See our Portal Twig Extension: https://github.com/sulu/web-twig/blob/2.5/docs/portal.md

That's very interesting! Does your portal deal with duplications? I'm not sure it fits my example with CSS-for-components, but it definitely represents a interesting addition to Component-Oriented-Twig.

Funnily I worked few months ago on React, on modal components, and I spent some time on the portal render API; Quite a bad practice on the React side, but definitively an interesting tool, maybe the Twig side get rid of the bad practice to keep the shiny tool.

iquito commented 6 months ago

It would be great if named arguments in filters/functions would not be converted to snake case (see https://github.com/twigphp/Twig/issues/3475) - this could be an easy fix for a new version, as it is clearly a BC break to change the behavior, but it is also a seemingly "undocumented" and surprising "feature" and changing it would improve Twig in general in my opinion. Introducing a deprecation error in Twig 3.x when a named argument is converted to snake case could also make upgrading a lot easier, so people who are affected could fix those issues before switching to Twig 4.x.

zmitic commented 6 months ago

This issue acts as a community wish list for things you want to see in the next major version that could lead to BC breaks

I don't know if it is a BC, but a support for promises would be great. I have some ideas how it would be possible to work without making another HTTP call (as I do now), but the implementation is beyond my skills.


Another wish would be Twital syntax like:

<ul t:if="users">
    <li t:for="user in users">
        {{ user.name }}
    </li>
</ul>
ericmorand commented 6 months ago

@zmitic how would your syntax proposal work exactly?

For example, how would you use it there?

{% for index in 1..10 %}
.foo-{{ index }} {
    z-index: {{ index }};
}
{% endfor %}
alinceDev commented 6 months ago

Not a BC break, I would like to have the template path to be rendered in dev mode in the source code so that when inspecting the code in the browser, I can see the template of the block, similar to what Drupal does."

<div>.....
    <!-- @twig templates/components/button.html.twig -->
    <button>
       My button
    </button>
    ......
</div>

I know there is the toolbar, but I think it is more convenient to have this information when inspecting.

Thanks

tacman commented 6 months ago

@twig templates/components/button.html.twig

you can use <!-- {{ _self }} -->
alinceDev commented 6 months ago

@twig templates/components/button.html.twig

you can use <!-- {{ _self }} -->

Does that mean adding this manually to all files? And then remembering to remove it before committing to git, as I only want to display it in the development environment.