Sonata là một bộ các bundle dành cho Symfony 4. Một trong số đó cho phép quản lý nội dung theo cách giống như WordPress.
Trên internet có rất ít tài liệu hướng dẫn, và có lý do cho điều đó, quá trình cài đặt có lỗi, và ít người đã đối mặt với trình theo dõi lỗi trên GitHub, đã bị nói thẳng là phải đọc kỹ tài liệu.
Chấp nhận thất bại như vậy không phải là phong cách của tôi.
Dưới đây là cách cài đặt và cấu hình Sonata-Page-Bundle, với quá trình cài đặt chính thức, và cách khắc phục những lỗi khốn kiếp này.
Bước 1, chúng ta sẽ lấy khung Symfony 4 của mình bao gồm cơ sở hoạt động của framework được bổ sung bởi Sonata + FosUser + Media.
Như vậy chúng ta đã tiết kiệm được một ngày tốt lành ^^
Nói chung, bạn nên theo dõi tài liệu hướng dẫn: https://sonata-project.org/bundles/page/3-x/doc/reference/installation.html
Cài đặt tất cả các Bundle, và theo dõi kỹ lưỡng tài liệu hướng dẫn.
Khó khăn đầu tiên là cài đặt thông qua composer. Tôi không thể cài đặt tất cả cho php 7.1. Cũng không phải cho php 7.2. Luôn có một gói xung đột.
Mẹo trong composer là bước ra khỏi ổn định với chỉ thị minimum-stability trong composer.json của bạn và từ đó, sau 3 hoặc 4 giờ thử nghiệm phiên bản, bạn có thể cài đặt tất cả.
Dưới đây là composer.json của tôi
{
"type": "project",
"license": "proprietary",
"require": {
"php": "^7.1.3",
"ext-ctype": "*",
"ext-iconv": "*",
"doctrine/doctrine-migrations-bundle": "^3.0",
"jms/serializer-bundle": "^3.7",
"liip/imagine-bundle": "^2.3",
"nelmio/api-doc-bundle": "^3.6",
"phpoffice/phpspreadsheet": "^1.14",
"pixassociates/sortable-behavior-bundle": "^1.5",
"prodigious/sonata-menu-bundle": "^3.0",
"sensio/framework-extra-bundle": "^5.5",
"sonata-project/block-bundle": "^3.20",
"sonata-project/cache-bundle": "^2.4",
"sonata-project/core-bundle": "^3.20",
"sonata-project/doctrine-orm-admin-bundle": "^3.20",
"sonata-project/formatter-bundle": "^4.2",
"sonata-project/intl-bundle": "^2.7",
"sonata-project/media-bundle": "^3.25",
"sonata-project/notification-bundle": "^3.8",
"sonata-project/page-bundle": "^3.17",
"sonata-project/seo-bundle": "^2.11",
"sonata-project/user-bundle": "^4.6",
"symfony/apache-pack": "^1.0",
"symfony/console": "4.*",
"symfony/debug-pack": "^1.0",
"symfony/dotenv": "4.4.*",
"symfony/flex": "^1.3.1",
"symfony/framework-bundle": "4.4.*",
"symfony/serializer-pack": "^1.0",
"symfony/swiftmailer-bundle": "^3.4",
"symfony/templating": "4.4.*",
"symfony/translation": "4.4.*",
"symfony/var-dumper": "4.4.*",
"symfony/yaml": "4.*",
"twig/twig": "^2.9.0"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.3",
"symfony/maker-bundle": "^1.19"
},
"config": {
"preferred-install": {
"*": "dist"
},
"sort-packages": true
},
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
},
"replace": {
"paragonie/random_compat": "2.*",
"symfony/polyfill-ctype": "*",
"symfony/polyfill-iconv": "*",
"symfony/polyfill-php71": "*",
"symfony/polyfill-php70": "*",
"symfony/polyfill-php56": "*"
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"conflict": {
"symfony/symfony": "*"
},
"extra": {
"symfony": {
"allow-contrib": false,
"require": "4.4.*"
}
}
,
"minimum-stability": "dev",
"prefer-stable": true
}
Sau khi các gói được cài đặt và cấu hình, đừng quên cài đặt CKEditor, và các tài nguyên. Và nhân tiện, người dùng quản trị cấp cao của bạn.
php bin/console assets:install
php bin/console ckeditor:install
mkdir -p public/uploads/media
php bin/console cache:clear
php bin/console fos:user:create admin tbourdin@partitech.com admin --super-admin
Đến lúc tạo trang web đầu tiên:
php app/console sonata:page:create-site
Please define a value for Site.name : Skeletton CMS
Please define a value for Site.host : www.skeleton-cms.test
Please define a value for Site.relativePath : /
Please define a value for Site.enabled : true
Please define a value for Site.locale : -
Please define a value for Site.enabledFrom : now
Please define a value for Site.enabledTo : +10 years
Please define a value for Site.default : true
Creating website with the following information :
name : www.skeleton-cms.test
site : http(s)://www.skeleton-cms.test
enabled : Tue, 10 Jul 2030 16:12:08 +0100 => Mon, 10 Jan 2022 16:12:08 +0100
Confirm site creation ?y
Site created !
You can now create the related pages and snapshots by running the followings commands:
php app/console sonata:page:update-core-routes --site=1
php app/console sonata:page:create-snapshots --site=1
Trang web đầu tiên giờ đã hoạt động. Tốt gần như vậy.
Trong giao diện trang, bạn cần tạo trang đầu tiên của mình

