Open fabpot opened 11 months ago
I really like the askama approach to make template type safe, and a little out of topic, it would be great if twig had an up-to-date formatter (maybe with a prettier plugin maintained by core team)
I like that you started removing usage of echo
and ob_*
in #3950. That's a breaking change if some extension use echo
instead of returning a string.
This will allow concurrent Twig renderings using fibers.
I would like to see a focus on component based templating over overriding/inheritance.
For that macros need an improvement specifically the need to import them separately in every block.
Would be great to support ux html component syntax natively for creating components.
A type checker plugin (pslam/phpstan) would be nice to have as well.
Support for step debugging via source maps would be nice.
Plugin / Theme Structure like Shopware built around Twig. We had to built something as well and it feels bad to just copy if this could have been solved directly. And twig is already strong in multiple inheritance.
Static type support for generated php to increase performance and predictable execution. I already tried to approach it but optimisation levels were commented as too breaking. So we could break it here. https://github.com/twigphp/Twig/pull/3928
Still be able to use Twig without pulling a lot of more dependencies.
E.g when you merge the twig symfony bridge into the twig package and in a small project just want to get some templating, you don't get the whole dependency clutter from the framework bundle. I don't use laravel/blade because you always get so much laravel stuff with it. I just want something small that can do inheritance and autoescape. Otherwise I would just use php right away.
I see benefits in merging some code from expression language as twig has to parse expressions as well and has "just" some nodes on top.
Change the null-coalescing operator to an empty- (or falsy-) coalescing operator. All the time I have a variable that is empty but not null, and I want to do variable ?? 'Something'
, but I can’t. And if ? :
works on falsiness, then so should ??
.
EDIT: I expected some 👎🏻 reactions but I'd like to hear an explanation for them
Some type of script block to write multiple statements without typing {% %} for every line would be nice.
Something like this:
{% script %}
set counter = 0;
set color = "#33b5e5";
set list_items = [{ ... }, { ... }];
if (some_var is same as true)
set list_items = [...list_items, { ... }];
endif
{% endscript %}
Definitely two things:
line
tagIt is not a template syntax but a compilation syntax, as discussed there: https://github.com/twigphp/Twig/pull/353.
As of today, the block
tag serves two purposes: declare a block and output a block. This not only creates confusion but also forces the compiler to make some arbitrary and non consistent decision like allowing a block definition inside a set
but forbidding it inside a if
(see https://github.com/twigphp/Twig/issues/3926).
I think that the block
tag should be dedicated to declare a block - and should be at the root level of template - and the block
and parent
functions dedicated to display them.
If you're intested in discussing this, I can write a special version of Twing that implement this specification so that we can test the behaviour with embed
.
Typically a big breaking change.
Change the null-coalescing operator to an empty- (or falsy-) coalescing operator. All the time I have a variable that is empty but not null, and I want to do
variable ?? 'Something'
, but I can’t. And if? :
works on falsiness, then so should??
.EDIT: I expected some 👎🏻 reactions but I'd like to hear an explanation for them
Its called the null coalescing operator for a reason. The whole php community is moving towards more strict typing, let's not create unexpected results by changing a well known concept like ??
. For your case I would implement a custom function or just make sure the data sent to the template is correct.
Add the possibility for global macro inheritance. Import in base template and available in all inheriting templates. Maybe a globalmacro
keyword?
Its called the null coalescing operator for a reason.
It's called that because that is what it does. If it did something else, it could be called something else.
The whole php community is moving towards more strict typing,
Twig is not PHP. It is written in PHP, it compiles to PHP, but it is its own language with its own goals. (inb4 "Your idea would be an ’own goal!’”) I don't believe its goal is merely "Enable back-end OOP PHP experts to write 1:1 PHP code in fewer characters".
let's not create unexpected results
This thread is specifically about breaking changes. Anything accepted from this thread will be equally unexpected to the person who compiles an old Twig file with the new version without reading the new docs.
For your case I would implement a custom function
??
is syntactic sugar for an easily conceived but lengthily typed combination of other Twig logic. The entire point is the brevity, and making it way more useful for common cases in a language that is not striving to be as strict as PHP proper is. And frankly, even if a null-coalescing operator is more appropriate for a stricter language, it kind of sucks that the question marks in ? :
and ??
mean similar but very-importantly-different things.
or just make sure the data sent to the template is correct.
Shall we deprecate the set
tag, then? What about the |map
filter? The Twig language could be a lot smaller if we told all its users to just make sure the data is correct before it's sent to the template's context.
Anyway, I won't say any more, this is clearly a non-starter. I don't think it should be, though. I'm full-stackish, I can write clean and pretty and strictly typed OOP PHP myself, but I don't think those same sensibilities should be uniformly applied to a template language meant to make front-end developers' lives easier.
I agree with most of the points @75th is raising, but let me add a bit of fuel to them:
Twig is not PHP. It is written in PHP, it compiles to PHP, but it is its own language with its own goals.
This should be in the front page of every article and documentation dedicated to Twig. Twig is not "a templating language for PHP" for a good reason: the definition does not make sense. No language can be a language for another language. I'm sure that said this way, everybody would agree that it makes no sense.
Twig is a language, with compilers dedicated to be run by different runtimes (PHP, Ruby, NodeJS, browser runtimes...). And as such, it can - and must in my opinion - make its own decisions.
the question marks in ? : and ?? mean similar but very-importantly-different things.
This should definitely raise some concern: that the ternary operator is not strict but that the nullish coallescing one is is something that it not consistent and goes against @reinierbutot "The whole [...] community is moving towards more strict typing". If one is strict, the other must be strict. If one is not, the other must not be.
Shall we deprecate the set tag, then? What about the |map filter? The Twig language could be a lot smaller if we told all its users to just make sure the data is correct before it's sent to the template's context.
Absolutely correct: @reinierbutot, if your position is "just make sure the data sent to the template is correct", then why these last years have seen more and more functions and filters added to Twig that are dedicated to manipulate and generate data instead of just outputting data?
Why do the formatting filters exist? The backend knows everything required to send appropriately formatted dates or numbers or currencies or countries to the templates, so why were these formatting features added if "just make sure the data sent to the template is correct"?
Why do the date creation function exist? The backend knows how to create a date and should send dates to the templates, right?
I think we must not forget that a templating language main purpose is to process plain text. So, yeah, @reinierbutot, I agree with you that we must "make sure the data sent to the template is correct". But you have to admit that it is not the direction that Twig has been taking - and it is getting worse and worse everyday (eg the infamous cache
tag that is the strict definition of something that is not in the scope of a templating language).
In template inheritance, variables set outside blocks in child templates should have priority over these declared in base template https://blog.srsbiz.pl/2021/07/scope-of-set-in-twig-templates/
@ericmorand
Why do the formatting filters exist? The backend knows everything required to send appropriately formatted dates or numbers or currencies or countries to the templates, so why were these formatting features added if "just make sure the data sent to the template is correct"?
As someone coming from extending applications built-upon a framework I sometimes do not have the option to change the data, that I get. "Have no option" means no services in DI, no events in event dispatcher or as in "no data returned from a controller but rendered directly". There is always the vendor patching way but I try to focus on something maintainable.
Example: when someone is giving me a doctrine entity for the template I just get a DateTimeInterface property, that could be not yet timezoned or formatted. And I think this is good. So when I have a data cache, the entity can be held in the cache independent to who renders the entity.
Another example: Sometimes data is expressed twice. So when I display a date human readable, I have often have a structured data part, that expresses the same date but in a machine readable format. So I'd rather have the date once in the template data and be able to format it whenever I need it in whatever format I need it. This is the part, that gives me flexibility in changing templates of others.
@JoshuaBehrens I get your point and I agree - but it says a lot about how backend are (not) specified - and mainly driven by the frameworks instead of by the requirements - and how frontend developers are left behind even today.
My point is that if one says that "data sent to the template is correct", then it makes all the filters and data manipulators of Twig not legit.
I would love the ability to statically analyze twig templates with phpstan/psalm. Twig templates are one of the remaining vulnerable parts of codebases to breaking changes and not knowing it.
@ericmorand @75th thanks guys.
So you want var ?? other_var
logic to be identical to var ?: other_var
?
It does actually feel more like the Twig way. I'll be looking forward to using this new falseycoalescing operator 😂
@reinierbutot, well, I don't have a PHP setup ready but even in JavaScript, the behaviour of the ternary and the nullish coallescing operators are not consistent - or maybe they are but it is not obvious to me:
console.log(null ?? 5); // 5
console.log(null ? 4 : 5); // 5
console.log('' ?? 5); // <empty string>
console.log('' ? 4 : 5); // 5
As you can see, ''
is considered as truthy in the third statement, but falsy in the fourth. So maybe Twig is right, and you are too.
Maybe not a BC break and even maybe discarded already, but what about allow to pass an object as context when rendering a template?
So you want var ?? other_var logic to be identical to var ?: other_var?
Ohh, crap. I must confess: I didn’t realize that a ?: b
was valid PHP and Twig, probably because it’s not valid JavaScript and I mistakenly wrote off the concept everywhere. I still think it’s kind of ugly to have ?
mean something different in two different operators, but then I guess if you take ?:
as “not a thing” and ??
as “REALLY not a thing” then it makes a certain sense.
So, request withdrawn.
(for that matter, it might have been nice to have it be ??:
so you could do a ?? b : c
, even though that would be useful infrequently)
The fact that the default default value of the default filter (wow, that sounds complicated) is ''
and not null
cost me a lot of time in the past. I think it would meet dev's expectations more when it returns null
if nothing else is specified.
- function _twig_default_filter($value, $default = '')
+ function _twig_default_filter($value, $default = null)
Having new extension points somewhere between Parser and Lexer could facilitate Twig/LiveComponent maintenance and unlock some new features :)
Very personal wish, but I'd love to have "private" templates (as in "not possible to override" with the multi-paths system, or extends)
I almost forgot... those are also old wishes of mine, but i can't remember / understand why i marked them at "not doable"
The equivalent of PHP if ($foo = ..)
{% ifset foo = bar|blop %}. ... {% endifset %}
Any method to "exit" while in loops, but ideally also inside blocks
Strict typing for example using === instead of same as (faster DX)
I'd like to be able to combine a set and if statement.
{% if count = post.comments.count %}
There are {{ count }} comments.
{% endif %}
instead of
{% set count = post.comments.count %}
{% if count %}
There are {{ count }} comments.
{% endif %}
I find adding properties to objects to be complicated and hard to read. I'd like to be able to add an element to an object without doing the merge, e.g. instead of
{% if options.depth is not none %}
{% set options = options|merge({'depth': currentOptions.depth - 1}) %}
{% endif %}
{# update the matchingDepth for children #}
{% if options.matchingDepth is not none and options.matchingDepth > 0 %}
{% set options = options|merge({'matchingDepth': currentOptions.matchingDepth - 1}) %}
{% endif %}
I'd like to be able to simply set an object property.
{% set options.currentDepth = currentOptions.depth - 1 %}
It'd be even better if there were a way to combine the if statement with the set
{% set options.currentDepth = (currentOptions.depth) - 1 if options.depth is not none %}
{% options.depth is not none ? set options.currentDepth = (currentOptions.depth) - 1 %}
Just brainstorming on the if / set, but the ability to set a single property would be really nice.
I would love the ability to statically analyze twig templates with phpstan/psalm. Twig templates are one of the remaining vulnerable parts of codebases to breaking changes and not knowing it.
Clearly a must have even if it's not directly linked to twig codebase ! Extending this check to twig bridge render arguments would be perfect.
It's a bit off, but with sf router we have the same vulnerability when you change an arg name. How are you managing it ?
(except using static call like myClassController::routeParams(/* here a duplicate from __invoke params except Request*/)
+ fqcn-based route myClassController::class
maybe, i did some similar experiments around league/plates).
@jwage
I would love the ability to statically analyze twig templates with phpstan/psalm. Twig templates are one of the remaining vulnerable parts of codebases to breaking changes and not knowing it.
I'm not sure I understand the point. We already have frontend projects that come with tests that ensure non regression based on the contracts of the templates. What would static analysis bring that these tools don't already provide?
Faster refactoring or upgrade.
@jwage
I would love the ability to statically analyze twig templates with phpstan/psalm. Twig templates are one of the remaining vulnerable parts of codebases to breaking changes and not knowing it.
I'm not sure I understand the point. We already have frontend projects that come with tests that ensure non regression based on the contracts of the templates. What would static analysis bring that these tools don't already provide?
@ericmorand Maybe I am missing something that already exists, but how do I protect against runtime errors in Twig templates that are due to incorrect types? For example, say I am passing an array to a Twig template with a property named article
and it is of type Article
.
Article
has a method named getName()
and the method is renamed to getTitle()
, you will only know that the Twig template is broken at runtime, nothing at build/compile time in CI/CD. Maybe if you have some web test cases you will catch it, but static analysis is a little bit better as it guarantees you won't have any runtime type errors. Static analysis eliminates the whole category of bugs from being possible.
@jwage
But what would you statically analyze?
Analyzing the Twig template would trigger no error - there is no such thing as types in Twig and even if there was, static analysis would not guarantee that the value passed to the template will be of the correct type.
Article has a method named getName() and the method is renamed to getTitle(), you will only know that the Twig template is broken at runtime
That's my point: there is only one way to test this and it is by having a contract that the backend and the frontend must honor and that defines the names of the variables and their type. Said differently, the template never expects the variable article
to be of type Article
because the Article
type doesn't exist in Twig. It expects the variables article
to have a property named getName
that is a function that, when executed, returns a string.
Typically, if the backend, written in TypeScript, puts the following object in the article
variable...
article: {
getName: () => 'foo';
}
...the Twig template would render perfectly. But there is no way to know at write time where the variable comes from - it could be TypeScript backend, a PHP one, data passed in the command line...
The only way to guarantee that a Twig template and the backend are compatible is to:
This is exactly the same pattern as when you write an HTTP API and you have to guarantee that both the provider of the API and the consumer are compatible: we write a contract and write tests on both sides that check that the contract is honored.
For reference, in our TypeScript backends, we write typed methods for each template:
type RenderHomepage = (data: {
title: string;
content: string;
}) => Promise<string>;
We then have a first pass of check done by the compiler (any place that would execute this methods by not passing the expected arguments would fail the compilation) but we also add tests that checks that executing this method with the expected arguments actually renders the template in the expected way.
Note that the type of the data
parameter is the contract I'm talking about. This is what we share with both frontend and backend developers. And we have the guarantee that when the template is executed by the backend, it will work. And that if either the contract, or the template or the backend changes in a non-backward compatible way, a test will fail.
@ericmorand @jwage is this what you are looking for? https://github.com/twigphp/Twig/pull/3928
@JoshuaBehrens not directly related but still quite interesting, and it reminds me of one of a the things that I think Twig should get rid of: this reolution aglorithm that attempts to guess the property to access or call.
So, let's make it an official request:
Twig tries to guess the correct attribute to either access or call when the accessor synatx is encountered:
{{ foo.bar }}
Here, Twig will challenge bar
, bar()
, getBar()
and isBar()
instead of just considering that the written attribute is the one that we want to access - namely bar
.
This guessing algorithm is an explicit admission that developers don't know what they are doing and that templates are written without perfect knowledge of the data that they support.
Think about it: why would anyone write {{ foo.bar }}
if what they want to do is execute bar()
? Why would they prefer {{ foo.bar }}
to {{ foo.bar() }}
except if they are not sure what the contract of the foo
variable is? It seems extremely problematic that Twig considers that templates are likely to not be contractualized and it definitely goes against all the good practices that our discipline have been pushing in the last years: considering contracts, types and tests as first class citizen.
Keeping this resolution algorithm is clearly an admission that not knowing what your data consist of is acceptable and encouraged.
@ericmorand I am not sure what the solution is or how you would accomplish it. What I want to solve for is never having runtime errors in Twig templates that I don't know about and a way to notify me in my automated tests whenever I have something that would fail at runtime. We have this solved in PHP with psalm/phpstan, but Twig templates are still a risky area. There have been a few people who have tried to add support for Twig in to phpstan and psalm by looking at the types passed to render()
from the controller and making sure there are no potential type errors in the templates.
There have been a few people who have tried to add support for Twig in to phpstan and psalm by looking at the types passed to render() from the controller and making sure there are no potential type errors in the templates.
Which controller? How can phpstan understand the infinite variety of patterns that ends up rendering a Twig template? I mean, even by limiting the options to the PHP ecosystem, I could totally render a Twig template outside of a controller - think about Slim Framework that doesn't enforce MVC for example. So now add the other runtime environments (node.js, ruby, go...from the top of my head) and it gives us an idea of the impossibility to write an analyzer that would be able to detect issues in data sent to the templates at runtime.
The solution is contractualising and testing, just like we test any other public API. We have to consider templates for what they are: public APIs. They work only if they are executed with a certain set of parameters. They are analoguous to functions.
Imagine the template home.twig
:
{{ foo }}
{{ bar.doSomething() }}
It is analoguous to the function:
RenderHome = (data: {
foo: string;
bar: {
doSomething: () => string
};
}): string;
Hence, we can just write unit tests for our templates, and those tests would fail if:
Which controller? How can phpstan understand the infinite variety of patterns that ends up rendering a Twig template? I mean, even by limiting the options to the PHP ecosystem, I could totally render a Twig template outside of a controller - think about Slim Framework that doesn't enforce MVC for example.
It does not really matter where the rendering originates. The entrypoint (almost) always is a call to the render()
method of the Twig
environment class from where type information of the data passed to render()
somehow needs to be forwarded down the templates to be able to reason about the data being available inside the templates.
So now add the other runtime environments (node.js, ruby, go...from the top of my head) [...]
Twig templates are not rendered by anything else than PHP.
Twig templates are not rendered by anything else than PHP.
Of course they do. We have a lot of compilers that compile Twig files to templates executable by runtimes that are not PHP.
That’s irrelevant here as we are talking about the code of this repository which does not know anything about any other project that implements a Twig-like compiler. Whatever other projects do is up to them. But here we are talking about PHP code.
@xabbuh, If i'm not mistaken, we are talking about breaking changes in the language:
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).
And by the way, there is no such thing as Twig-like compilers. There are Twig compilers. I think it is important that no one here disregard the effort of the community to bring Twig to other runtimes than PHP.
Anyway, @jwage if you want to continue the discussion about testing Twig templates, we can move to the Discussions tab. I've worked a lot these last few years on this subject and we have projects that use Twig as their templating languages and that don't depend on any static analysis to guarantee non-regression and perfect integration with our PHP or Node.js backends. So don't hesistate to ask there, I'd be very happy to help.
@xabbuh, If i'm not mistaken, we are talking about breaking changes in the language:
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).
And by the way, there is no such thing as Twig-like compilers. There are Twig compilers. I think it is important that no one here disregard the effort of the community to bring Twig to other runtimes than PHP.
Anyway, @jwage if you want to continue the discussion about testing Twig templates, we can move to the Discussions tab. I've worked a lot these last few years on this subject and we have projects that use Twig as their templating languages and that don't depend on any static analysis to guarantee non-regression and perfect integration with our PHP or Node.js backends. So don't hesistate to ask there, I'd be very happy to help.
Thanks! I know how to unit test templates :) Static analysis just gives you another level of stability and type safety to guarantee the impossibility of whole categories of bugs being possible. I would like that guarantee, that is all.
You have been told several times before that there is no Twig language. There is just Twig which is this PHP templating engine. In this issue we are only talking about the future of Twig.
Twig is its own language, regardless of whether you consider a common specification or a particular implementation to be the official definition of that language. And for what it’s worth, Eric’s work with Twing is the only thing standing between you and an exodus of users from the Twig language as a whole, since that’s what lets anyone use the increasingly popular Storybook design library system with Twig components.
@75th I think that twig.js is the compiler that is essentially used by Storybook but your point remains: disregarding the amount of work, time and passion that the community did put into porting Twig language to other runtimes is definitely disrespectful. Twig.js passionate contributors have greatly contributed to the success of Twig outside of the PHP community and they deserve respect for this.
Pretending that Twig is not a language just because it serves the agenda of the TwigPHP maintainers - i.e. not emitting specification and not considering the other implementations at all during the design decision - is disrespectful to the open source community that dedicates personal time and contributes every day to the success of Twig and Symfony (and Drupal and Magento to name a few frameworks that we have been selling to our customers with frontend developed with twig.js or twing).
I will stop dicussing this here - this is not the topic of the issue - but seeing that, 8 years after the emergence of twig.js and 6 after the birth of twing, some Symfony contributors continue to disregard our point of view and feedback (even if we arguably are the ones that know the language the best after the TwigPHP maintainers themselves) is depressing and does not put the Twig community under a very flattering light.
@ericmorand twig.js is years and years behind Twig PHP the last I checked. Doesn’t even support the function version of include
I think. And I don’t know of any common/official twig.js Storybook setup.
I and others I work with have spent a lot of time over the last year moving from Pattern Lab to a Twing-powered Storybook setup so we can stick with Twig components. We have WordPress Gutenberg blocks backed by Twing-rendered components both on the front end and in the block editor itself.
And I guarantee that if Twing did not exist, all those many hours would have gone not into improving twig.js, nor into bringing Pattern Lab up to scratch with Storybook, nor into figuring out how the heck Pattern Lab was running TwigPHP in Node and transplanting that into a Storybook builder; no, they would all have gone to rewriting all our components in React and figuring out how to serialize that into something static for monolithic Drupal builds.
Anyway you’re right, this is off topic so I’ll shut up, but it’s all the truth.
We could drop TwigExtraBundle and move all integrations to TwigBundle.
Please consider adding a "components" feature, a mix of macro
and embed
.
Similar to ux-twig-component
with additional details:
content
and support for block
.Open up new opportunities to create a Twig UI component library, similar to https://ui.mantine.dev/.
An example:
{# file: components/layout.tpl.twig #}
{# <twig:Layout [...params]>[content]</twig:Layout> #}
{% component default Layout(
gap = 0,
justify = "start",
content, {# Special parameter, evaluate `block` #}
) %}
<div class="flex fx-gap-{{ gap }} {{ 'fx-justify-content-' ~ justify }}">
{{ content }}
</div>
{% endcomponent %}
{# <twig:Layout.Section [...params]>[content]</twig:Layout.Section> #}
{% component Section(class, _content) %}
{% if content %}
<div class="{{ class }}">
{{ content }}
</div>
{% endif %}
{% endcomponent %}
Usage:
{#
{% import "components/layout.tpl.twig" as Layout %}
{% import "components/card.tpl.twig" as Card %}
{% import "components/form.tpl.twig" as Form %}
{% import "components/alert.tpl.twig" as Alert %}
#}
{% import "components/*" as Layout, Card, Form, Alert %}
{% import "components/content/post.tpl.twig" as Post %}
<twig:Layout gap=5>
{% block sidebar_alpha %}{% endblock %}
{{ route === "home" ? (
<twig:Alert variant="info" message="Notification message" />
) }}
<twig:Layout.Section>
<twig:Card title={{ i18.title_featured|"Featured Posts" }}>
<twig:Post.Lists source={{ postFeatured }} gap=2 />
</twig:Card>
</twig:Layout.Section>
<twig:Layout.Section>
<twig:Card title="Information">
<twig:Form action="{{ form.action }}">
<twig:Layout gap=3>
<twig:Form.Input label="Name" name="name" value={{ user.name }} />
<twig:Form.Input label="Email" name="email" type="email" value={{ user.email }} />
<twig:Layout.Section justify="end">
<twig:Button text={{ i18n.cancel }} />
<twig:Button type="submit" variant="primary" text={{ i18n.submit }} />
</twig:Layout.Section>
</twig:Layout>
</twig:Form>
</twig:Card>
</twig:Layout.Section>
{% block sidebar_omega %}{% endblock %}
</twig:Layout>
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.