Control Flow Directives #

Control flow directives wrap an element in a conditional or loop. Only one control-flow directive can appear on a single element. Use <s-template> when you want control flow without adding a wrapper element.

Directives #

  • s:if - Render when a condition is true.
  • s:ifblock - Render when a child block override exists.
  • s:unless - Render when a condition is false.
  • s:isset - Render when a variable is set.
  • s:empty - Render when a value is empty.
  • s:notempty - Render when a value is not empty.
  • s:foreach - Loop over an iterable.
  • s:forelse - Loop with an empty fallback.
  • s:while - Loop while a condition is true.
  • s:times - Loop a fixed number of times.
  • s:cache - Cache a rendered fragment by key.
  • s:switch - Switch/case rendering.
  • s:ifcontent - Render wrappers only if they contain output.
  • s:try - Wrap output in a try block with optional finally.
  • s:finally - Optional sibling for s:try cleanup.

Examples #

s:if #

Render the element only when the expression evaluates to true.

<div s:if="$isReady">Ready</div>
<div s:if="!$isReady">Loading...</div>
<!-- $isReady = true -->
<div>Ready</div>

s:unless #

Render the element only when the expression evaluates to false.

For more about empty/false checks, see Empty Checking.

<div s:unless="$isReady">Loading...</div>
<s-unless condition="$isReady">Loading...</s-unless>
<!-- $isReady = false -->
<div>Loading...</div>
<!-- $isReady = false -->
Loading...

s:ifblock #

Render the element only when a child template defines the named block.

For inheritance details, see Template Inheritance.

<aside s:ifblock="'sidebar'">
    <section s:block="sidebar"></section>
</aside>
<s-ifblock name="sidebar">
    <aside><section s:block="sidebar"></section></aside>
</s-ifblock>
<aside>
    <section>Sidebar content</section>
</aside>

s:isset #

Render the element when the variable is set (not null and defined).

<div s:isset="$user">Welcome, <?= $user->name ?></div>
<s-isset value="$user">Welcome, <?= $user->name ?></s-isset>
<!-- $user->name = 'Alice' -->
<div>Welcome, Alice</div>
<!-- $user->name = 'Alice' -->
Welcome, Alice

s:empty #

Render the element when the value is empty.

For more about empty/false checks, see Empty Checking.

<div s:empty="$items">No items found</div>
<s-empty value="$items">No items found</s-empty>
<!-- $items = [] -->
<div>No items found</div>
<!-- $items = [] -->
No items found

s:notempty #

Render the element when the value is not empty.

For more about empty/false checks, see Empty Checking.

<div s:notempty="$items">Items available</div>
<s-notempty value="$items">Items available</s-notempty>
<!-- $items = ['A'] -->
<div>Items available</div>
<!-- $items = ['A'] -->
Items available

s:foreach #

Repeat the host element for every item in an iterable.

If you only want to repeat children without repeating a wrapper element, place s:foreach on an s-template fragment.

For full loop metadata details, see Loop Metadata.

<ul s:foreach="$items as $item">
    <li><?= $item ?></li>
</ul>
<dl s:foreach="$stats as $label => $value">
    <dt><?= $label ?></dt>
    <dd><?= $value ?></dd>
</dl>
<ul s:foreach="$items as $item">
    <li s:class="['first' => $loop->first, 'last' => $loop->last, 'odd' => $loop->odd]">
        <?= $item ?> (<?= $loop->iteration ?> of <?= $loop->count ?>)
    </li>
</ul>
<!-- $items = ['A', 'B'] -->
<ul>
    <li>A</li>
</ul>
<ul>
    <li>B</li>
</ul>

s:forelse #

Loop over items and fall back to an s:empty sibling when empty.

For full loop metadata details, see Loop Metadata.

For more about empty/false checks, see Empty Checking.

<ul s:forelse="$items as $item">
    <li><?= $item ?></li>
</ul>
<div s:empty>No items found</div>
<ul s:forelse="$items as $item">
    <li s:class="['odd' => $loop->odd, 'even' => $loop->even]">
        <?= $item ?> (<?= $loop->iteration ?>)
    </li>
</ul>
<div s:empty>No items found</div>
<!-- $items = [] -->
<div>No items found</div>

s:while #

Repeat the element while a condition remains true.

<div s:while="$poller->hasNext()">
    <?= $poller->next() ?>
</div>
<s-while condition="$poller->hasNext()">
    <?= $poller->next() ?>
</s-while>

s:times #

Repeat the element a fixed number of times.

<span s:times="3">*</span>
<span s:times="5 as $i">#<?= $i ?></span>
<s-times count="3"><span>*</span></s-times>
<s-times count="5 as $i"><span>#<?= $i ?></span></s-times>

s:cache #

Cache a fragment’s rendered output using a configured PSR-16 cache store.

s:cache is opt-in at engine setup time. Register the optional FragmentCache extension:

use Sugar\Core\Engine;
use Sugar\Extension\FragmentCache\FragmentCacheExtension;

$cache = new YourPsr16CacheStore(); // must implement Psr\SimpleCache\CacheInterface

$engine = Engine::builder()
    ->withTemplateLoader($loader)
    ->withExtension(new FragmentCacheExtension($cache, defaultTtl: 300))
    ->build();

Directive forms:

  • s:cache - auto key, default TTL from new FragmentCacheExtension(..., defaultTtl: ...)
  • s:cache="'users-' . $userId" - explicit key, default TTL
  • s:cache="['key' => 'users-' . $userId, 'ttl' => 60]" - explicit key + per-fragment TTL override
<section s:cache>
    <h2>Popular items</h2>
    <?= $expensiveHtml ?>
</section>
<section s:cache="'users-' . $userId">
    <?= $userCardHtml ?>
</section>
<section s:cache="['key' => 'users-' . $userId, 'ttl' => 120]">
    <?= $userCardHtml ?>
</section>

If no fragment cache store is configured, s:cache is treated as a no-op wrapper and content still renders.

The element form uses a key attribute instead of the directive expression. Omitting key uses the same auto-key behaviour as bare s:cache:

<s-cache key="'sidebar'">
    <nav>...</nav>
</s-cache>

<!-- Auto key -->
<s-cache>
    <section>...</section>
</s-cache>

s:switch #

Choose between s:case and s:default children based on a value.

<div s:switch="$role">
    <span s:case="'admin'">Administrator</span>
    <span s:default>User</span>
</div>
<div s:switch="$status">
    <span s:case="'open'">Open</span>
    <span s:case="'closed'">Closed</span>
    <span s:default>Unknown</span>
</div>
<div s:switch="$role">
    <span s:case="'admin'">Administrator</span>
    <span s:case="'moderator'">Moderator</span>
    <span s:default>User</span>
</div>

s:ifcontent #

Render the wrapper only when it would contain output.

<div s:ifcontent class="card">
    <?php if ($showContent): ?>
        <p>Some content here</p>
    <?php endif; ?>
</div>

s:try / s:finally #

Wrap output in a try block with an optional finally sibling. There is no s:catch directive; if s:finally is omitted, Sugar emits a catch that returns null to keep the PHP valid and silently stop output on errors.

<div s:try>
    <?= $content ?>
</div>
<div s:finally>
    <?php $logger->flush(); ?>
</div>
<s-try>
    <?= $content ?>
</s-try>
<div s:finally>
    <?php $logger->flush(); ?>
</div>