Symfony 控制器的最佳实践

best practice of Symfony controllers

我正在寻找 symfony 控制器逻辑的最佳实践。我当前的代码 有一个控制器:

<?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,
        ]);
    }
}

类别实体:

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;
    }


}

我想将“$categories”变量重复到所有其他页面。 所以这意味着 - 每个页面都有额外的查询,但我不想重复代码:

$categories = $this->getDoctrine()
        ->getRepository(Categories::class)
        ->findAll();

无处不在,因为所有页面都必须始终显示类别。同样根据 symfony 逻辑,所有 @route 都应该有自己的功能。那么我应该如何制作路由逻辑而不是重复类别请求代码呢?通过使用此代码在 class 之外创建另一个并在所有其他路由方法中重用它?

编辑: 我的解决方案:

templates/index.html.twig文件(一处):

 {{ render(controller('App\Repository\CategoriesListRepository::getCategories')) }}

templates/categories.html.twig(一个文件):

{% 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()]);
    }
}

很高兴听到一些 comments/recommendations

有几种可能的解决方案。最明显的方法是子类化 AbstractController,向其添加 protected 方法 getCategories() 并从需要类别的所有控制器方法中调用该方法。

但仍然:这是非常重复的。因此,我可能会添加一个自定义 Twig 函数,这样在任何需要它的模板中,您只需编写 {{ displayCategories() }}{% for category in getCategories() %} ... {% endfor %} 之类的内容,Twig 扩展就会为您处理所有这些。 (有关更多信息,请参阅 Twig 文档。这并不难。您只需将 Doctrine 作为扩展的构造函数的依赖注入并覆盖 Twig_Extension 中的 getFunctions() 方法)。

您可以从模板调用控制器。

创建仅呈现类别列表的控制器:

public function listAllAction()
{
    $categories = $this->getDoctrine()
        ->getRepository(Categories::class)
        ->findAll();

    return $this->render('categories.html.twig', [
        'categories' => $categories,
    ]);
}

然后在 twig 文件中用 render 函数调用它:

<div id="list-of-categories">
    {{ render(controller(
        'App:Category:listAll',
    )) }}
</div>

看看How to Inject Variables into all Templates (i.e. global Variables)

您将创建以下配置:

# config/packages/twig.yaml
twig:
    # ...
    globals:
        # the value is the service's id
        category_service: '@App\Service\CategoryService'

并且在您的 CategoryService 中,您将在 getCategories() 方法中获得所有类别。稍后你可以调用你的树枝模板 category_service.getCategories().

您可以使用 $container->set() 方法将 $categories 作为容器的变量,然后使用 $container->get('categories').

获取变量

如果您只想避免重复代码(在不同的地方复制代码),以前的答案可能适合。但是如果你不想为了优化数据库性能而一遍又一遍地执行你的存储库调用,在我看来,将代码移动到不负责的地方(例如 twig 全局变量)并不是一个好习惯。

即使不同页面的结果可能始终相同,也可能是不同的结果 return。但那是你不应该担心的事情。

相反,我会为我的查询使用学说结果缓存。缓存应该判断请求的数据和之前请求的数据是否相同,然后把数据给我。

只要你使用的是 doctrine 而不是 DBAL,你就可以接受这个。