Publicado em 2025-05-30 por Higor Zica
public function __invoke(Request $request): Response
{
$page = max(1, (int) $request->query->get('page', 1));
$limit = max(1, (int) $request->query->get('limit', 2));
$result = $this->productRepository->fetchAll($page, $limit);
$products = $result['products'];
$total = $result['total'];
return new Response(
$this->twig->render(
'product/list.html.twig',
[
'products' => $products,
'page' => $page,
'limit' => $limit,
'total' => $total,
]
)
);
}
__invoke(Request $request)
Esse método mágico __invoke()
permite que a classe do controller seja usada como uma função.
Isso é comum quando usamos o controller como um "action" direto em rotas Symfony:
product_list:
path: /products
controller: App\Controller\ProductListController
$page = max(1, (int) $request->query->get('page', 1));
$limit = max(1, (int) $request->query->get('limit', 2));
Request $request
: o Symfony injeta automaticamente a requisição HTTP.query->get('page', 1)
: busca o parâmetro page
na URL (/products?page=2
), e se não tiver, usa 1
por padrão.max(1, ...)
: garante que não venha 0
ou valores negativos (evita bugs).Exemplo:
/products?page=3&limit=4
, então:
$page = 3
$limit = 4
$result = $this->productRepository->fetchAll($page, $limit);
fetchAll($page, $limit)
retorna um array com:
'products' => [...]
'total' => 5
(por exemplo)$products = $result['products'];
$total = $result['total'];
Só está quebrando o array $result
em variáveis separadas, pra facilitar o uso na view.
return new Response(
$this->twig->render(
'product/list.html.twig',
[
'products' => $products,
'page' => $page,
'limit' => $limit,
'total' => $total,
]
)
);
list.html.twig
) e passa variáveis:
'products'
→ lista dos produtos da página'page'
→ número da página atual'limit'
→ limite de produtos por página'total'
→ total de produtos existentesResponse
com o HTML gerado.Se você acessar:
/products?page=2&limit=2
Então:
$page = 2
$limit = 2
public function fetchAll(int $page = 1, int $limit = 2): array
{
$allProducts = [
new ListProduct(id: 1, sku: 'PATA03731', name: 'M\'s Torrentshell 3L Rain Jkt Endless Blue', price: 199.9, promoPrice: 129.9, brand: 'Patagonia'),
new ListProduct(id: 2, sku: 'NIKA01001', name: 'Nike Air Max Alpha Trainer', price: 149.9, promoPrice: 99.9, brand: 'Nike'),
new ListProduct(id: 3, sku: 'ADID02022', name: 'Adidas Ultraboost Light Running', price: 179.9, promoPrice: 139.9, brand: 'Adidas'),
new ListProduct(id: 4, sku: 'NORT33333', name: 'The North Face Down Jacket', price: 249.9, promoPrice: 199.9, brand: 'The North Face'),
new ListProduct(id: 5, sku: 'COLUMB7777', name: 'Columbia Waterproof Hiking Boots', price: 159.9, promoPrice: 109.9, brand: 'Columbia'),
// ... pode adicionar mais produtos depois
];
$offset = ($page - 1) * $limit;
return [
'products' => array_slice($allProducts, $offset, $limit),
'total' => count($allProducts),
];
}
public function fetchAll(int $page = 1, int $limit = 2): array
fetchAll
é pública (pode ser acessada de fora da classe).$page
: a página atual (padrão 1)$limit
: quantos produtos por página (padrão 2)$allProducts = [ ... ];
ListProduct
.id
, sku
, name
, price
, promoPrice
, brand
.$offset = ($page - 1) * $limit;
Esse cálculo é essencial para paginação:
return [
'products' => array_slice($allProducts, $offset, $limit),
'total' => count($allProducts),
];
array_slice($allProducts, $offset, $limit)
:
offset
e pega no máximo $limit
produtos.array_slice($allProducts, 2, 2)
retorna os itens de índice 2 e 3.'total' => count($allProducts)
:
Se você chamar:
$repo->fetchAll(2, 2);
Você recebe:
[
'products' => [
produto de índice 2,
produto de índice 3
],
'total' => 5
]
Se quiser, posso te mostrar como montar uma resposta paginada na API, tipo:
{
"products": [ ... ],
"pagination": {
"page": 2,
"limit": 2,
"total": 5
}
}
<div class="card-footer">
<!-- Pagination -->
{% set totalPages = (total / limit)|round(0, 'ceil') %}
<nav aria-label="Pagination">
<ul class="pagination">
{% for i in 1..totalPages %}
<li class="page-item {% if i == page %}active{% endif %}">
<a class="page-link" href="?page={{ i }}&limit={{ limit }}">{{ i }}</a>
</li>
{% endfor %}
<li class="mt-2">
<span style="margin-left: 10px; font-size: 12px;">Total produits : {{total}}</span>
</li>
</ul>
</nav>
</div>
{% set totalPages = (total / limit)|round(0, 'ceil') %}
total
: número total de produtos (ex: 12)limit
: quantos produtos por página (ex: 5)|round(0, 'ceil')
para arredondar pra cima.
12 / 5 = 2.4
→ arredondado pra 3 páginastotalPages
agora vale 3{% for i in 1..totalPages %}
1
até totalPages
totalPages = 3
, então ele vai gerar 3 <li>
com links.<li class="page-item {% if i == page %}active{% endif %}">
<a class="page-link" href="?page={{ i }}&limit={{ limit }}">{{ i }}</a>
</li>
page
) for igual ao número (i
), adiciona a classe active
(para destacar a página atual)?page=i&limit=limit
?page=2&limit=5
<li class="mt-2">
<span style="margin-left: 10px; font-size: 12px;">Total produits : {{total}}</span>
</li>
Imagina que temos 12 produtos, limit = 5
. Então o layout seria:
[1] [2] [3] Total produits: 12
Se você estiver na página 2, ele mostra o botão 2 como "ativo".