- https://github.com/sonata-project/SonataAdminBundle
- http://sonata-project.org/bundles/admin (official doc)
- http://sonata-project.org/bundles/doctrine-orm-admin/master/doc/index.html (other official doc)
- SonataMediaBundle : a media manager bundle
- SonataNewsBundle : a news/blog bundle
- SonataPageBundle : a page (CMS like) bundle
- SonataUserBundle : integration of FOSUserBundle and SonataAdminBundle
Prérequis
Si une gestion des traductions est nécessaire, et que vous voulez utiliser la traduction par défaut du bundle, vérifiez que la config suivante est activée dans app/config/config.yml :
framework:
translator: ~
Installation
Télécharger les dépendances
[SonataAdminBundle]
git=http://github.com/sonata-project/SonataAdminBundle.git
target=/bundles/Sonata/AdminBundle
version=origin/2.0
[SonataBlockBundle]
git=http://github.com/sonata-project/SonataBlockBundle.git
target=/bundles/Sonata/BlockBundle
[SonataCacheBundle]
git=http://github.com/sonata-project/SonataCacheBundle.git
target=/bundles/Sonata/CacheBundle
[SonatajQueryBundle]
git=http://github.com/sonata-project/SonatajQueryBundle.git
target=/bundles/Sonata/jQueryBundle
[KnpMenuBundle]git=http://github.com/KnpLabs/KnpMenuBundle.git target=/bundles/Knp/Bundle/MenuBundle[KnpMenu]git=http://github.com/KnpLabs/KnpMenu.git target=/knp/menu
[Exporter]
git=http://github.com/sonata-project/exporter.git
target=/exporter
[SonataDoctrineORMAdminBundle]
git=http://github.com/sonata-project/SonataDoctrineORMAdminBundle.git
target=/bundles/Sonata/DoctrineORMADminBundle
Si vous rencontrez le problème suivant lors de l’exécution de php bin/vendors install, suivez ces lignes :
[SymfonyComponentConfigDefinitionExceptionInvalidConfigurationException]
The child node “default_contexts” at path “sonata_block” must be configured.
Modifier app/config/config.yml
et ajouter les lignes suivantes (en théorie, seules les 2 premières lignes sont utiles, mais sans confirmation, il vaut mieux faire ce que la doc officielle conseille) :
sonata_block:
default_contexts: [cms]
blocks:
sonata.admin.block.admin_list:
contexts: [admin]
sonata.block.service.text:
sonata.block.service.action:
sonata.block.service.rss:
Relancer alors php bin/vendor install
.
Il est aussi possible de rencontrer des problèmes suite au changement de version Symfony2 vers Symfony2.1. Voir ce ticket : http://sonata-project.org/blog/2012/3/15/moving-to-symfony2-1
Enregistrer les namespaces
Dans le fichier autoloader, ajouter les lignes suivantes au registerNamespaces :
$loader->registerNamespaces(array(
// ...
'Sonata' => __DIR__.'/../vendor/bundles',
'Exporter' => __DIR__.'/../vendor/exporter/lib',
'KnpBundle'=>__DIR__.'/../vendor/bundles','KnpMenu'=>__DIR__.'/../vendor/knp/menu/src',
// ...
));
Enregistrer le bundle
Dans appKernel.php
:
public function registerBundles()
{
$bundles = array(
// ...
new SonataAdminBundleSonataAdminBundle(),
new SonataBlockBundleSonataBlockBundle(),
new SonataCacheBundleSonataCacheBundle(),
new SonatajQueryBundleSonatajQueryBundle(),
newKnpBundleMenuBundleKnpMenuBundle(),
new SonataDoctrineORMAdminBundleSonataDoctrineORMAdminBundle(),
);
// ...
)
Maintenant, il faut installer les assets des bundles et supprimer le cache :
php app/console assets:install web
php app/console cache:clear
Configurer les routes
Les routes sont toutes inclues dans un fichier de routing qu’il faut référencer dans le routing principal app/config/routing.yml :
admin:
resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml'
prefix: /admin
_sonata_admin:
resource: .
type: sonata_admin
prefix: /admin
Dès l’instant que les routes sont en place, l’admin est accessible via http://localhost/admin/dashboard
Configurer le service de persistance (ORM, ODM…)
SonataAdminBundle n’impose pas le service de persistance, qui permet d’utiliser et contrôler les modèles, mais au cas où vous en utilisiez vous pouvez installer un des bundles officiels comme SonataDoctrineORMAdminBundle, SonataDoctrineMongoDBAdminBundle ou SonataDoctrinePhpcrAdminBundle. Dans notre exemple actuel, nous avons installé SonataDoctrineORMAdminBundle.
Utilisation
Créer les entities
Si vous n’en avez pas, créez tout d’abord le bundle et les entités, puis mappez les (astuce : créez vos entités et exécutez php app/console doctrine:generate:entities AcseoTestBundle pour créer les getters et setters, puis php app/console doctrine:schema:update –force pour créer les tables).
Définir le routing
Oh joie, les routes sont automatiquement créées. Nous avons donc list, create, batch, update, edit et delete par défaut !
Créer le contrôleur CRUD
Cette étape n’est pas nécessaire. Si vous décidez de le créer, au cas où vous auriez d’autres actions à créer dedans, il doit étendre de CRUDController pour utiliser les méthodes de CRUD de Sonata. Si aucun contrôleur n’est créé, Sonata utilisera par défaut le sien. On doit donc avoir un contrôleur comme suit, si vous désirez créer un contrôleur :
namespace AcseoTestBundleController;
use SonataAdminBundleControllerCRUDController as Controller;
class CommentAdminController extends Controller
{
}
Créer la classe d’admin
Ressource http://sonata-project.org/bundles/doctrine-orm-admin/master/doc/tutorial/creating_your_first_admin_class/installation.html Cette classe contient toutes les informations requises pour générer l’interface CRUD. Par convention, nous créons un dossier Admin dans chaque bundle, dans lequel nous mettrons les classes EntityAdmin.php. En voici un rapide exemple, pour une entité ayant 2 champs : title et content.
namespace TutorialBlogBundleAdmin;
use SonataAdminBundleAdminAdmin;
use SonataAdminBundleFormFormMapper;
use SonataAdminBundleDatagridDatagridMapper;
use SonataAdminBundleDatagridListMapper;
use SonataAdminBundleShowShowMapper;
use KnpMenuItemInterface as MenuItemInterface;
use AcseoTestBundleEntityPage;
class PageAdmin extends Admin
{
/**
* @param SonataAdminBundleShowShowMapper $showMapper
*
* @return void
*/
protected function configureShowField(ShowMapper $showMapper)
{
$showMapper
->add('title')
->add('content')
;
}
/**
* @param SonataAdminBundleFormFormMapper $formMapper
*
* @return void
*/
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->with('General')
->add('title')
->add('content')
->end()
;
}
/**
* @param SonataAdminBundleDatagridListMapper $listMapper
*
* @return void
*/
protected function configureListFields(ListMapper $listMapper)
{
$listMapper
->addIdentifier('title')
->add('content')
->add('_action', 'actions', array(
'actions' => array(
'view' => array(),
'edit' => array(),
'delete' => array(),
)
))
;
}
/**
* @param SonataAdminBundleDatagridDatagridMapper $datagridMapper
*
* @return void
*/
protected function configureDatagridFilters(DatagridMapper $datagridMapper)
{
$datagridMapper
->add('title')
;
}
}
Puis il faut enregistrer le service dans le fichier services.xml du bundle : Notons que group sera le nom visible de notre module dans l’interface d’admin, et label sera le nom visible de l’entité, il est donc recommandé de les écrire avec majuscules, espaces et tout caractère nécessaire à la bonne compréhension par tous.
<service id="acseo.test.admin.page" class="AcseoTestBundleAdminPageAdmin">
<tag name="sonata.admin" manager_type="orm" group="Ceci est un test ACSEO" label="Page"/>
Nous avons donc maintenant un backoffice listant chacun de nos bundles, contenant chacun leurs entités, administrables via des CRUD !
Customisation de l’interface
On peut modifier l’affichage de notre interface d’admin via le fichier app/config/config.yml. On peut aussi customiser les champs.
sonata_admin:
title: Administration de notre application
title_logo: /bundles/sonataadmin/logo_title.png
templates:
# default global templates
layout: SonataAdminBundle::standard_layout.html.twig
ajax: SonataAdminBundle::ajax_layout.html.twig
dashboard: SonataAdminBundle:Core:dashboard.html.twig
# default actions templates, should extend a global templates
list: SonataAdminBundle:CRUD:list.html.twig
show: SonataAdminBundle:CRUD:show.html.twig
edit: SonataAdminBundle:CRUD:edit.html.twig
dashboard:
blocks:
# display a dashboard block
- { position: left, type: sonata.admin.block.admin_list }
# Customize this part to add new block configuration
- { position: right, type: sonata.block.service.text, settings: { content: "
Welcome
totheSonataAdmin
This
isasonata.block.service.textfromtheBlockBundle,youcancreateandaddnewblockintheseareabyconfiguringthesonata_adminsection.<br/>Forinstance,hereaRSSfeedparser(sonata.block.service.rss):"} }
- { position: right, type: sonata.block.service.rss, settings: { title: Sonata Project's Feeds, url: http://sonata-project.org/blog/archive.rss }}
groups:
default: ~
# on peut aussi afficher les groupes que l'on veut via BlockBundle
# on peut aussi customiser les champs. Les champs par défaut.
sonata_doctrine_orm_admin:
templates:
types:
list:
array: SonataAdminBundle:CRUD:list_array.html.twig
boolean: SonataAdminBundle:CRUD:list_boolean.html.twig
date: SonataAdminBundle:CRUD:list_date.html.twig
time: SonataAdminBundle:CRUD:list_time.html.twig
datetime: SonataAdminBundle:CRUD:list_datetime.html.twig
text: SonataAdminBundle:CRUD:base_list_field.html.twig
trans: SonataAdminBundle:CRUD:list_trans.html.twig
string: SonataAdminBundle:CRUD:base_list_field.html.twig
smallint: SonataAdminBundle:CRUD:base_list_field.html.twig
bigint: SonataAdminBundle:CRUD:base_list_field.html.twig
integer: SonataAdminBundle:CRUD:base_list_field.html.twig
decimal: SonataAdminBundle:CRUD:base_list_field.html.twig
identifier: SonataAdminBundle:CRUD:base_list_field.html.twig
show:
array: SonataAdminBundle:CRUD:show_array.html.twig
boolean: SonataAdminBundle:CRUD:show_boolean.html.twig
date: SonataAdminBundle:CRUD:show_date.html.twig
time: SonataAdminBundle:CRUD:show_time.html.twig
datetime: SonataAdminBundle:CRUD:show_datetime.html.twig
text: SonataAdminBundle:CRUD:base_show_field.html.twig
trans: SonataAdminBundle:CRUD:show_trans.html.twig
string: SonataAdminBundle:CRUD:base_show_field.html.twig
smallint: SonataAdminBundle:CRUD:base_show_field.html.twig
bigint: SonataAdminBundle:CRUD:base_show_field.html.twig
integer: SonataAdminBundle:CRUD:base_show_field.html.twig
decimal: SonataAdminBundle:CRUD:base_show_field.html.twig
Sécurité
La dernière chose importante est la sécurité. Par défaut il n’y a aucun système de gestion des utilisateurs, mais il existe SonataUserBundle qui intègre FOSUserBundle.
Configuration avancée
Chaque classe admin d’entité possède 4 fonctions :
- configureShowField
- configureFormFields
- configureListFields
- configureDatagridFilters
Types de champs disponibles :
- boolean
- datetime
- decimal
- identifier
- integer
- many_to_one
- string
- text
- date
- time
Si aucun type n’est défini, la classe Admin utilisera le type défini dans la définition du mapping Doctrine.
Configuration des actions dans configureListFields
Par défaut les actions edit et delete sont activées, mais on peut ajouter nos propres actions (dans notre exemple on rajoute delete). Le fichier de template appelé par défaut est SonataAdminBundle:CRUD:list_action[ACTION_NAME].html.twig
Configuration des filtres dans configureDatagridFilters
Il est possible de modifier le label, mais aussi d’ajouter un filtre en callback. Pour créer un filtre en callback, il est nécessaire d’implémenter 2 méthodes : une pour définir le type de champ, et une autre pour définir comment la valeur du champ est utilisée. Cette dernière doit retourner si le filtre est appliqué ou non au queryBuilder. Dans notre exemple, getWithOpenCommentField et getWithOpenCommentFilter implémentent cette fonctionnalité.
protected function configureDatagridFilters(DatagridMapper $datagrid)
{
$datagrid
->add('tags', null, array('label' => 'les tags'), null, array('expanded' => true, 'multiple' => true) # ajout d'un label
# ajout d'un callback
->add('with_open_comments', 'doctrine_orm_callback', array(
// 'callback' => array($this, 'getWithOpenCommentFilter'),
'callback' => function($queryBuilder, $alias, $field, $value) {
if (!$value) return;
$queryBuilder->leftJoin(sprintf('%s.comments', $alias), 'c');
$queryBuilder->andWhere('c.status = :status');
$queryBuilder->setParameter('status', Comment::STATUS_MODERATE);
return true;
}, 'field_type' => 'checkbox'
))
;
}
public function getWithOpenCommentFilter($queryBuilder, $alias, $field, $value)
{
if (!$value) return;
$queryBuilder->leftJoin(sprintf('%s.comments', $alias), 'c');
$queryBuilder->andWhere('c.status = :status');
$queryBuilder->setParameter('status', Comment::STATUS_MODERATE);
return true;
}
Configuration des champs des formulaires dans configureFormFields
Les champs sont requis par défaut.
protected function configureFormFields(FormMapper $formMapper)
{
$formMapper
->add('author', 'sonata_type_model', array(), array('edit' => 'list'))
->add('enabled')
->add('title')
->add('abtract', null, array('required' => false))
->add('content')
->add('binaryContent', 'file', array('required' => false)); // fichier binaire
// you can define help messages like this
->setHelps(array(
'title' => $this->trans('help_post_title')
));
}
public function validate(ErrorElement $errorElement, $object)
{
// conditional validation, see the related section for more information
if ($object->getEnabled()) {
// abstract cannot be empty when the post is enabled
$errorElement
->with('abtract')
->assertNotBlank()
->assertNotNull()
->end()
;
}
}
Dans le cas où il y a une relation many-to-one, nous avons 3 options à disposition :
- standard : valeur par défaut, listing des user dans un widget de sélection
- list : la liste de user est dans un modèle où on peut chercher et sélectionner un utilisateur
- inline : embarqué dans le formulaire user dans le formulaire post. Pratique pour du one-to-one ou si vous voulez donner le droit à l’admin de modifier les informations sur les user
$formMapper ->with('General') ->add('enabled', null, array('required' => false)) ->add('author', 'sonata_type_model', array(), array('edit' => 'list')) ->add('title') ->add('abstract') ->add('content') ->end() ->with('Tags') ->add('tags', 'sonata_type_model', array('expanded' => true)) ->end() ->with('Options', array('collapsed' => true)) ->add('commentsCloseAt') ->add('commentsEnabled', null, array('required' => false)) ->add('commentsDefaultStatus', 'choice', array('choices' => Comment::getStatusList())) ->end() ;
Dans le cas où il y a une relation one-to-many nous avons 3 options à disposition. Prenons une Gallery qui lie à plusieurs Media via une table intermédiaire galleryHasMedias. On peut ajouter une nouvelle ligne galleryHasMedias via ces options :
- edit : inline|standard, plus de lignes avec le mode inline
- inline : table|standard, les champs sont présentés sous forme de tableau
- sortable : si le modèle a un champ position, vous pouvez activer un effet de tri par drag&drop en mettant sortabl=field_name
$formMapper ->add('code') ->add('enabled') ->add('name') ->add('defaultFormat') ->add('galleryHasMedias', 'sonata_type_collection', array(), array( 'edit' => 'inline', 'inline' => 'table', 'sortable' => 'position' )) ;