Sonata is a suite of bundles for Symfony 4. One of which allows for content management in a WordPress-like manner.
There is little documentation available on the internet, and for good reason, the installation is bugged, and the few people who have dealt with the bug tracker on GitHub, have been bluntly told to RTFM.

Staying put with such failure is not really my style.
Here's how to install and configure Sonata-Page-Bundle, with the official installation, and the workarounds for these F###ing bugs.

Step 1, we retrieve our Symfony 4 skeleton which includes our functional base of the framework complemented by Sonata + FosUser + Media.
Already we save a good day ^^

In short, you should follow the documentation: https://sonata-project.org/bundles/page/3-x/doc/reference/installation.html

Install all Bundles, and meticulously follow the documentation.

The first difficulty is the installation via composer. I could not install everything for php 7.1. Nor for php 7.2 for that matter. There is always a conflicting package.

The trick in composer is to step out of stable with the minimum-stability instruction in your composer.json and there, after 3 or 4 hours of testing versions, you can install everything.

Here is my composer.json

{
    "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
}

Once the packages are installed, and configured, don't forget to install CKEditor, and the assets. And incidentally, your super admin user.

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

It remains to create the first site:

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

The first site is now operational. Well almost.

In the page interface, you need to create our first page

image

The default configuration contains nothing. It is then necessary to organize the content of this page through an editor, which allows organizing our page in the form of blocks, assigned to areas of our templates.

In our bundle configuration, we have declared a default template. And it's this configuration, that allows us to model the block management interface

   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
image-2

In our example above, we inserted 2 blocks into our zone.
The first is a simple text, the second is a menu (a classic footer menu).
To insert a block we position ourselves on a zone to the left, and click on the + button to the right to select the type of block after having selected it from the list.

All page management is done in this way.
Which means that we will have to create a series of blocks according to the needs of our application.
And to create a block, it's super simple. We will take the example of our menu block.

The menus are managed via the Sonata-Menu-Bundle package which is compatible with Sonata-Page-Bundle. But it is not provided with a default block. It's stupid because a menu is a bit of a basic thing.

So we will create our block in /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',
        ]);
    }
}

We add our templates in /Block/Menu/menu.html.twig and the menu macro file in /Block/Menu/menu_tree_macro.html.twig.
It's simply the default files of the bundle surrounded by a div that contains block information for front-end editing.

{#/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 %}

We reference our block in our services

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 }   

And we then have our new block that is available in our block list. You can basically do whatever you want in a block, in the manner of a controller.

Sélection_296

So we can create our footer menu

image-3

And use it in our block

image-4

Here is the rendering of our page

image-5

Actually, no. That's the rendering with a corrected template. Otherwise, the rendering would look like this:

Sélection_302

So we are going to have to duplicate the base template into our project and fix what's wrong.

We create our base_layout and our test template test-layout in the Layouts/ directory

{#/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>

and our template

{#/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 %}

We then reference it in the bundle configuration file

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
                    

Our front-end editing is almost functional.
We do have a navigation bar on our page. When editing zones is activated, everything displays correctly. If we double-click a block area, a window appears with the block editing in the admin. And when we move blocks from one area to another, it moves them well. But if we look at the page, nothing is saved.
We will have to create a dedicated method on our side to save the settings.

Sélection_303

We will then create a controller in our extended bundle to manage the data update.

We create our UpdatePositionController with our method sonata_page_update_position()

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

    }
}
?>


We must then reference it in our services:

services:
    App\Application\Sonata\PageBundle\Controller\:
        resource: '../src/Application/Sonata/PageBundle/Controller'
        tags: ['controller.service_arguments']

We must then reference it in the annotations so that the route is automatically managed via Symfony:

#config/routes/annotations.yaml
controllers:
    resource: ../../src/Controller/
    type: annotation

controllers_sonata_pages:
    resource: ../../src/Application/Sonata/PageBundle/Controller/
    type: annotation  

And finally, we must modify the link to update the order of blocks in our template which should be: 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>

Here is the result: