Циклы и итерации
Полное руководство по работе с циклами в шаблонах MNRFY
Цикл foreach
Базовый синтаксис
<!-- Простая итерация по массиву -->
{% foreach users as user %}
<div class="user-item">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
{% endforeach %}
<!-- Итерация с ключами -->
{% foreach menu as key => item %}
<li data-key="{{ key }}">
<a href="{{ item.url }}">{{ item.title }}</a>
</li>
{% endforeach %}
Переменная loop
В каждом цикле foreach доступна специальная переменная loop:
| Свойство | Описание | Тип |
|---|---|---|
loop.index |
Номер итерации (начиная с 1) | number |
loop.index0 |
Номер итерации (начиная с 0) | number |
loop.revindex |
Обратный номер итерации (от конца, начиная с 1) | number |
loop.revindex0 |
Обратный номер итерации (от конца, начиная с 0) | number |
loop.first |
Первая итерация | boolean |
loop.last |
Последняя итерация | boolean |
loop.length |
Общее количество элементов | number |
loop.parent |
Контекст родительского цикла | object |
Примеры использования переменной loop
{% foreach products as product %}
<div class="product-item {{ loop.first ? 'first' : '' }} {{ loop.last ? 'last' : '' }}">
<span class="item-number">#{{ loop.index }}</span>
<h3>{{ product.name }}</h3>
<p>{{ product.price | money('₽') }}</p>
{% if loop.first %}
<span class="badge badge-featured">Рекомендуемый</span>
{% endif %}
{% if loop.last %}
<hr>
<p>Всего товаров: {{ loop.length }}</p>
{% endif %}
</div>
{% endforeach %}
Чередование стилей
<table class="data-table">
{% foreach users as user %}
<tr class="{{ loop.index % 2 == 0 ? 'even' : 'odd' }}">
<td>{{ loop.index }}</td>
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
</tr>
{% endforeach %}
</table>
Цикл for (диапазоны)
Числовые диапазоны
<!-- От 1 до 10 -->
{% for i in 1..10 %}
<div class="item-{{ i }}">Элемент {{ i }}</div>
{% endfor %}
Обратный порядок
<!-- Обратный отсчет -->
<div class="countdown">
{% for i in 10..1 %}
<span class="countdown-number">{{ i }}</span>
{% endfor %}
<span class="countdown-go">Поехали!</span>
</div>
Генерация опций
<!-- Годы для даты рождения -->
<select name="birth_year">
{% for year in 1950..2024 %}
<option value="{{ year }}" {{ year == user.birth_year ? 'selected' : '' }}>
{{ year }}
</option>
{% endfor %}
</select>
<!-- Рейтинг звездами -->
<div class="rating">
{% for star in 1..5 %}
<i class="fas fa-star {{ star <= product.rating ? 'active' : '' }}"></i>
{% endfor %}
</div>
Вложенные циклы
Категории и товары
{% foreach categories as category %}
<div class="category">
<h2>{{ category.name }}</h2>
<div class="category-products">
{% foreach category.products as product %}
<div class="product">
<h3>{{ product.name }}</h3>
<p>Категория #{{ loop.parent.index }}, Товар #{{ loop.index }}</p>
<p>{{ product.price | money('₽') }}</p>
{% if product.images %}
<div class="product-gallery">
{% foreach product.images as image %}
<img src="{{ image.url }}"
alt="{{ product.name }}"
class="{{ loop.first ? 'main-image' : 'thumb' }}">
{% endforeach %}
</div>
{% endif %}
</div>
{% endforeach %}
</div>
</div>
{% endforeach %}
Таблица с данными
<table class="data-table">
<thead>
<tr>
{% foreach table_headers as header %}
<th>{{ header.title }}</th>
{% endforeach %}
</tr>
</thead>
<tbody>
{% foreach table_rows as row %}
<tr class="{{ loop.index % 2 == 0 ? 'even' : 'odd' }}">
{% foreach table_headers as header %}
<td>{{ row[header.field] }}</td>
{% endforeach %}
</tr>
{% endforeach %}
</tbody>
</table>
Условия в циклах
Пропуск элементов (continue)
{% foreach users as user %}
{% if user.is_banned %}
{% continue %} <!-- Пропускаем заблокированных пользователей -->
{% endif %}
<div class="user-card">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
{% if user.is_premium %}
<span class="badge badge-gold">Премиум</span>
{% endif %}
</div>
{% endforeach %}
Прерывание цикла (break)
{% foreach items as item %}
<div class="item">
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
</div>
{% if loop.index >= 10 %}
{% break %} <!-- Показываем только первые 10 элементов -->
{% endif %}
{% endforeach %}
Условные блоки
{% foreach products as product %}
<div class="product {{ loop.index % 3 == 0 ? 'product-featured' : '' }}">
<h3>{{ product.name }}</h3>
<p>{{ product.price | money('₽') }}</p>
{% if loop.index % 5 == 0 %}
<div class="advertisement">
<!-- Реклама каждые 5 товаров -->
<p>Специальное предложение!</p>
</div>
{% endif %}
{% if loop.last %}
<div class="products-footer">
<p>Показано {{ loop.length }} товаров</p>
</div>
{% endif %}
</div>
{% endforeach %}
Цикл while
Базовое использование
{% php %}
$counter = 1;
$maxItems = 10;
{% endphp %}
{% while counter <= maxItems %}
<div class="dynamic-item">
<h3>Элемент {{ counter }}</h3>
<p>Это динамически сгенерированный элемент</p>
</div>
{% php %}
$counter++;
{% endphp %}
{% endwhile %}
While с данными из базы
{% php %}
$page = 1;
$perPage = 20;
$hasMore = true;
{% endphp %}
<div class="infinite-scroll">
{% while hasMore %}
{% set pageUsers = db('1752665380840')->paginate('users', page, perPage) %}
{% if pageUsers.data %}
{% foreach pageUsers.data as user %}
<div class="user-item">
<h3>{{ user.name }}</h3>
<p>Страница {{ page }}</p>
</div>
{% endforeach %}
{% endif %}
{% php %}
$hasMore = $pageUsers['next_page'] ? true : false;
$page++;
{% endphp %}
{% endwhile %}
</div>
Работа с пустыми коллекциями
Проверка перед циклом
{% if users and users | length > 0 %}
<div class="users-list">
{% foreach users as user %}
<div class="user-item">
<h3>{{ user.name }}</h3>
<p>{{ user.email }}</p>
</div>
{% endforeach %}
</div>
{% else %}
<div class="empty-state">
<i class="fas fa-users fa-3x"></i>
<h3>Пользователи не найдены</h3>
<p>Добавьте первого пользователя</p>
<a href="/users/create" class="btn btn-primary">Добавить пользователя</a>
</div>
{% endif %}
Else в цикле
<div class="products-grid">
{% foreach products as product %}
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | money('₽') }}</p>
</div>
{% else %}
<div class="no-products">
<i class="fas fa-box-open fa-4x"></i>
<h2>Товары не найдены</h2>
<p>В данной категории пока нет товаров</p>
<a href="/products/create" class="btn btn-primary">Добавить товар</a>
</div>
{% endforeach %}
</div>
Сложные примеры
Группировка элементов
<!-- Группировка товаров по 3 в ряду -->
<div class="products-grid">
{% foreach products as product %}
{% if loop.index % 3 == 1 %}
<div class="products-row">
{% endif %}
<div class="product-card">
<h3>{{ product.name }}</h3>
<p>{{ product.price | money('₽') }}</p>
</div>
{% if loop.index % 3 == 0 or loop.last %}
</div>
{% endif %}
{% endforeach %}
</div>
Древовидная структура
<!-- Рекурсивное меню -->
{% macro render_menu(items, level) %}
<ul class="menu-level-{{ level }}">
{% foreach items as item %}
<li class="menu-item">
<a href="{{ item.url }}">{{ item.title }}</a>
{% if item.children %}
{{ _self.render_menu(item.children, level + 1) }}
{% endif %}
</li>
{% endforeach %}
</ul>
{% endmacro %}
<nav class="main-menu">
{{ _self.render_menu(menu_items, 0) }}
</nav>
Календарь
<!-- Генерация календаря -->
{% set currentMonth = date('m') %}
{% set currentYear = date('Y') %}
{% set daysInMonth = date('t') %}
{% set firstDayOfWeek = date('w', strtotime(currentYear ~ '-' ~ currentMonth ~ '-01')) %}
<div class="calendar">
<div class="calendar-header">
<h2>{{ date('F Y') }}</h2>
</div>
<div class="calendar-weekdays">
{% for day in ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'] %}
<div class="weekday">{{ day }}</div>
{% endfor %}
</div>
<div class="calendar-days">
<!-- Пустые ячейки для первой недели -->
{% for i in 1..firstDayOfWeek %}
<div class="calendar-day empty"></div>
{% endfor %}
<!-- Дни месяца -->
{% for day in 1..daysInMonth %}
{% set isToday = (day == date('d') and currentMonth == date('m')) %}
<div class="calendar-day {{ isToday ? 'today' : '' }}">
{{ day }}
</div>
{% endfor %}
</div>
</div>
Производительность циклов
Оптимизация запросов
<!-- Плохо: N+1 запросов -->
{% foreach users as user %}
<div class="user">
<h3>{{ user.name }}</h3>
{% set userPosts = db('1752665380840')->getItems('posts', {'user_id': user.id}) %}
<p>Постов: {{ userPosts | length }}</p>
</div>
{% endforeach %}
<!-- Хорошо: один запрос -->
{% set postCounts = db('1752665380840')->query('SELECT user_id, COUNT(*) as count FROM posts GROUP BY user_id') %}
{% set postCountsMap = {} %}
{% foreach postCounts as count %}
{% set postCountsMap[count.user_id] = count.count %}
{% endforeach %}
{% foreach users as user %}
<div class="user">
<h3>{{ user.name }}</h3>
<p>Постов: {{ postCountsMap[user.id] ?? 0 }}</p>
</div>
{% endforeach %}
Ограничение количества элементов
<!-- Ограничение отображения -->
{% foreach items as item %}
<div class="item">{{ item.title }}</div>
{% if loop.index >= 50 %}
<div class="show-more">
<p>Показаны первые 50 элементов из {{ items | length }}</p>
<button onclick="loadMore()">Показать еще</button>
</div>
{% break %}
{% endif %}
{% endforeach %}
Следующий шаг: Изучите фильтры (pipes).