Sonata Extra Bundle cung cấp một mô-đun blog tích hợp trong Sonata Page, với các tuyến đường có thể tùy chỉnh và hiển thị phù hợp với các trang web đa ngôn ngữ.
Tích hợp các tuyến đường chính
Cập nhật các tuyến chính của blog
Tích hợp các trang của trang web của bạn với bộ điều khiển blog bằng cách thực hiện update-core-route đơn đặt hàng:
php bin/console sonata:page:update-core-routes --site={id}
Việc thực thi lệnh này sẽ đồng bộ trang Sonata với các tuyến đường lai sau:
- Danh mục blog:
/blog-category/{category} - Nhãn blog:
/blog-tag/{tag} - Bài viết trên blog :
/blog-article/{article}
Những tuyến đường này tương ứng với các trang được tạo động có liên kết với các bộ điều khiển tương ứng của chúng.
Tùy chỉnh các tuyến đường
Tùy chỉnh các mẫu tuyến đường mặc định để xác định vị trí hoặc thay đổi cấu trúc URL của bạn trong khi vẫn duy trì liên kết với bộ điều khiển bằng cách chỉnh sửa các chi tiết của trang:
- Một trình nghe sự kiện trên routing.loader được thêm tự động.
- Một tuyến đường mới được tạo với mô hình tùy chỉnh của bạn từ trường custom_url.

Tham khảo mẫu sau để tạo các đường dẫn URL trong Twig:
{{ path('sonata_extra_blog_category', {'category': category.getSlug()}) }}
{{ path('sonata_extra_blog_tag', {'tag': tag.getSlug()}) }}
{{ path('sonata_extra_blog_article', {'article': article.getSlug()}) }}
Mẫu và kết xuất
Liên kết việc sản xuất nội dung cho blog của bạn với cấu hình mô hình của bạn bằng cách sử dụng Blog page type. Loại dịch vụ trang này được giải thích chi tiết trong Tài liệu của SonataPageBundle.
Các biến Twig có sẵn
Cái Blog page dịch vụ cung cấp các biến sau cho mô hình của bạn để tích hợp liền mạch:

