Я новичок и пытаюсь следовать руководству по созданию блога под Symfony 7, к сожалению, у меня возникает ошибка, когда я хочу отобразить страницу статьи, используя его адрес (слаг). Я получаю:
Контроллеру «App\Controller\ArticleController::show» требуется аргумент «$article», который не удалось разрешить. Невозможно автоматически подключить аргумент $article «App\Controller\ArticleController::show()»: ему нужен экземпляр «App\Entity\Article», но этот тип исключен из «config/services.yaml».
Проблема возникла, когда я внедрил свою сущность Article в функцию show моего ArticleController.
<?php
namespace App\Controller;
use App\Entity\Article;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ArticleController extends AbstractController
{
#[Route('/article/{slug}', name: 'article_show')]
public function show(Article $article): Response
{
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
}
Когда я использую ссылку с домашней страницы, отображаемый маршрут правильный (с меткой).
Есть идеи, как я могу это исправить? (это должно быть что-то очень простое, но я не понимаю)
Я попытался проверить свой код, особенно объект:
<?php
namespace App\Entity;
use App\Model\TimestampedInterface;
use App\Repository\ArticleRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: ArticleRepository::class)]
class Article implements TimestampedInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 255)]
private ?string $title = null;
#[ORM\Column(length: 255)]
private ?string $slug = null;
#[ORM\Column(type: Types::TEXT, nullable: true)]
private ?string $content = null;
#[ORM\Column(length: 100, nullable: true)]
private ?string $featuredText = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $createdAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $updatedAt = null;
/**
* @var Collection<int, Category>
*/
#[ORM\ManyToMany(targetEntity: Category::class, mappedBy: 'articles')]
private Collection $categories;
/**
* @var Collection<int, Comment>
*/
#[ORM\OneToMany(targetEntity: Comment::class, mappedBy: 'article', orphanRemoval: true)]
private Collection $comments;
#[ORM\ManyToOne]
private ?Media $featuredImage = null;
public function __construct()
{
$this->categories = new ArrayCollection();
$this->comments = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getTitle(): ?string
{
return $this->title;
}
public function setTitle(string $title): static
{
$this->title = $title;
return $this;
}
public function getSlug(): ?string
{
return $this->slug;
}
public function setSlug(string $slug): static
{
$this->slug = $slug;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
public function setContent(?string $content): static
{
$this->content = $content;
return $this;
}
public function getFeaturedText(): ?string
{
return $this->featuredText;
}
public function setFeaturedText(?string $featuredText): static
{
$this->featuredText = $featuredText;
return $this;
}
public function getCreatedAt(): ?\DateTimeInterface
{
return $this->createdAt;
}
public function setCreatedAt(\DateTimeInterface $createdAt): static
{
$this->createdAt = $createdAt;
return $this;
}
public function getUpdatedAt(): ?\DateTimeInterface
{
return $this->updatedAt;
}
public function setUpdatedAt(?\DateTimeInterface $updatedAt): static
{
$this->updatedAt = $updatedAt;
return $this;
}
/**
* @return Collection<int, Category>
*/
public function getCategories(): Collection
{
return $this->categories;
}
public function addCategory(Category $category): static
{
if (!$this->categories->contains($category)) {
$this->categories->add($category);
$category->addArticle($this);
}
return $this;
}
public function removeCategory(Category $category): static
{
if ($this->categories->removeElement($category)) {
$category->removeArticle($this);
}
return $this;
}
/**
* @return Collection<int, Comment>
*/
public function getComments(): Collection
{
return $this->comments;
}
public function addComment(Comment $comment): static
{
if (!$this->comments->contains($comment)) {
$this->comments->add($comment);
$comment->setArticle($this);
}
return $this;
}
public function removeComment(Comment $comment): static
{
if ($this->comments->removeElement($comment)) {
// set the owning side to null (unless already changed)
if ($comment->getArticle() === $this) {
$comment->setArticle(null);
}
}
return $this;
}
public function getFeaturedImage(): ?Media
{
return $this->featuredImage;
}
public function setFeaturedImage(?Media $featuredImage): static
{
$this->featuredImage = $featuredImage;
return $this;
}
}
Вы это поняли, поэтому цель состоит в том, чтобы отобразить страницу статьи:
{% extends 'base.html.twig' %}
{% block title %}{{ article.title }}{% endblock %}
{% block body %}
<div class = "container">
<h1>{{ article.title }}</h1>
<hr>
{{ article.content|raw }}
</div>
{% endblock %}
Большое спасибо,
EDIT1: Вот код домашней страницы, на которой создается ссылка на страницу статьи. Но какой бы пул ни был указан в URL-адресе (существует он или нет в базе данных), сообщение об ошибке одно и то же.
{% extends 'base.html.twig' %}
{% block title %}Accueil{% endblock %}
{% block body %}
<main class = "container">
<div class = "row">
<div class = "col-md-8">
<h3 class = "pb-4 mb-4 fst-italic border-bottom">Articles récents</h3>
<div id = "articles-list">
{% include 'article/list.html.twig' with { articles } %}
</div>
</div>
<div class = "col-md-4">
<div class = "position-sticky" style = "top: 2rem;">
{% include 'widget/about.html.twig' %}
{% include 'widget/categories.html.twig' %}
</div>
</div>
</div>
</main>
{% endblock %}
Шаблон list.html.twig:
{% for article in articles %}
{% include 'article/item.html.twig' with { article } %}
{% endfor %}
элемент.html.twig:
{% set article_show = path('article_show', { 'slug': article.slug }) %}
<article class = "mb-5">
<div class = "row">
<div class = "col-md-5">
{% if article.featuredImage %}
<a href = "{{ article_show }}">
<img src = "/uploads/{{ article.featuredImage.filename }}" alt = "{{ article.featuredImage.altText }}" loading = "lazy" width = "350" height = "205">
</a>
{% endif %}
</div>
<div class = "col-md-7">
<h2>
<a class = "text-decoration-none" href = "{{ article_show }}">{{ article.title }}</a>
</h2>
<p>
{{ article.createdAt|date('d M Y') }}
</p>
{{ article.featuredText ?: article.content|striptags|slice(0, 130) ~ '...' }}
</div>
</div>
</article>
EDIT2, у меня есть только одна статья в базе данных, и в ней есть пуля. Обычно пул не может быть нулевым, но если мне удастся пройти этот шаг, я добавлю перенаправление на случай, если статья не будет найдена:
class ArticleController extends AbstractController
{
#[Route('/article/{slug}', name: 'article_show')]
public function show(?Article $article): Response
{
if (!$article) {
return $this->redirectToRoute('app_home');
}
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
}
На данный момент, если я применю этот код, статья не будет найдена, и мы вернемся на домашнюю страницу (это кажется нормальным, учитывая ошибку, которая у меня была до этого).
EDIT3. Вот HomeController с findAll():
<?php
namespace App\Controller;
use App\Repository\ArticleRepository;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class HomeController extends AbstractController
{
#[Route('/', name: 'app_home')]
public function index(ArticleRepository $articleRepo): Response
{
return $this->render('home/index.html.twig', [
'articles' => $articleRepo->findAll()
]);
}
}
И репозиторий статей:
<?php
namespace App\Repository;
use App\Entity\Article;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
class ArticleRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Article::class);
}
}
EDIT4, я только что проверил, заменив пул на идентификатор статьи в ArticleController:
<?php
namespace App\Controller;
use App\Entity\Article;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ArticleController extends AbstractController
{
#[Route('/article/{id}', name: 'article_show')]
public function show(Article $article): Response
{
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
}
Это прекрасно работает с использованием URL-адреса, содержащего идентификатор статьи, что заставляет меня думать, что мой код хорош. Однако до сих пор нет решения, позволяющего заставить его работать с пулей.
Код добавлен в мой исходный пост, спасибо.
Итак, вы предполагаете, что slug
не имеет значения null при создании ссылки, но slug
имеет значение null в вашей сущности. Я предполагаю, что в вашей базе данных есть хотя бы одна запись статьи, где slug
имеет значение null, и в этом проблема. Убедитесь, что все ваши пули не являются нулевыми и уникальными.
Когда вы выгружаете статьи, у вас есть слаг для всех ваших статей?
У меня есть только одна статья в базе данных, и в ней есть пул. Обычно пул не может быть нулевым. (Я отредактировал свое сообщение для получения дополнительной информации)
в вашем домашнем шаблоне, я думаю, ваши переменные статьи взяты из метода findAll() вашего репозитория статей?
Да, абсолютно. Пост отредактирован с кодами.
В вашем EDIT2: у вас есть общедоступный метод show(), и вы вводите объектную статью... Каков маршрут этого метода? Используете ли вы параметр маршрута для определения объекта?
Это был просто ответ Крейгу, чтобы проиллюстрировать, что я буду делать (после того, как проблема будет решена), чтобы перенаправить в случае, если статья не будет найдена. На данный момент используемый код является самым первым в моем сообщении. (Я полностью отредактировал EDIT2 с функцией, чтобы было понятнее)
И когда вы заменяете метод show кодом, который я предоставил в своем ответе, у вас все равно возникает первоначальная ошибка (невозможно автоматически подключить $article...)?
Я только что перепроверил, та же проблема. Я создал вторую статью, та же проблема. Какой бы пул не указан в URL-адресе (существует он или нет в базе данных), проблема та же.
Извините, но со всеми этими элементами я действительно не понимаю, как у вас все еще может быть та же ошибка :(
Я тоже, но большое спасибо за вашу помощь и время.
К вашему сведению, это отлично работает, если заменить пул на идентификатор статьи (см. EDIT4 в моем посте).
Вы можете использовать атрибут MapEntity для решения вашей проблемы следующим образом:
<?php
namespace App\Controller;
use App\Entity\Article;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
class ArticleController extends AbstractController
{
#[Route('/article/{slug}', name: 'article_show')]
public function show(#[MapEntity(mapping:['slug'=>'slug'])]Article $article): Response
{
return $this->render('article/show.html.twig', [
'article' => $article,
]);
}
}
Атрибут mapEntity позволит Symfony найти объект с пулем, который у вас есть в параметре маршрута.
Спасибо за ваш ответ. К сожалению, я получаю ту же ошибку. Что бы ни указывал пуля (существует ли она в базе данных или нет), та же ошибка.
Как сказал @craig, покажите нам код, в котором вы генерируете ссылку, потому что я не понимаю, как у вас все еще может быть та же ошибка.
Я только что отредактировал свой первый пост, спасибо.
Ошибка говорит
«нужен экземпляр «App\Entity\Article», но этот тип был исключен в "config/services.yaml""
Так что я думаю, что есть некоторые
App\:
resource: '../src/'
exclude:
- ...
- '../src/Entity/'
в этом файле, поэтому, если вы удалили эту строку...
Сущности не являются службами и не должны автоматически подключаться.
Спасибо за ваш ответ. Я уже исследовал этот путь. Действительно, если я изменю файл Services.yaml, я выдаю ошибку, но моя статья не появляется. На самом деле это не решает проблему, и (даже если я новичок) я согласен в этом с Крейгом.
После нескольких часов борьбы и изучения я нашел простое решение: просто установите значение «true» для «auto_mapping» в файле Doctor.yaml:
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
рад слышать, что вы нашли свое решение. Я не учел этого, потому что предполагал, что вы использовали бы композитор и flex для установки доктрины, которая бы установила это значение в своем рецепте. В будущем подумайте о композиторе и flex, вам будет легче добиться успеха.
Спасибо, кажется, я установил композитор (насчет flex не помню). Но так как я новичок, есть некоторые очевидные вещи, которые не сразу приходят в голову.
покажите код, в котором вы генерируете ссылку на страницу статьи.