Cấu hình mặc định không chứa gì cả. Do đó, cần phải tổ chức nội dung của trang này thông qua một trình biên tập, cho phép tổ chức trang của chúng ta dưới dạng các khối, được gán cho các khu vực của mẫu của chúng ta.
Trong cấu hình gói của chúng tôi, chúng tôi đã khai báo một mẫu mặc định. Và đây là cấu hình cho phép chúng tôi mô hình hóa giao diện quản lý khối
default_template: default
templates:
default:
path: '@SonataPage/layout.html.twig'
name: 'default'
containers:
header:
name: Header
content_top:
name: Top content
content:
name: Main content
content_bottom:
name: Bottom content
footer:
name: Footer
matrix:
layout: |
HHHHHHHH
HHHHHHHH
TTTTTTTT
TTTTTTTT
CCCCCCCC
CCCCCCCC
BBBBBBBB
BBBBBBBB
FFFFFFFF
FFFFFFFF
mapping:
H: header
T: content_top
C: content
B: content_bottom
F: footer

Trong ví dụ trên, chúng tôi đã chèn 2 khối vào khu vực của mình.
Khối đầu tiên là một văn bản đơn giản, khối thứ hai là một menu (một menu chân trang cổ điển).
Để chèn một khối, chúng tôi đặt mình vào một khu vực bên trái và nhấp vào nút + bên phải để chọn loại khối sau khi đã chọn nó từ danh sách.
Mọi quản lý trang đều được thực hiện theo cách này.
Điều này có nghĩa là chúng tôi sẽ phải tạo một loạt khối theo nhu cầu của ứng dụng của mình.
Và để tạo một khối, thật đơn giản. Chúng tôi sẽ lấy ví dụ về khối menu của mình.
Các menu được quản lý thông qua gói Sonata-Menu-Bundle tương thích với Sonata-Page-Bundle. Nhưng nó không đi kèm với một khối mặc định. Điều đó thật ngớ ngẩn vì menu là một thứ cơ bản.
Vì vậy, chúng tôi sẽ tạo khối của mình trong /src/Block/SonataMenuBlockService.php
<?php
namespace App\Block;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Block\Service\AbstractAdminBlockService;
use Sonata\BlockBundle\Meta\Metadata;
use Sonata\BlockBundle\Model\BlockInterface;
use Sonata\Form\Type\ImmutableArrayType;
use Sonata\PageBundle\Model\Page;
use Sonata\PageBundle\Model\PageManagerInterface;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Sonata\AdminBundle\Admin\Pool;
use Sonata\BlockBundle\Block\BaseBlockService;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;
use Doctrine\ORM\EntityManager;
use Sonata\CoreBundle\Validator\ErrorElement;
class SonataMenuBlockService extends AbstractAdminBlockService
{
/**
* @var PageManagerInterface
*/
protected $pageManager;
/**
* @param string $name
*/
public function __construct($name, EngineInterface $templating, Pool $pool, EntityManager $em , PageManagerInterface $pageManager)
{
parent::__construct($name, $templating);
$this->pool = $pool;
$this->em = $em;
$this->pageManager = $pageManager;
}
public function getName()
{
return 'Partitech Menu Block';
}
public function buildEditForm(FormMapper $formMapper, BlockInterface $block)
{
$menu_list=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\Menu')->findAll();
if(!empty($menu_list))
{
$option_list=array();
foreach($menu_list as $m)
{
$option_list[$m->getAlias()]=$m->getName();
}
}
$formMapper->add('settings', ImmutableArrayType::class, [
'keys' => [
/* ['title', TextType::class, [
'label' => 'form.label_title',
'required' => false,
]],*/
['menu', ChoiceType::class, [
'label' => 'Menu',
'choices' => array_flip($option_list),
]],
['template', TextType::class, [
'label' => 'template',
'required' => false,
]],
],
'translation_domain' => 'SonataPageBundle',
]);
}
public function execute(BlockContextInterface $blockContext, ?Response $response = null)
{
$settings=$blockContext->getSettings();
$systemElements = $this->pageManager->findBy([
'url' => null,
'parent' => null,
]);
$menu_list=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\Menu')->findOneBy(['alias'=> $settings['menu']]);
if(!empty($menu_list)){
$menu=$this->pool->getContainer()->get('doctrine')->getManager()->getRepository('Prodigious\Sonata\MenuBundle\Entity\MenuItem');
$mm = $this->pool->getContainer()->get('prodigious_sonata_menu.manager');
$menu = $mm->load($menu_list->getId());
// $status = true (Get enabled menu items)
// $status = false (Get disabled menu items)
// getMenuItems($menu, $root = MenuManager::ITEM_CHILD, $status = MenuManager::STATUS_ALL)
$menuItems = $mm->getMenuItems($menu, true, true);
}else{
$menuItems=false;
}
;
return $this->renderResponse($blockContext->getTemplate(), [
'context' => $blockContext,
'block' => $blockContext->getBlock(),
'settings' => $blockContext->getSettings(),
'systemElements' => $systemElements,
'menuItems'=>$menuItems
], $response);
}
public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'menu'=>'menu',
'template' => '/Block/Menu/menu.html.twig',
]);
}
public function getBlockMetadata($code = null)
{
return new Metadata($this->getName(), (null !== $code ? $code : $this->getName()), false, 'SonataPageBundle', [
'class' => 'fa fa-list-ul',
]);
}
}
Chúng tôi thêm mẫu của mình trong /Block/Menu/menu.html.twig và tệp macro menu trong /Block/Menu/menu_tree_macro.html.twig.
Đó đơn giản chỉ là các tệp mặc định của gói được bao quanh bởi một div chứa thông tin khối cho chỉnh sửa phía front-end.
{#/Block/Menu/menu.html.twig#}
{% if block.name|length %}
{% set name=block.name%}
{% else %}
{% set name=block.settings.menu %}
{% endif %}
<div id="cms-block-25" class="cms-block cms-block-element ui-sortable-handle" data-id="{{ block.id }}" data-name="{{ name }}" data-role="block" data-page-id="{{ block.page.id }}">
{% if menuItems is not null and menuItems|length > 0 %}
{% import 'Block/Menu/menu_tree_macro.html.twig' as tree %}
{% set currentPath = app.request.requestUri %}
{{ tree.menu(menuItems, currentPath) }}
{% endif %}
</div>
{#/Block/Menu/menu_tree_macro.html.twig#}
{% macro menu(items, currentPath) %}
{% import _self as self %}
<ul>
{% for menuItem in items %}
{% set url = menuItem.url %}
{% set attributes = "menu-item" %}
{% if menuItem.classAttribute %}
{% set attributes = attributes ~ ' ' ~ menuItem.classAttribute %}
{% endif %}
{% if menuItem.hasChild() %}
{% set attributes = attributes ~ ' has-child' %}
{% for childItem in menuItem.children %}
{% set childUrl = childItem.url %}
{% if childUrl == currentPath %}
{% set attributes = attributes ~ ' current-parent' %}
{% endif %}
{% endfor %}
{% endif %}
<li class="{{ attributes }}" role="menu-item">
{% if menuItem.hasChild() %}
<a href="{{ url }}" class="parent" {% if currentPath == url %} class="current"{% endif %}" {% if menuItem.target %} target="_blank"{% endif %}>{{ menuItem.name }}</a>
{{ self.menu(menuItem.children, currentPath) }}
{% else %}
<a href="{{ url }}" {% if currentPath == url %} class="current"{% endif %}" {% if menuItem.target %} target="_blank"{% endif %}>{{ menuItem.name }}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}
Chúng tôi tham chiếu khối của mình trong dịch vụ của mình
services:
sonata.block.service.sonata_menu:
class: App\Block\SonataMenuBlockService
arguments:
- "sonata.block.service.sonata_menu"
- "@templating"
- "@sonata.admin.pool"
- "@doctrine.orm.entity_manager"
- "@sonata.page.manager.page"
tags:
- { name: sonata.block }
Và sau đó chúng tôi có khối mới của mình có sẵn trong danh sách khối của mình. Bạn có thể làm bất cứ điều gì trong một khối, theo cách của một bộ điều khiển.

Vì vậy, chúng tôi có thể tạo menu chân trang của mình

Và sử dụng nó trong khối của mình

Đây là hiển thị của trang của chúng tôi

Thực ra, không. Đó là hiển thị với một mẫu đã được sửa. Nếu không, hiển thị sẽ trông như thế này:

Vì vậy, chúng tôi sẽ phải sao chép mẫu cơ sở vào dự án của mình và sửa những gì sai.
Chúng tôi tạo mẫu base_layout và mẫu test của mình test-layout trong thư mục Layouts/
{#/templates/Layouts/base_layout.html.twig#}
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{%- block sonata_page_html_tag -%}
<!DOCTYPE html>
<html {{ sonata_seo_html_attributes() }}>
{% endblock %}
{% block sonata_page_head %}
<head {{ sonata_seo_head_attributes() }}>
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
{{ sonata_seo_title() }}
{{ sonata_seo_metadatas() }}
{% block sonata_page_stylesheets %}
{% block page_stylesheets %} {# Deprecated block #}
{% for stylesheet in sonata_page.assets.stylesheets %}
<link rel="stylesheet" href="{{ asset(stylesheet) }}" media="all">
{% endfor %}
{% endblock %}
{% endblock %}
{% block sonata_page_javascripts %}
{% block page_javascripts %} {# Deprecated block #}
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% for js in sonata_page.assets.javascripts %}
<script src="{{ asset(js) }}"></script>
{% endfor %}
{% endblock %}
{% endblock %}
</head>
{% endblock %}
{% block sonata_page_body_tag %}
<body class="sonata-bc">
{% endblock %}
{% block sonata_page_top_bar %}
{% block page_top_bar %} {# Deprecated block #}
{% if sonata_page.isEditor or ( app.user and is_granted('ROLE_PREVIOUS_ADMIN') ) %}
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
<!-- CMS specific variables -->
<script>
jQuery(document).ready(function() {
Sonata.Page.init({
url: {
block_save_position: '{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}',
block_edit: '{{ sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': 'BLOCK_ID'}) }}'
}
});
});
</script>
{% endif %}
<header class="sonata-bc sonata-page-top-bar navbar navbar-inverse navbar-fixed-top" role="banner"
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
data-page-editor='{{ {
url: {
block_save_position: sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page),
block_edit: sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': 'BLOCK_ID'})
}
}|json_encode()|raw }}'
{% endif %}>
<div class="container">
<ul class="nav navbar-nav">
{% if app.user and is_granted('ROLE_SONATA_ADMIN') %}
<li><a href="{{ path('sonata_admin_dashboard') }}">{{ "header.sonata_admin_dashboard"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
{% if sonata_page.isEditor %}
{% set sites = sonata_page.siteavailables %}
{% if sites|length > 1 and site is defined %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ site.name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
{% for site in sites %}
<li><a href="{{ site.url }}">{{ site.name }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Page <span class="caret"></span></a>
<ul class="dropdown-menu">
{% if page is defined %}
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}" target="_new">{{ "header.edit_page"|trans({}, 'SonataPageBundle') }}</a></li>
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page|sonata.page.admin.snapshot', 'list', page) }}" target="_new">{{ "header.create_snapshot"|trans({}, 'SonataPageBundle') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ sonata_admin.url('sonata.page.admin.page', 'list') }}" target="_new">{{ "header.view_all_pages"|trans({}, 'SonataPageBundle') }}</a></li>
{% if error_codes is defined and error_codes|length %}
<li class="divider"></li>
<li><a href="{{ path('sonata_page_exceptions_list') }}" target="_new">{{ "header.view_all_exceptions"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
</ul>
</li>
{% if page is defined %}
<li>
<a href="{{ sonata_admin.url('sonata.page.admin.page', 'compose', {'id': page.id}) }}">
<i class="fa fa-magic"></i>
{{ 'header.compose_page'|trans({}, 'SonataPageBundle')}}
</a>
</li>
{% endif %}
{% if page is defined and not page.enabled %}
<li><span style="padding-left: 20px; background: red;"><strong><em>{{ 'header.page_is_disabled'|trans([], 'SonataPageBundle') }}</em></strong></span></li>
{% endif %}
{% if sonata_page.isInlineEditionOn and page is defined %}
<li>
<form class="form-inline" style="margin: 0px">
<label class="checkbox inline" for="page-action-enabled-edit"><input type="checkbox" id="page-action-enabled-edit">{{ 'header.show_zone'|trans({}, 'SonataPageBundle') }}</label>
<input type="submit" class="btn" value="{{ 'btn_save_position'|trans({}, 'SonataPageBundle') }}" id="page-action-save-position">
</form>
</li>
{% endif %}
{% endif %}
{% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %}
<li><a href="{{ url('homepage', {'_switch_user': '_exit'}) }}">{{ "header.switch_user_exit"|trans({}, 'SonataPageBundle')}}</a></li>
{% endif %}
</ul>
</div>
</header>
{% endif %}
{% endblock %}
{% endblock %}
{% block sonata_page_container %}
{% block page_container %}{% endblock %} {# Deprecated block #}
{% endblock %}
{% block sonata_page_asset_footer %}
{% block page_asset_footer %} {# Deprecated block #}
{% if page is defined %}
{% if page.javascript is not empty %}
<script>
{{ page.javascript|raw }}
</script>
{% endif %}
{% if page.stylesheet is not empty %}
<style>
{{ page.stylesheet|raw }}
</style>
{% endif %}
{% endif %}
{#
These includes can be done only at this point as all blocks are loaded,
Limition : this does not work if a global page is loaded from an ESI tag inside a container block
#}
{{ sonata_block_include_stylesheets('screen', app.request.basePath) }}
{{ sonata_block_include_javascripts('screen', app.request.basePath) }}
{% endblock %}
{% endblock %}
<!-- monitoring:3e9fda56df2cdd3b039f189693ab7844fbb2d4f6 -->
</body>
</html>
và mẫu của chúng tôi
{#/templates/Layouts/test-layout.html.twig#}
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{% extends '/Layouts/base_layout.html.twig' %}
{% block sonata_page_container %}
<div class="container">
<div class="content">
<div class="row page-header">
{{ sonata_page_render_container('header', 'global') }}
</div>
<!----> {% block sonata_page_breadcrumb %}
<div class="row">
{{ sonata_block_render_event('breadcrumb', { 'context': 'page', 'current_uri': app.request.requestUri }) }}
</div>
{% endblock %}
{% if page is defined %}
<div class="row">
{% if page.name != 'global'%}
{{ sonata_page_render_container('content_top', 'global') }}
{% endif %}
{{ sonata_page_render_container('content_top', page) }}
</div>
{% endif %}
<div class="row">
{% block page_content %}
{% if content is defined %}
{{ content|raw }}
{% else %}
{% set content = block('content') is defined ? block('content') : '' %}
{% if content|length > 0 %}
{{ content|raw }}
{% elseif page is defined %}
{{ sonata_page_render_container('content', page) }}
{% endif %}
{% endif %}
{% endblock %}
</div>
{% if page is defined %}
<div class="row">
{{ sonata_page_render_container('content_bottom', page) }}
{% if page.name != 'global'%}
{{ sonata_page_render_container('content_bottom', 'global') }}
{% endif %}
</div>
{% endif %}
</div>
<footer class="row">
{{ sonata_page_render_container('footer', 'global') }}
</footer>
</div>
{% endblock %}
Sau đó chúng tôi tham chiếu nó trong tệp cấu hình gói
sonata_page:
test:
path: 'Layouts/test-layout.html.twig'
name: 'test'
containers:
header:
name: Header
content_top:
name: Top content
content:
name: Main content
content_bottom:
name: Bottom content
footer:
name: Footer
matrix:
layout: |
HHHHHHHH
HHHHHHHH
TTTTTTTT
TTTTTTTT
CCCCCCCC
CCCCCCCC
BBBBBBBB
BBBBBBBB
FFFFFFFF
FFFFFFFF
mapping:
H: header
T: content_top
C: content
B: content_bottom
F: footer
Chỉnh sửa phía front-end của chúng tôi gần như hoạt động.
Chúng tôi có một thanh điều hướng trên trang của mình. Khi kích hoạt chỉnh sửa khu vực, mọi thứ hiển thị chính xác. Nếu chúng tôi nhấp đúp vào một khu vực khối, một cửa sổ xuất hiện với chỉnh sửa khối trong admin. Và khi chúng tôi di chuyển các khối từ một khu vực sang khu vực khác, nó di chuyển chúng tốt. Nhưng nếu chúng tôi nhìn vào trang, không có gì được lưu.
Chúng tôi sẽ phải tạo một phương thức riêng biệt từ phía mình để lưu cài đặt.

Chúng tôi sau đó sẽ tạo một bộ điều khiển trong gói mở rộng của mình để quản lý cập nhật dữ liệu.
Chúng tôi tạo bộ điều khiển UpdatePositionController của mình với phương thức sonata_page_update_position() của mình
<?php
namespace App\Application\Sonata\PageBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Sonata\MediaBundle\Entity\MediaManager;
use Sonata\MediaBundle\Provider\FileProvider;
use Symfony\Component\HttpKernel\KernelInterface;
use Psr\Container\ContainerInterface;
class UpdatePositionController extends AbstractController
{
/**
* @Route("/sonata-page-update-position", methods={"GET","POST"}, name="sonata_page_update_position")
*
*
* @return array
*/
public function sonata_page_update_position(Request $request)
{
$keyword=$request->request->all();
if(!empty($keyword['disposition']))
{
foreach($keyword['disposition'] as $i=>$v)
{
$id=$keyword['disposition'][$i]['id'];
$position=$keyword['disposition'][$i]['position'];
$parent_id=$keyword['disposition'][$i]['parent_id'];
$page_id=$keyword['disposition'][$i]['page_id'];
$connection = $this->getDoctrine()->getConnection();
$statement = $connection->prepare("UPDATE `page__block` SET `parent_id` = :parent_id, `position` = :position, `page_id`=:page_id WHERE `page__block`.`id` = :id ");
$statement->bindValue('id', $id);
$statement->bindValue('page_id', $page_id);
$statement->bindValue('position', $page_id);
$statement->bindValue('parent_id', $parent_id);
$statement->execute();
}
}
return new JsonResponse( (object) [
'result' => 'ok'
]);
}
}
?>
Sau đó chúng tôi phải tham chiếu nó trong dịch vụ của mình:
services:
App\Application\Sonata\PageBundle\Controller\:
resource: '../src/Application/Sonata/PageBundle/Controller'
tags: ['controller.service_arguments']
Chúng tôi sau đó phải tham chiếu nó trong các chú thích để tuyến đường được tự động quản lý qua Symfony:
#config/routes/annotations.yaml
controllers:
resource: ../../src/Controller/
type: annotation
controllers_sonata_pages:
resource: ../../src/Application/Sonata/PageBundle/Controller/
type: annotation
Và cuối cùng, chúng tôi phải sửa đổi liên kết để cập nhật thứ tự của các khối trong mẫu của mình, đó phải là: block_save_position: '{{ path('sonata_page_update_position', {}) }}',
{#
This file is part of the Sonata package.
(c) Thomas Rabaix <thomas.rabaix@sonata-project.org>
For the full copyright and license information, please view the LICENSE
file that was distributed with this source code.
#}
{%- block sonata_page_html_tag -%}
<!DOCTYPE html>
<html {{ sonata_seo_html_attributes() }}>
{% endblock %}
{% block sonata_page_head %}
<head {{ sonata_seo_head_attributes() }}>
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
{{ sonata_seo_title() }}
{{ sonata_seo_metadatas() }}
{% block sonata_page_stylesheets %}
{% block page_stylesheets %} {# Deprecated block #}
{% for stylesheet in sonata_page.assets.stylesheets %}
<link rel="stylesheet" href="{{ asset(stylesheet) }}" media="all">
{% endfor %}
{% endblock %}
{% endblock %}
{% block sonata_page_javascripts %}
{% block page_javascripts %} {# Deprecated block #}
<!-- Le HTML5 shim, for IE6-8 support of HTML elements -->
<!--[if lt IE 9]>
<script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
{% for js in sonata_page.assets.javascripts %}
<script src="{{ asset(js) }}"></script>
{% endfor %}
<script src="/bundles/pixsortablebehavior/js/jquery-ui.min.js"></script>
{% endblock %}
{% endblock %}
</head>
{% endblock %}
{% block sonata_page_body_tag %}
<body class="sonata-bc">
{% endblock %}
{% block sonata_page_top_bar %}
{% block page_top_bar %} {# Deprecated block #}
{% if sonata_page.isEditor or ( app.user and is_granted('ROLE_PREVIOUS_ADMIN') ) %}
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
<!-- CMS specific variables -->
<script>
jQuery(document).ready(function() {
Sonata.Page.init({
url: {
block_save_position: '{{ path('sonata_page_update_position', {}) }}',
block_edit: '{{ sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': page.id , 'childId': 'BLOCK_ID'}) }}'
}
});
});
</script>
{% endif %}
<header class="sonata-bc sonata-page-top-bar navbar navbar-inverse navbar-fixed-top" role="banner"
{% if sonata_page.isEditor and sonata_page.isInlineEditionOn %}
data-page-editor='{{ {
url: {
block_save_position: path('sonata_page_update_position', {}),
block_edit: sonata_admin.url('sonata.page.admin.page|sonata.page.admin.block', 'edit', {'id': page.id , 'childId': 'BLOCK_ID'})
}
}|json_encode()|raw }}'
{% endif %}>
<div class="container">
<ul class="nav navbar-nav">
{% if app.user and is_granted('ROLE_SONATA_ADMIN') %}
<li><a href="{{ path('sonata_admin_dashboard') }}">{{ "header.sonata_admin_dashboard"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
{% if sonata_page.isEditor %}
{% set sites = sonata_page.siteavailables %}
{% if sites|length > 1 and site is defined %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ site.name }} <span class="caret"></span></a>
<ul class="dropdown-menu">
{% for site in sites %}
<li><a href="{{ site.url }}">{{ site.name }}</a></li>
{% endfor %}
</ul>
</li>
{% endif %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Page <span class="caret"></span></a>
<ul class="dropdown-menu">
{% if page is defined %}
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page', 'edit', page) }}" target="_new">{{ "header.edit_page"|trans({}, 'SonataPageBundle') }}</a></li>
<li><a href="{{ sonata_admin.objectUrl('sonata.page.admin.page|sonata.page.admin.snapshot', 'list', page) }}" target="_new">{{ "header.create_snapshot"|trans({}, 'SonataPageBundle') }}</a></li>
<li class="divider"></li>
{% endif %}
<li><a href="{{ sonata_admin.url('sonata.page.admin.page', 'list') }}" target="_new">{{ "header.view_all_pages"|trans({}, 'SonataPageBundle') }}</a></li>
{% if error_codes is defined and error_codes|length %}
<li class="divider"></li>
<li><a href="{{ path('sonata_page_exceptions_list') }}" target="_new">{{ "header.view_all_exceptions"|trans({}, 'SonataPageBundle') }}</a></li>
{% endif %}
</ul>
</li>
{% if page is defined %}
<li>
<a href="{{ sonata_admin.url('sonata.page.admin.page', 'compose', {'id': page.id}) }}">
<i class="fa fa-magic"></i>
{{ 'header.compose_page'|trans({}, 'SonataPageBundle')}}
</a>
</li>
{% endif %}
{% if page is defined and not page.enabled %}
<li><span style="padding-left: 20px; background: red;"><strong><em>{{ 'header.page_is_disabled'|trans([], 'SonataPageBundle') }}</em></strong></span></li>
{% endif %}
{% if sonata_page.isInlineEditionOn and page is defined %}
<li>
<form class="form-inline" style="margin: 0px">
<label class="checkbox inline" for="page-action-enabled-edit"><input type="checkbox" id="page-action-enabled-edit">{{ 'header.show_zone'|trans({}, 'SonataPageBundle') }}</label>
<input type="submit" class="btn" value="{{ 'btn_save_position'|trans({}, 'SonataPageBundle') }}" id="page-action-save-position">
</form>
</li>
{% endif %}
{% endif %}
{% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %}
<li><a href="{{ url('homepage', {'_switch_user': '_exit'}) }}">{{ "header.switch_user_exit"|trans({}, 'SonataPageBundle')}}</a></li>
{% endif %}
</ul>
</div>
</header>
{% endif %}
{% endblock %}
{% endblock %}
{% block sonata_page_container %}
{% block page_container %}{% endblock %} {# Deprecated block #}
{% endblock %}
{% block sonata_page_asset_footer %}
{% block page_asset_footer %} {# Deprecated block #}
{% if page is defined %}
{% if page.javascript is not empty %}
<script>
{{ page.javascript|raw }}
</script>
{% endif %}
{% if page.stylesheet is not empty %}
<style>
{{ page.stylesheet|raw }}
</style>
{% endif %}
{% endif %}
{#
These includes can be done only at this point as all blocks are loaded,
Limition : this does not work if a global page is loaded from an ESI tag inside a container block
#}
{{ sonata_block_include_stylesheets('screen', app.request.basePath) }}
{{ sonata_block_include_javascripts('screen', app.request.basePath) }}
{% endblock %}
{% endblock %}
<!-- monitoring:3e9fda56df2cdd3b039f189693ab7844fbb2d4f6 -->
</body>
</html>
Đây là kết quả: