Sonata Extra Bundle fornisce un modulo blog integrato in Sonata Page, con percorsi personalizzabili e una resa adatta ai siti multilingue.
Integrazione delle strade principali
Aggiornamento delle principali rotte del blog
Integra le pagine del tuo sito con il controller del blog eseguendo il update-core-route ordine:
php bin/console sonata:page:update-core-routes --site={id}
L'esecuzione di questo comando sincronizza la pagina Sonata con i seguenti itinerari ibridi:
- Categoria del blog:
/blog-category/{category} - Etichetta del blog:
/blog-tag/{tag} - Articolo del blog :
/blog-article/{article}
Queste rotte corrispondono a delle pagine generate dinamicamente che sono collegate ai rispettivi controller.
Personalizzazione delle rotte
Personalizza i modelli di rotta predefiniti per localizzare o modificare la struttura del tuo URL pur mantenendo il collegamento con il controllore modificando i dettagli della pagina:
- Un ascoltatore di eventi su routing.loader viene aggiunto automaticamente.
- Una nuova rotta è creata con il tuo modello personalizzato a partire dal campo custom_url.

Fate riferimento al modello seguente per generare percorsi URL in 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()}) }}
Modelli e resa
Collegate la produzione di contenuti del vostro blog alla configurazione del vostro modello usando il Blog page type. Questo tipo di servizio di pagina è spiegato in dettaglio in Documentazione di SonataPageBundle.
Variabili Twig disponibili
Il Blog page Il servizio fornisce le seguenti variabili al tuo modello per un'integrazione senza soluzione di continuità:

content: Il contenuto HTML del tuo blog.page: IlSonataPagePageistanza di entità.site: IlSonataPageSiteistanza di entità.
Visualizza il contenuto del tuo blog nel tuo modello Twig come segue:
{{ content|raw }}
Per l'articolo della pagina, la categoria e i tag, il servizio metterà inoltre a disposizione dei parametri SEO:
ogTitleogDescriptionogImage: Il percorso dell'immagine sarà già calcolato. Assicurati di avere un formato immagine nominatoog_imagealtrimentireferencesarà utilizzato. Poiché i social network hanno una larghezza/altezza massima, non è consigliabile utilizzare ilreferenceformato.seoTitleseoKeywordseoDescription
Nel vostro modello, potete usarlo in questo modo
{% 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 %}
e nel vostro modello di base
{% block sonata_seo_title %}
{{ sonata_seo_title() }}
{% endblock %}
{% block sonata_seo_metadatas %}
{{ sonata_seo_metadatas() }}
{% endblock %}
{% block app_og_metadatas %}
{{ app_og_metadatas() }}
{% endblock %}
L'entità Categoria e Etichetta dovrebbe avere i campi di riferimento
- 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;
}
}
Adattare l’esempio alla versione installata
Gli esempi Symfony e Sonata rimangono legati alle versioni indicate nell’articolo. Symfony 4.x e Symfony 6.2 non sono più supportati: per un progetto attuale, utilizzate un ramo mantenuto e la documentazione corrispondente esattamente alle vostre dipendenze.
php bin/console about
composer show symfony/framework-bundle sonata-project/admin-bundle
Prima di riprendere il codice, controllate le firme, i servizi, le rotte e i template interessati. Aggiungete poi un test funzionale che copra le autorizzazioni, il codice HTTP e il risultato visibile.
Riferimento : versioni di Symfony supportate.