Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

* Add the `%empty` loop branch: a bare `%empty` (no parentheses) between `%loop` and `%endloop` renders its content when the iterable is empty, like Blade's `@forelse` / `@empty`. `%empty($x)` with parentheses remains the conditional helper. Nested loops each get their own empty flag, and a `%loop` without `%empty` still compiles to a plain `foreach`.

### Fixed

* Fix greedy condition in `%stop(...)` and `%jump(...)`: when another `(...)` followed on the same line, the condition extended to the last `)` and produced broken PHP. The breaker directives now use the same balanced-paren matcher as the loop heads.
* Fix greedy `condition_pattern` that produced broken PHP when an `%if`, `%unless`, `%isset`, `%loop`, `%while`, or `%for` directive sat on a single line with a `{{ }}` echo in its body (the head would extend past the real `)` and swallow the echo's closing paren). Replaced `(.*)` with a recursive balanced-paren matcher in `Compiler::$condition_pattern`.

## 3.1.5 - 2025-12-21
Expand Down
57 changes: 51 additions & 6 deletions docs/en.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [The `%loop` / `%for` / `%while` directives](#the-loop--for--while-directives)
- [Using `%loop`](#using-loop)
- [Syntactic sugars `%jump` and `%stop`](#syntactic-sugars-jump-and-stop)
- [The `%empty` branch of a loop](#the-empty-branch-of-a-loop)
- [The use of `%for` and `%while`](#the-use-of-for-and-while)
- [Conditional Classes and Styles](#conditional-classes-and-styles)
- [Include subviews](#include-subviews)
Expand Down Expand Up @@ -203,10 +204,10 @@ In addition to the conditional directives already discussed, the `%isset`, `%emp

%notempty($records)
// $records is not "empty"...
%notendempty
%endnotempty
```

> You can add `%esle` to perform an opposite action
> You can add `%else` to perform an opposite action

## The `%loop` / `%for` / `%while` directives

Expand All @@ -222,6 +223,20 @@ This clause does exactly the action of `foreach`.
%endloop
```

Anything PHP's `foreach` accepts works in the head, such as key/value pairs or list destructuring:

```t
%loop($users as $id => $user)
{{ $id }}: {{ $user->name }}
%endloop
```

The whole loop can also sit on a single line:

```t
<ul>%loop($names as $name) <li>{{ $name }}</li> %endloop</ul>
```

This clause can also be coupled with any other clause such as `%if`.
A quick example.

Expand Down Expand Up @@ -260,24 +275,54 @@ With syntactic sugars, we can reduce the code like this:
%endloop
```

`%stop` compiles to `break` and `%jump` to `continue`. Both work in `%loop`, `%for` and `%while`, with or without a condition, and the condition may contain any PHP expression, including nested parentheses.

### The `%empty` branch of a loop

When the list you iterate over may be empty, you often end up wrapping the `%loop` in an `%if`. A bare `%empty` inside the loop lets you declare the content to render when the loop body never ran:

```t
%loop($users as $user)
Hello {{ $user->name }}
%empty
No user yet.
%endloop
```

The `%empty` branch is rendered only if `$users` is empty. It works with nested loops, each `%loop` keeps its own `%empty`. This branch is only available with `%loop`.

> Note: `%empty` without parentheses is the loop branch. `%empty($value)` with parentheses stays the conditional helper described above and can still be used inside a loop.

### The use of `%for` and `%while`

This clause does exactly the action of `for`.
`%for` does exactly the action of `for`.

```t
%for($i = 0; $i < 10; $i++)
//..
{{ $i }}
%endfor
```

This clause does exactly the action of `while`.
`%while` does exactly the action of `while`.

```t
%while($name != 'tintin')
//..
{{ $name = next($names) }}
%endwhile
```

`%jump` and `%stop` are available in both, exactly as in `%loop`:

```t
%for($i = 0; $i < 10; $i++)
%jump($i % 2 == 0)
%stop($i > 7)
{{ $i }}
%endfor
```

> Note: the `%empty` branch is only available with `%loop`. `%for` and `%while` have no equivalent.

## Conditional Classes and Styles

The `%class` directive conditionally compiles a CSS class string. The directive accepts an array of classes where the array key contains the class(es) you want to add, while the value is a boolean expression. If the array element has a numeric key, it will always be included in the rendered class list:
Expand Down
59 changes: 52 additions & 7 deletions docs/fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- [Les directives `%loop` / `%for` / `%while`](#les-directives-loop--for--while)
- [L'utilisation de `%loop`](#lutilisation-de-loop)
- [Les sucres syntaxiques `%jump` et `%stop`](#les-sucres-syntaxiques-jump-et-stop)
- [La branche `%empty` d'une boucle](#la-branche-empty-dune-boucle)
- [L'utilisation de `%for` et `%while`](#lutilisation-de-for-et-while)
- [Classes et styles conditionnels](#classes-et-styles-conditionnels)
- [Inclure les sous-vues](#inclure-les-sous-vues)
Expand Down Expand Up @@ -203,10 +204,10 @@ En plus des directives conditionnelles déjà discutées, les directives `%isset

%notempty($records)
// $records is not "empty"...
%notendempty
%endnotempty
```

> Vous pouvez ajouter `%esle` pour effectuer une action contraire
> Vous pouvez ajouter `%else` pour effectuer une action contraire

## Les directives `%loop` / `%for` / `%while`

Expand All @@ -222,7 +223,21 @@ Cette clause faire exactement l'action de `foreach`.
%endloop
```

Cette clause peux être aussi coupler avec tout autre clause telque `%if`.
Tout ce que le `foreach` de PHP accepte fonctionne dans l'en-tête, comme les paires clé/valeur ou la déstructuration de liste:

```t
%loop($users as $id => $user)
{{ $id }}: {{ $user->name }}
%endloop
```

La boucle entière peut aussi tenir sur une seule ligne:

```t
<ul>%loop($names as $name) <li>{{ $name }}</li> %endloop</ul>
```

Cette clause peut aussi être couplée avec toute autre clause telle que `%if`.
Un exemple rapide.

```t
Expand Down Expand Up @@ -260,24 +275,54 @@ Avec les sucres syntaxique, on peut réduire le code comme ceci:
%endloop
```

`%stop` compile vers `break` et `%jump` vers `continue`. Les deux fonctionnent dans `%loop`, `%for` et `%while`, avec ou sans condition, et la condition peut contenir n'importe quelle expression PHP, y compris des parenthèses imbriquées.

### La branche `%empty` d'une boucle

Lorsque la liste parcourue peut être vide, on finit souvent par entourer le `%loop` d'un `%if`. Un `%empty` seul dans la boucle permet de déclarer le contenu à afficher lorsque le corps de la boucle n'a jamais été exécuté:

```t
%loop($users as $user)
Bonjour {{ $user->name }}
%empty
Aucun utilisateur pour le moment.
%endloop
```

La branche `%empty` n'est affichée que si `$users` est vide. Elle fonctionne avec les boucles imbriquées, chaque `%loop` garde son propre `%empty`. Cette branche n'est disponible qu'avec `%loop`.

> Note: `%empty` sans parenthèses est la branche de boucle. `%empty($valeur)` avec parenthèses reste l'aide conditionnelle décrite plus haut et peut toujours être utilisée dans une boucle.

### L'utilisation de `%for` et `%while`

Cette clause faire exactement l'action de `for`.
`%for` fait exactement l'action de `for`.

```t
%for($i = 0; $i < 10; $i++)
// ..
{{ $i }}
%endfor
```

Cette clause faire exactement l'action de `while`.
`%while` fait exactement l'action de `while`.

```t
%while($name != 'tintin')
// ..
{{ $name = next($names) }}
%endwhile
```

`%jump` et `%stop` sont disponibles dans les deux, exactement comme dans `%loop`:

```t
%for($i = 0; $i < 10; $i++)
%jump($i % 2 == 0)
%stop($i > 7)
{{ $i }}
%endfor
```

> Note: la branche `%empty` n'est disponible qu'avec `%loop`. `%for` et `%while` n'ont pas d'équivalent.

## Classes et styles conditionnels

La directive `%class` compile conditionnellement une chaîne de classe CSS. La directive accepte un tableau de classes où la clé du tableau contient la ou les classes que vous souhaitez ajouter, tandis que la valeur est une expression booléenne. Si l'élément de tableau a une clé numérique, il sera toujours inclus dans la liste de classe rendue :
Expand Down
1 change: 1 addition & 0 deletions src/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ public function compile(string $data): string
$data = $this->compileVerbatim($data);
$data = $this->compileComments($data);
$data = $this->collapseMultilineDirectives($data);
$this->scanLoopEmpty($data);

$data = preg_split('/\n|\r\n/', $data);

Expand Down
Loading
Loading