Sonata admin

How to make a full-featured backend without any line on client-side(html/css/js)





Author: Andrew Kovalyov
andrew.kovalyov@knplabs.com


What is sonata admin?



"A set of several rich open source bundles
based on Symfony2"


http://sonata-project.org/

Why sonata?

  • You can create a rich, consistent and  configurable interface for  models  data manipulation
  • You have a high-featured backoffice out of the box
  • Sonata is built using the best practices of Symfony2 and on base of Symfony2
  • You still have high flexibility for customization
  • You do not need to reinvent a wheel each time when writing backoffice

Sonata Main bundles

Core bundles
  • SonataCoreBundle(Classes which are reused in project itself, like variuous form types, doctrine basic entity manager, serialization stuff, various helpers)
  • SonataAdminBundle(Admin generator itself)
Persistence layer
  • SonataDoctrineORMAdminBundle
  • SonataDoctrineMongoDBAdminBundle(early stage)
  • SonataDoctrinePhpcrAdminBundle(early stage)
  • SonataPropelAdminBundle(early stage)

sonata featured bundles

  • SonataPageBundle(CMS)

  • SonataMediaBundle(media library based on a dedicated abstract provider which handles different type of media: files, videos or images)

  • SonataNewsBundle(blog platform based on Doctrine2 and Symfony2)

  • SonataUserBundle(FOS/User integration)

  • SonataBlockBundle(block element rendering and caching using different cache storages (memcached/apc/esi/ssi/mongo))

Let's start

{
"require": {
"sonata-project/admin-bundle": "~2.2@dev",
"sonata-project/block-bundle": "~2.2@dev",
"sonata-project/doctrine-orm-admin-bundle": "~2.2@dev",
"sonata-project/core-bundle": "~2.2@dev"
}
}
 class AppKernel extends Kernel
{
public function registerBundles()
{
$bundles = array(
//...
new Sonata\CoreBundle\SonataCoreBundle(),
new Sonata\BlockBundle\SonataBlockBundle(),
new Sonata\jQueryBundle\SonatajQueryBundle(),
new Knp\Bundle\MenuBundle\KnpMenuBundle(),
new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(),
new Sonata\AdminBundle\SonataAdminBundle(),
//...
}
}


Configuration

#config.yml
sonata_block:
default_contexts: [cms]
blocks:
sonata.admin.block.admin_list:
contexts: [admin]

#routing.yml
admin:
resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
prefix: /admin

_sonata_admin:
resource: .
type: sonata_admin
prefix: /admin




That's it

We have almost nothing


but we added almost nothing

Let's create models

class Country
{
/**
* @ORM\Column(type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

/**
* @ORM\Column(type="string", length=2)
*/
private $isoCode;

/**
* @ORM\Column(type="string", length=25)
*/
private $name;

}

Let's create models(part 2)

class Index
{
/**
* @var string name
* @ORM\Column(type="string")
*/
private $name;

/**
* @var Country
* @ORM\ManyToOne(targetEntity="App\Entity\Country")
*/
private $country;

}

let's create models(part 3)

class Value
{
const VOLATILITY_LOW = 1;
/**
* @ORM\Column(type="datetime")
*/
private $createdAt;
/**
* @ORM\Column(type="integer")
*/
private $volatility = self::VOLATILITY_LOW;
/**
* @ORM\Column(type="float")
*/
private $fixed;
/**
* @ORM\Column(type="float")
*/
private $prognosed;
/**
* @ORM\Column(type="float")
*/
private $actual;
}


OK, we have models

We even have fake data.



How to work with it?

admin class

  • Meta description of CRUD operations
  •  No code generation
  •  Based on Symfony services + Sonata Admin services and itself is a service.

the simplest admin ever

namespace App\Admin;

use Sonata\AdminBundle\Admin\Admin;

class CountryAdmin extends Admin
{

}

#src/App/Resources/config/services.yml
parameters:
app.service.admin.country.class: App\Admin\CountryAdmin
services:
app.service.admin.country:
class: %app.service.admin.country.class%
tags:
- { name: sonata.admin, manager_type: orm, group: "Content", label: "Countries" }
arguments:
- ~
- App\Entity\Country
- ~

Results

Menu


List

form configuration


<?php
namespace App\Admin;

use Sonata\AdminBundle\Form\FormMapper;

class CountryAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('translations', 'a2lix_translations')
->add('isoCode');
}
}