content: Nội dung HTML của blog của bạn.page: TheSonataPagePagethực thể instance.site: TheSonataPageSitethực thể instance.
Hiển thị nội dung blog của bạn trong mẫu Twig của bạn như sau:
{{ content|raw }}
Đối với bài viết trên trang, danh mục và thẻ, dịch vụ cũng sẽ cung cấp các cài đặt SEO:
ogTitleogDescriptionogImage: Đường dẫn của hình ảnh sẽ được tính toán sẵn. Hãy đảm bảo rằng bạn có một định dạng hình ảnh có tênog_imagenếu khôngreferencesẽ được sử dụng. Vì các mạng xã hội có chiều rộng/chiều cao tối đa, nên không khuyến khích sử dụngreferenceđịnh dạng.seoTitleseoKeywordseoDescription
Trong mô hình của bạn, bạn có thể sử dụng nó như vậy
{% block sonata_seo_title %}
{% apply spaceless %}
<title>{{ seoTitle|default('Titre par défaut') }}</title>
{% endapply %}
{% endblock %}
{% block sonata_seo_metadatas %}
{% apply spaceless %}
{% if seoDescription is defined %}
<meta name="description" content="{{ seoDescription }}">
{% endif %}
{% if seoKeyword is defined %}
<meta name="keywords" content="{{ seoKeyword }}">
{% endif %}
{% endapply %}
{% endblock %}
{% block app_og_metadatas %}
{% apply spaceless %}
{% if ogTitle is defined %}
<meta property="og:title" content="{{ ogTitle }}">
{% endif %}
{% if ogDescription is defined %}
<meta property="og:description" content="{{ ogDescription }}">
{% endif %}
{% if ogImage is defined %}
<meta property="og:image" content="{{ absolute_url(ogImage) }}">
{% endif %}
<meta property="og:type" content="article">
{% endapply %}
{% endblock %}
và trong mô hình cơ bản của bạn
{% block sonata_seo_title %}
{{ sonata_seo_title() }}
{% endblock %}
{% block sonata_seo_metadatas %}
{{ sonata_seo_metadatas() }}
{% endblock %}
{% block app_og_metadatas %}
{{ app_og_metadatas() }}
{% endblock %}
Thực thể Danh mục và Nhãn nên có các trường tham chiếu
- App\Entyty\SonataClassificationCategory.php :
<?php
namespace App\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Sonata\ClassificationBundle\Entity\BaseCategory;
use Partitech\SonataExtra\Traits\EntityTranslationTrait;
use Partitech\SonataExtra\Contract\MediaInterface;
#[ORM\Entity]
#[ORM\Table(name: 'classification__category')]
class SonataClassificationCategory extends BaseCategory
{
use EntityTranslationTrait;
#[ORM\Id]
#[ORM\Column(type: Types::INTEGER)]
#[ORM\GeneratedValue]
protected ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
#[ORM\ManyToOne(targetEntity: MediaInterface::class, cascade: ["persist"])]
#[ORM\JoinColumn(name: "featured_image__media_id", referencedColumnName: "id", nullable: true, onDelete: "SET NULL")]
private $featured_image = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_title = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_keywords = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $seo_description = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_og_title = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $seo_og_description = null;
#[ORM\ManyToOne(targetEntity: MediaInterface::class, cascade: ["persist"])]
#[ORM\JoinColumn(name: "seo_og_image__media_id", referencedColumnName: "id", nullable: true, onDelete: "SET NULL")]
private $seo_og_image = null;
public function getFeaturedImage()
{
return $this->featured_image;
}
public function setFeaturedImage($featured_image): self
{
$this->featured_image = $featured_image;
return $this;
}
public function getSeoTitle(): ?string {
return $this->seo_title;
}
public function setSeoTitle(?string $seo_title): self {
$this->seo_title = $seo_title;
return $this;
}
public function getSeoKeywords(): ?string {
return $this->seo_keywords;
}
public function setSeoKeywords(?string $seo_keywords): self {
$this->seo_keywords = $seo_keywords;
return $this;
}
public function getSeoDescription(): ?string {
return $this->seo_description;
}
public function setSeoDescription(?string $seo_description): self {
$this->seo_description = $seo_description;
return $this;
}
public function getSeoOgTitle(): ?string {
return $this->seo_og_title;
}
public function setSeoOgTitle(?string $seo_og_title): self {
$this->seo_og_title = $seo_og_title;
return $this;
}
public function getSeoOgDescription(): ?string {
return $this->seo_og_description;
}
public function setSeoOgDescription(?string $seo_og_description): self {
$this->seo_og_description = $seo_og_description;
return $this;
}
public function getSeoOgImage() {
return $this->seo_og_image;
}
public function setSeoOgImage($seo_og_image): self {
$this->seo_og_image = $seo_og_image;
return $this;
}
}
- App\Entyty\SonataClassificationTag :
<?php
declare(strict_types=1);
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\DBAL\Types\Types;
use App\Repository\SonataClassificationTagRepository;
use Sonata\ClassificationBundle\Entity\BaseTag;
use Partitech\SonataExtra\Traits\EntityTranslationTrait;
use Partitech\SonataExtra\Contract\MediaInterface;
#[ORM\Entity(repositoryClass: SonataClassificationTagRepository::class)]
#[ORM\Table(name: 'classification__tag')]
#[ORM\HasLifecycleCallbacks]
class SonataClassificationTag extends BaseTag
{
use EntityTranslationTrait;
#[ORM\Id]
#[ORM\Column(type: Types::INTEGER)]
#[ORM\GeneratedValue]
protected ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
#[ORM\ManyToOne(targetEntity: MediaInterface::class, cascade: ["persist"])]
#[ORM\JoinColumn(name: "featured_image__media_id", referencedColumnName: "id", nullable: true, onDelete: "SET NULL")]
private $featured_image = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_title = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_keywords = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $seo_description = null;
#[ORM\Column(type: 'string', length: 255, nullable: true)]
private ?string $seo_og_title = null;
#[ORM\Column(type: 'text', nullable: true)]
private ?string $seo_og_description = null;
#[ORM\ManyToOne(targetEntity: MediaInterface::class, cascade: ["persist"])]
#[ORM\JoinColumn(name: "seo_og_image__media_id", referencedColumnName: "id", nullable: true, onDelete: "SET NULL")]
private $seo_og_image = null;
public function getFeaturedImage()
{
return $this->featured_image;
}
public function setFeaturedImage($featured_image): self
{
$this->featured_image = $featured_image;
return $this;
}
public function getSeoTitle(): ?string {
return $this->seo_title;
}
public function setSeoTitle(?string $seo_title): self {
$this->seo_title = $seo_title;
return $this;
}
public function getSeoKeywords(): ?string {
return $this->seo_keywords;
}
public function setSeoKeywords(?string $seo_keywords): self {
$this->seo_keywords = $seo_keywords;
return $this;
}
public function getSeoDescription(): ?string {
return $this->seo_description;
}
public function setSeoDescription(?string $seo_description): self {
$this->seo_description = $seo_description;
return $this;
}
public function getSeoOgTitle(): ?string {
return $this->seo_og_title;
}
public function setSeoOgTitle(?string $seo_og_title): self {
$this->seo_og_title = $seo_og_title;
return $this;
}
public function getSeoOgDescription(): ?string {
return $this->seo_og_description;
}
public function setSeoOgDescription(?string $seo_og_description): self {
$this->seo_og_description = $seo_og_description;
return $this;
}
public function getSeoOgImage() {
return $this->seo_og_image;
}
public function setSeoOgImage($seo_og_image): self {
$this->seo_og_image = $seo_og_image;
return $this;
}
}
Điều chỉnh ví dụ cho phù hợp với phiên bản đã cài đặt
Các ví dụ Symfony và Sonata vẫn gắn với các phiên bản được chỉ ra trong bài viết. Symfony 4.x và Symfony 6.2 không còn được duy trì: đối với một dự án hiện tại, hãy sử dụng một nhánh được duy trì và tài liệu tương ứng chính xác với các phụ thuộc của bạn.
php bin/console about
composer show symfony/framework-bundle sonata-project/admin-bundle
Trước khi tiếp tục với mã, hãy kiểm tra các chữ ký, dịch vụ, tuyến đường và mẫu liên quan. Sau đó thêm một bài kiểm tra chức năng bao phủ quyền, mã HTTP và kết quả hiển thị.
Tham chiếu : phiên bản Symfony được hỗ trợ.