Sonata Admin Custom Page

Marko Kunic
3 min readSep 17, 2017

--

Sonata admin is one of the most used Admin bundles for Symfony Framework. If you do a google search for “sonata admin custom page” you will probably find this, while this is still a valid solution there is a better one. I needed to create a statistics page that would cover various Entities and I needed it in Sonata Admin. Here I will explain a better way for a custom page and how to use sonata blocks to make a universal block for statistics.

Note: This was originally posted on my blog here.

TL;DR: Code for this can be found here.

Custom Page

You can register Sonata Admin class without Entity (I was shocked too as this is not mentioned anywhere).

services.yml

#app/config/services.yml
bundle.admin.stats:
class: YourBundle\Admin\StatsAdmin
arguments: [~, ~, YourBundle:StatsCRUD]
tags:
- { name: sonata.admin, manager_type: orm, label: Stats }

As you can see here, we registered admin class without Entity with a custom controller.

StatsAdmin.php

<?phpnamespace YourBundle\Admin;use Sonata\AdminBundle\Admin\AbstractAdmin;
use Sonata\AdminBundle\Route\RouteCollection;
class StatsAdmin extends AbstractAdmin
{
protected $baseRoutePattern = 'stats';
protected $baseRouteName = 'stats';
protected function configureRoutes(RouteCollection $collection)
{
$collection->clearExcept(['list']);
}
}

You have to define ‘$baseroutePattern’ and ‘$baseRouteName’, and we will clear all other routes but the list.

StatsCRUDController.php

<?phpnamespace YourBundle\Controller;use Sonata\AdminBundle\Controller\CRUDController;class StatsCRUDController extends CRUDController
{
public function listAction()
{
return $this->render('YourBundle::stats.html.twig');
}
}

In a controller, it is up to you but as I said that I am making statistics page I will just render my template here. I will now go to creating custom Sonata Block and after that, I will show you template.

Sonata Block

Here we will create a universal block that will accept any Entity and Repository Method, also CSS class, font awesome icon, and a title.

StatsBlockService.php

<?phpnamespace YourBundle\Block\Service;use Doctrine\ORM\EntityManagerInterface;
use Sonata\AdminBundle\Form\FormMapper;
use Sonata\CoreBundle\Validator\ErrorElement;
use Sonata\BlockBundle\Block\Service\AbstractBlockService;
use Sonata\BlockBundle\Block\BlockContextInterface;
use Sonata\BlockBundle\Model\BlockInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Bundle\TwigBundle\TwigEngine;
class StatsBlockService extends AbstractBlockService
{
private $entityManager; public function __construct(
string $serviceId,
TwigEngine $templating,
EntityManagerInterface $entityManager
) {
$this->entityManager = $entityManager;
parent::__construct($serviceId, $templating);
}
/**
* {@inheritdoc}
*/

public function getName()
{
return 'Stats Block';
}
/**
* {@inheritdoc}
*/

public function configureSettings(OptionsResolver $resolver)
{
$resolver->setDefaults([
'entity' => 'Add Entity',
'repository_method' => 'findAll',
'title' => 'Insert block Title',
'css_class' => 'bg-blue',
'icon' => 'fa-users',
'template' => 'YourBundle:Block:block_stats.html.twig',
]);
}
/**
* {@inheritdoc}
*/

public function buildEditForm(
FormMapper $formMapper,
BlockInterface $block
) {
$formMapper->add(
'settings',
'sonata_type_immutable_array',
[
'keys' => [
['entity', 'text', ['required' => false]],
['repository_method', 'text'],
['title', 'text', ['required' => false]],
['css_class', 'text', ['required' => false]],
['icon', 'text', ['required' => false]],
]
]
);
}
/**
* {@inheritdoc}
*/

public function validateBlock(
ErrorElement $errorElement,
BlockInterface $block
) {
$errorElement
->with('settings[entity]')
->assertNotNull(array())
->assertNotBlank()
->end()
->with('settings[repository_method]')
->assertNotNull(array())
->assertNotBlank()
->end()
->with('settings[title]')
->assertNotNull(array())
->assertNotBlank()
->assertMaxLength(array('limit' => 50))
->end()
->with('settings[css_class]')
->assertNotNull(array())
->assertNotBlank()
->end()
->with('settings[icon]')
->assertNotNull(array())
->assertNotBlank()
->end();
}
/**
* {@inheritdoc}
*/

public function execute(
BlockContextInterface $blockContext,
Response $response = null
) {
$settings = $blockContext->getSettings();
$entity = $settings['entity'];
$method = $settings['repository_method'];
$rows = $this->entityManager
->getRepository($entity)
->$method();
return $this->templating
->renderResponse($blockContext->getTemplate(), [
'count' => $rows,
'block' => $blockContext->getBlock(),
'settings' => $settings,
],
$response);
}

So basically, you define entity and method and return that to your template. We register block as a service:

services.yml

#app/config/services.yml
admin.block.service.stats:
class: YourBundle\Block\Service\StatsBlockService
arguments:
- "admin.block.service.stats"
- "@templating"
- "@doctrine.orm.entity_manager"
public: true
tags:
- {name: "sonata.block"}

Also, inside of your sonata config for blocks, you need to add this new block:

config.yml

#app/config/config.yml
sonata_block:
default_contexts: [cms]
blocks:
# enable the SonataAdminBundle block
sonata.admin.block.admin_list:
contexts: [admin]
sonata.admin.block.search_result:
contexts: [admin]
admin.block.service.stats: ~

block_stats.html.twig

<div id="cms-block-{{ block.id }}" 
class="cms-block cms-block-element col-md-3">
<div class="small-box {{ settings.css_class }}">
<div class="inner">
<h3>{{ count }}</h3>
<p>{{ settings.title }}</p>
</div>
<div class="icon">
<i class="fa {{ settings.icon }}"></i>
</div>
</div>
</div>

And now we go back to twig template for stats admin:

stats.html.twig

{% extends 'SonataAdminBundle::standard_layout.html.twig' %}{% block content %}
<div class="row">
{{ sonata_block_render({ 'type': 'admin.block.service.stats' }, {
'entity' : 'AppBundle:User',
'repository_method' : 'findNumberofAllUsers',
'title' : 'Users',
'css_class' : 'bg-gray-active',
'icon' : 'fa-users'
}) }}
{{ sonata_block_render({ 'type': 'admin.block.service.stats' }, {
'entity' : 'AppBundle:Delivery',
'repository_method' : 'findAllDeliversInProgress',
'title' : 'Deliveries in Progress',
'css_class' : 'bg-yellow',
'icon' : 'fa-truck'
}) }}
{{ sonata_block_render({ 'type': 'admin.block.service.stats' }, {
'entity' : 'AppBundle:Delivery',
'repository_method' : 'findAllFailedDelivers',
'title' : 'Failed Deliveries',
'css_class' : 'bg-red',
'icon' : 'fa-truck'
}) }}
</div>
{% endblock %}

And that is it, you create a custom page that is more sonata way and you get a statistics page. I hope this helps someone as it helped me.

--

--