result


under the hood


admin class deps overview

  • ConfigurationPool: configuration pool where all Admin class instances are stored
  • ModelManager: service which handles specific code relating to your persistence layer (e.g. Doctrine ORM)
  • FormContractor: builds the forms for the edit/create views using the Symfony FormBuilder
  • ShowBuilder: builds the show fields
  • ListBuilder: builds the list fields
  • DatagridBuilder: builds the filter fields
  • Request: the http request received
  • RouteBuilder: allows you to add routes for new actions and remove routes for default actions
  • RouterGenerator: generates the different urls
  • SecurityHandler: handles permissions for model instances and actions
  • Validator: handles model validation
  • Translator: generates translations
  • LabelTranslatorStrategy: a strategy to use when generating labels
  • MenuFactory: generates the side menu, depending on the current action

Admin class configuration overview

     app.service.admin.country: 
class: %app.service.admin.country.class%
tags:
- { name: sonata.admin, manager_type: orm|doctrine_mongodb|doctrine_phpcr, group: "Content", label: "Countries" }
arguments:
- ~ #code
- App\Entity\Index #model
- ~ #related CRUD controller
calls:
- [ setLabelTranslatorStrategy, ["@sonata.admin.label.strategy.underscore|noop|native|form_component|bc"]]
- [ setTranslationDomain, "some_domain"] - [ setRouteBuilder, [ @sonata.admin.route.path_info|default_generator|query_string ]]

list configuration

<?php
namespace App\Admin;

use Sonata\AdminBundle\Datagrid\ListMapper;

class CountryAdmin extends Admin{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('name')
->add('isoCode')
->add('_action', 'actions', [
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
]
]
);
}
}

result



additional form types example

 <?php

use Sonata\AdminBundle\Form\FormMapper;

class IndexAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('name')
->add(
'country',
'sonata_type_model',
[
'property' => 'name',
'required' => true
]
)
->add('enabled', null, ['required' => false]);
}

result


form types


  • Admin Type - embedding a form from another Admin class
  • Collection Type - one-to-many associations
  • Model Type - similar to Entity Type
  • Model Reference Type - references to a model, handles entity id
  • Immutable array type - form type per array element

Collection type(one to many)

sonata_type_collection

By reference = false

Sonata options:

  • edit: standard:inline
  • inline:table|list
  • sortable by field name if specified

result

groups and help

use Sonata\AdminBundle\Form\FormMapper;

class ValueAdmin extends Admin
{
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('Meta')
->add('date', 'datetime')
->add(
'volatility',
'choice',
[
'choices' => Value::getVolatilityValues()
]
)
->add('index')
->end()
->with('Data')
->add('prognosed')
->add('actual')
->end()
->setHelps(
[
'prognosed' => 'Fixed value will be calculated automatically based on diff between actual and prognosed values ',
]
);

}

result

quick models status change

use Sonata\AdminBundle\Datagrid\ListMapper;

class IndexAdmin extends Admin
{
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('id')
->add('name')
->add('enabled', null, ['editable' => true])
->add(
'_action',
'actions',
[
'actions' => [
'show' => [],
'edit' => [],
'delete' => [],
]
]
);
}
}


result



final result


customization example

what is not good yet

High cost of deep customization

Documentation is messy and not always clear 

Unclear strategy of releases

Rather difficult for newcomers

when to use


  • small projects? - overkill
  • medium/pet projects - rather useful
  • large, custom projects - might not be flexible enough

a bit of stats

          > 345000 installs on Packagist

          > 900 stargazers

          > 500 forks

         ~ 3000 commits

resources

Sonata:

https://github.com/sonata-project

http://sonata-project.org



Sample app

https://github.com/akovalyov/SonataSimpleIndexes



contacts



  • Accounts: @KNPLabs, @KNPLabsRu
  • Hashtag: #SymfonyPizza


http://knplabs.com


andrew.kovalyov@knplabs.com

Questions?