Reputation: 311
I'm looking for best practice in symfony controller logic. My current code have one controller:
<?php
namespace App\Controller;
use App\Entity\Categories;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class Controller extends AbstractController
{
/**
* @Route("/", name="main_index")
*/
public function index()
{
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
return $this->render('index.html.twig', [
'categories' => $categories,
]);
}
}
categories Entity:
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Categories
*
* @ORM\Table(name="categories", indexes={@ORM\Index(name="title", columns={"title"}), @ORM\Index(name="url", columns={"url"})})
* @ORM\Entity
*/
class Categories
{
/**
* @var bool
*
* @ORM\Column(name="id", type="integer", nullable=false)
* @ORM\Id
* @ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* @var string|null
*
* @ORM\Column(name="title", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $title = 'NULL';
/**
* @var string|null
*
* @ORM\Column(name="url", type="string", length=255, nullable=true, options={"default"="NULL"})
*/
private $url = 'NULL';
/**
* @var string|null
*
* @ORM\Column(name="header_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $headerText = 'NULL';
/**
* @var string|null
*
* @ORM\Column(name="body_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $bodyText = 'NULL';
/**
* @var string|null
*
* @ORM\Column(name="footer_text", type="text", length=65535, nullable=true, options={"default"="NULL"})
*/
private $footerText = 'NULL';
/**
* @var \DateTime|null
*
* @ORM\Column(name="created_at", type="datetime", nullable=true, options={"default"="NULL"})
*/
private $createdAt = 'NULL';
/**
* @var \DateTime|null
*
* @ORM\Column(name="updated_at", type="datetime", nullable=true, options={"default"="NULL"})
*/
private $updatedAt = 'NULL';
public function getId(): ?bool
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(?string $title): self
{
$this->title = $title;
return $this;
}
public function getUrl(): ?string
{
return $this->url;
}
public function setUrl(?string $url): self
{
$this->url = $url;
return $this;
}
public function getHeaderText(): ?string
{
return $this->headerText;
}
public function setHeaderText(?string $headerText): self
{
$this->headerText = $headerText;
return $this;
}
public function getBodyText(): ?string
{
return $this->bodyText;
}
public function setBodyText(?string $bodyText): self
{
$this->bodyText = $bodyText;
return $this;
}
public function getFooterText(): ?string
{
return $this->footerText;
}
public function setFooterText(?string $footerText): self
{
$this->footerText = $footerText;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(?\DateTimeInterface $createdAt): self
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): self
{
$this->updatedAt = $updatedAt;
return $this;
}
}
I want to repeat "$categories" variable to all my other pages. So it means - extra query on every page, but I don't want to repeat code:
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
everywhere, because all the pages must to show categories all the time. Also by symfony logic all the @route should have own function. So how I suppose to make the route logic and not to repeat the categories request code? by making another outside class with this code and just reuse it in all the other route methods?
EDIT: My solution:
templates/index.html.twig file (one place):
{{ render(controller('App\\Repository\\CategoriesListRepository::getCategories')) }}
templates/categories.html.twig (one file):
{% for category in categories %}
<li>
{{ category.getName() }}
</li>
{% endfor %}
Repository/CategoriesListRepository.php :
<?php declare(strict_types=1);
namespace App\Repository;
use App\Entity\Categories;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
/**
* Class CategoriesListRepository
* @package App\Repository
*/
final class CategoriesListRepository extends AbstractController
{
/**
* @var \Doctrine\Common\Persistence\ObjectRepository
*/
private $repository;
/**
* CategoriesListRepository constructor.
* @param EntityManagerInterface $entityManager
*/
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Categories::class);
}
public function getCategories(): Response
{
return $this->render('categories.html.twig', ['categories' => $this->repository->findAll()]);
}
}
would be nice to hear some comments/recommendations
Upvotes: 0
Views: 1618
Reputation: 2270
If you only want to avoid repeating the code (have the a code copy on different places), the previous answers may fit. But if you wan't to not execute your repository calls over and over again with the goal to optimize database performance, in my opinion its no good practice to move code to places which are not responsible for (e.g. twig global variables).
Even if the result may be always the same over different pages, it could also be that different results return yet. But thats something you should not be worry about.
Instead i would go for using the doctrine result cache for my queries. The cache should decide whether the requested data is the same as in the previous request or not and give me the data.
As long as you are using doctrine and no DBAL you will be fine with this.
Upvotes: 0
Reputation: 21
You can have $categories as a container's variable by using $container->set() method and then get the variable with $container->get('categories').
Upvotes: 0
Reputation: 1051
Take a look at How to Inject Variables into all Templates (i.e. global Variables).
You would create the following config:
# config/packages/twig.yaml
twig:
# ...
globals:
# the value is the service's id
category_service: '@App\Service\CategoryService'
And in your CategoryService
you would get all categories within a getCategories()
method. Later on you can call in your twig template category_service.getCategories()
.
Upvotes: 0
Reputation: 49
You can call a controller from a template.
Create the controller that renders only list of categories:
public function listAllAction()
{
$categories = $this->getDoctrine()
->getRepository(Categories::class)
->findAll();
return $this->render('categories.html.twig', [
'categories' => $categories,
]);
}
and then call it with render
function in a twig file:
<div id="list-of-categories">
{{ render(controller(
'App:Category:listAll',
)) }}
</div>
Upvotes: 0
Reputation: 3851
There are several possible solutions. The most obvious one would be to subclass AbstractController
, add a protected
method getCategories()
to it and call that method from all the controller methods where the categories are needed.
But still: this is very repetitive. Therefore, I’d probably add a custom Twig function, so that in any template where it is needed, you just write something like {{ displayCategories() }}
or {% for category in getCategories() %} ... {% endfor %}
, and the Twig extension handles all that for you. (See Twig docs for more info. This is not difficult. You just have to inject Doctrine as a dependeny to the extension’s constructor and overwrite getFunctions()
method from Twig_Extension
).
Upvotes: 1