Sonata Project
    AdminBundle
Who am I ?

• Thomas Rabaix
• Speaker at Symfony Live Conferences
• Author of many symfony1 plugins
• lead developer of the sonata project
• Working at Ekino, a french web agency
Talk

• Sonata Project presentation
• Quick Tour
• Under the hood
• Customize / Advanced features
• Conclusion
Sonata Project
• A not so young project
• Based on symfony1 plugins
• Recoded with the best practices of
  Symfony2
• Built on top on very strong and powerful
  framework
Sonata Project

• An ecommerce toolbox
• How :
   • avoiding reinvented the wheel
   • contribution to the community
   • built on top of a strong framework
Sonata’s bundles
• PageBundle : a page manager with block
  as service and strong caching mechanism
• MediaBundle : a media manager on
  steroid, you don’t have to worry about
  managing files or videos
• UserBundle, IntlBundle, etc ...
• AdminBundle : A backend generator
http://sonata-project.org
AdminBundle
   why ?
• No admin generator for Symfony 2.0
• Frustrating by the admin generator provided by
  symfony1

• Admin is not only about Model; but about
  providing a consistent and rich user
  experience for managing data.
Quick Tour




 http://www.flickr.com/photos/38104873@N03/4559985343/
Admin Class

•   An metadata description
    of CRUD operations

•   No code generation

•   Based on Symfony
    services + Sonata Admin
    services
Dashboard



        Actions
       shortcut

       Group +
        Model
Dashboard
Register admin class with the tag “sonata.admin”
  And admin will appears into the dashboard

1.         <services>
2.             <service id="sonata.news.admin.comment" class="%sonata.news.admin.comment.class%">
3.                 <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="comment"/>
4.                 <argument />
5.                 <argument>%sonata.news.admin.comment.entity%</argument>
6.                 <argument>%sonata.news.admin.comment.controller%</argument>
7.             </service>
8.  
9.                 <service id="sonata.news.admin.post" class="%sonata.news.admin.post.class%">
10.                    <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="post"/>
11.                    <argument />
12.                    <argument>%sonata.news.admin.post.entity%</argument>
13.                    <argument>%sonata.news.admin.post.controller%</argument>
14.                </service>
15. 
16.            <service id="sonata.news.admin.tag" class="%sonata.news.admin.tag.class%">
17.                <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="tag"/>
18.                <argument />
19.                <argument>%sonata.news.admin.tag.entity%</argument>
20.                <argument>%sonata.news.admin.tag.controller%</argument>
21.            </service>
22.        </services>
Breadcrumb             List Action
                                          Model
                                          Actions




              Batch
             Actions

                                Filters
List Action




 fields, custom        fields, type
templates, type     detection, based
   detection           on Form
                     Component
List Action
1.     protected function configureDatagridFilters(DatagridMapper $datagridMapper) {
2.                 $datagridMapper
3.  
4.  
5.  
        
        
        
            
            
            
                
                
                
                       ->add('name')
                       ->add('providerReference')
                       ->add('enabled')
                                                    field guesser
6.                     ->add('context')
7.                 ;
8.                 $providers = array();
9.  
10.          foreach($this->pool->getProviderNamesByContext('default') as $name) {
11.              $providers[$name] = $name;
12.          }
13. 
14.                $datagridMapper->add('providerName', 'doctrine_orm_choice', array(
15.                    'field_options'=> array(
16.                        'choices' => $providers,
17. 
18. 
19. 
        
        
        
            
            
            
                
                
                
                           'required' => false,
                           'multiple' => false,
                           'expanded' => false,
                                                                                            custom filter
20.                    ),
21.                    'field_type'=> 'choice',
22.                ));
23.        }


                                                              edit link
24. 
25.        protected function configureListFields(ListMapper $listMapper) {
26.            $listMapper
27.                ->addIdentifier('id')
28.                ->add('image', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_image.html.twig'))
29.                ->add('custom', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_custom.html.twig'))
30.                ->add('enabled')
31. 
32. 
        
        
                   ->add('_action', 'actions', array(
                       'actions' => array(                                              custom template
33.                        'view' => array(),
34.                        'edit' => array(),
35.                    )
36.                ))
37. 
38. 
        
        
               ;
           }                                                     row’s actions
Edit/Create Form




side menu


                      Field Group
Edit/Create Form


                        Helper message


                                add relation

                              hide fields




save options          delete action
Edit/Create Form
1.          protected function configureFormFields(FormMapper $formMapper)
2.          {
3.              $templates = array();
4.              foreach ($this->cmsManager->getPageManager()->getTemplates() as $code => $template) {
5.                  $templates[$code] = $template->getName();
6.              }

                                                                                             create group
7.  
8.                  $formMapper
9.                      ->with($this->trans('form_page.group_main_label'))
10.                         ->add('name')
11.                         ->add('enabled', null, array('required' => false))
12.                         ->add('position')
13.                         ->add('templateCode', 'choice', array('required' => true, 'choices' => $templates))
14.                         ->add('parent', 'sonata_page_selector', array(
15.                             'page'          => $this->getSubject() ?: null,
16.                             'model_manager' => $this->getModelManager(),
17.                             'class'         => $this->getClass(),
18.  
19.  
         
         
             
             
                 
                 
                                'filter_choice' => array('hierarchy' => 'root'),
                                'required'      => false                                Form Component
20.                         ))
21.                     ->end()
22.                 ;
23.  
24.                 $formMapper
25.                     ->with($this->trans('form_page.group_seo_label'), array('collapsed' => true))
26.                         ->add('metaKeyword', 'textarea', array('required' => false))
27.                         ->add('metaDescription', 'textarea', array('required' => false))
28.                     ->end()

                                                                                                             group options
29.                 ;
30.  
31.                 $formMapper
32.                     ->with($this->trans('form_page.group_advanced_label'), array('collapsed' => true))
33.                         ->add('javascript', null,  array('required' => false))
34.                         ->add('stylesheet', null, array('required' => false))
35.                         ->add('rawHeaders', null, array('required' => false))
36.                     ->end()
37.                 ;
38.  
39.             $formMapper->setHelps(array(
40.                 'name' => $this->trans('help_page_name')
41.  
42.  
         
         
                ));
            }
                                                                               Define help messages
Other Features

• Permissions management
• Flash messages
• Nested Admin
• Command lines utilities
• Translated into more than10 languages
Quick Tour Summary
• Dashboard
• Consistent Interface across bundles
• Easy to configure, but powerful for
  advanced users

• Advanced features
• Inspired from the django admin module
  (user interactions)
Under the hood




   http://www.flickr.com/photos/52251564@N08/5937620090
Admin Class Dependencies

                    Security              Builder




                                                      Sonata Admin Bundle
                                             List
Symfony Framework




                    Translator
                                           Datagrid

                                 Admin      Show
                     Routing
                                  class     Form


                    Validator
                                           Model
                                          Manager
                      Form
Security

• Based on the SecurityHandlerInterface
• 2 built-in implementations
   • NoopSecurityHandler : use the
      Symfony’s firewall
   • AclSecurityHandler : based on ACL
      - Advanced users only
Security
          • Admin Usage
1.     protected function configureFormFields(FormMapper $formMapper)
2.     {
3.         $formMapper
4.             ->with('General')
5.                 ->add('enabled', null, array('required' => false))
6.                 ->add('author', 'sonata_type_model', array(), array('edit' => 'list'))
7.                 ->add('title')
8.             ->end()
9.         ;
10.       
11.        if (!$this->isGranted('CREATE')) {
12.            // do specific code if the user cannot create a new object
13.        }
14.    }




          • Template Usage
                        1. {% if admin.isGranted('CREATE') %}
                        2.    // DO YOUR STUFF
                        3. {% endif %}
Security : ACL
• Required to have a custom external bundle to
  manage permissions and user : see FOS/
  UserBundle and Sonata/UserBundle
• Built on top of a custom MaskBuilder (basic
  roles : List,View, Edit, Create, Delete)

• Command lines :
   • php app/console init:acl
   • php app/console sonata:admin:setup-acl
Routing
          • Definition set from the Admin class
          • Can be tweaked by the configureRoute
        1.         /**
        2.          * @param SonataAdminBundleRouteRouteCollection $collection
        3.          * @return void
        4.          */
        5.         protected function configureRoutes(RouteCollection $collection)
        6.         {
        7.             $collection->add('snapshots');                                Add the id parameter
        8.             $collection->remove('edit');
        9.
        10.          $collection->add('test', $this->getRouterIdParameter().'/test');
        11.      }




          • Template Usage
1. <a href="{{ admin.generateUrl('view', { 'id' : media.id, 'format' : 'reference'}) }}">reference</a>
2. <a href="{{ admin.generateObjectUrl(media, 'view', {'format' : 'reference'}) }}">reference</a>
Admin Model Manager
• Persistency layer abstraction for Admin Bundle.
• All Persistencies actions are done in the Model
  Manager
    • delete, query, pagination, etc..
    • form type manipulation delegation
• For now only Doctrine ORM (Propel and
  Doctrine ODM are work in progress by external
  contributors)
Admin Model Manager

• Some bundle provides custom Model Manager

• You can define your own proxy Admin Model
  Manager to reuse the Bundle Model Manager


• Use case : Sonata Media Bundle (delete action)
Model Manager                              1. class AdminModelManager extends ModelManager {
                                                                           2.     protected $manager;

Sonata Media Bundle                                                        3.  
                                                                           4.     public function __construct($entityManager, $manager) {
                                                                           5.         parent::__construct($entityManager);
  2 Model Managers :                                                       6.         $this->manager = $manager;
     - entity : deal with deletion and so on ...                           7.     }
                                                                           8.  
     - admin : proxy some methods to the entity                            9.     public function delete($object) {
                                                                           10.         $this->manager->delete($object);
                                                                           11.     }
  How to ?                                                                 12. }

    1. create a dedicated Admin Model Manager                              1. class BundleMediaManager extends AbstractMediaManager {  
    2. create a Bundle Model Manager                                       1.      public function delete(MediaInterface $media) {
                                                                           2.         $this->pool
    3. redefine only required methods                                       3.              ->getProvider($media->getProviderName())
    4. define services                                                      4.              ->preRemove($media);
                                                                           5.         $this->em->remove($media);
                                                                           6.         $this->em->flush();

                                    Delete thumbnails
                                                                           7.  
                                                                           8.         $this->pool
                                                                           9.              ->getProvider($media->getProviderName())
                                                                           10.             ->postRemove($media);
                                                                           11.         $this->em->flush();
                                                                           12.     }
                                                                           2. }

   1.              <service id="sonata.media.admin.media" class="SonataMediaBundleEntityBundleMediaManager">
   2.                    <tag name="sonata.admin" manager_type="orm" group="sonata_media" label="media"/>
   3.                    <argument />
   4.                    <argument>%sonata.media.admin.media.entity%</argument>
   5.                    <argument>%sonata.media.admin.media.controller%</argument>
   6.  
   7.                      <call method="setModelManager">
   8.                          <argument type="service" id="sonata.media.admin.media.manager" />
   9.                      </call>
   10.                 </service>
   11.  
   12.                 <service id="sonata.media.admin.media.manager" class="SonataMediaBundleAdminManagerDoctrineModelManager">
   13.                     <argument type="service" id="doctrine.orm.default_entity_manager" />
   14.                     <argument type="service" id="sonata.media.manager.media" />
   15.                 </service>
Translator
• 2 catalogues
   • SonataAdminBundle used to translate
     shared messages
   • messages used to translate current Admin
      1.                   $formMapper
      2.                       ->with($this->trans('form_page.group_main_label'))
      3.                           ->add('name')
      4.                       ->end()
      5.                   ;



• Can be set by updating the translationDomain
  property
Form

• Originally built on the the first implementation
• Too bad ..... major refactoring 3 months later
   • Some admin features has been removed or still
      broken :(
   • But the new form implementation is pretty
      awesome .... once you know how to use it.
Form Mapper

• Interact with the FormMapper
• Proxy class between the Admin Class and the
  Symfony Form Component
• Act as the Symfony FormBuilder component
• You can use your custom field types
Core types


                                   Form Type                                                Model Manager Types

                                                                                                Form Types



• AdminType : used to embedded form from
  another Admin class
• CollectionType : use by one-to-many association
• ModelType : select choice (like EntityType)
• ModelReferenceType : handle an model id
• ImmutableArrayType : specify a form type per
  array element
  1.                   $formMapper->add('settings', 'sonata_type_immutable_array', array(
  2.                       'keys' => array(
  3.                           array('layout', 'textarea', array()),
  4.                           array('action', 'text', array()),
  5.                           array('parameters', 'text', array()),
  6.                       )
  7.                   ));
Validator


• Assert rules can be defined in the validation.
  [xml|yml] files (validator component)
  nothing new ...




• Conditional Validation
Conditional Validation

• Not mandatory, but allow to add inline validation
  a runtime.
    • ex : check only if a value is set
• Validate method inside the admin class
• Interact with a new ErrorElement object
• The ErrorElement is just a validator service based
  on the Validator Component
Validator
1.     /**
2.        * @param SonataAdminBundleValidatorErrorElement $errorElement
3.        * @param $object
4.        * @return void
5.        */
6.       public function validate(ErrorElement $errorElement, $object)
7.       {
8.           $errorElement
9.               ->with('name')
10.                  ->assertMaxLength(array('limit' => 32))
11.              ->end()
12.          ;

                                                      symfony constraint
13.         
14.          if ($object->getFoo()) {
15.              $errorElement
16.                  ->with('test')
17.                      ->addViolation('my_message')
18.                  ->end()
19.              ;
20.          }
21.      }                                                  custom error
Menu

• Based on KnpMenu lib

• Used for Breadcrumb and Side Menu

• No magic for sidemenu, need to code your
  own menu per admin (if required)
Menu

1. class PostAdmin extends Admin
2. {
3.     protected function configureSideMenu(MenuItemInterface $menu, $action, Admin $childAdmin = null)
4.     {
5.         if (!$childAdmin && !in_array($action, array('edit'))) {
6.             return;
7.         }
8.  
9.         $admin = $this->isChild() ? $this->getParent() : $this;
10. 
11.        $id = $admin->getRequest()->get('id');
12. 
13.        $menu->addChild(
14.            $this->trans('view_post'),
15.            array('uri' => $admin->generateUrl('edit', array('id' => $id)))
16.        );
17. 
18.        $menu->addChild(
19.            $this->trans('link_view_comment'),
20.            array('uri' => $admin->generateUrl('sonata.news.admin.comment.list', array('id' => $id)))
21.        );
22.    }
23.}
customize




http://www.flickr.com/photos/7552532@N07/449769140/
Form : one-to-many
• Type : sonata_type_collection (CollectionType)
• Form Option
       by_reference => false
• Sonata Options
     • edit : standard / inline
     • inline : table | list
     • position : field name (if exists)
Form : many-to-many


• Type : sonata_type_model (ModelType)
• Sonata Options
     • no options
Form : many-to-one
• Type : sonata_type_model (ModelType)
• Form Options :
     • expanded : true|false
• Sonata Options
     • edit : standard (select box) / list (popup)
     • link_parameters : add extra link parameters to
        the link
Form : many-to-one

• Type : sonata_type_admin (AdminType)
• Embed an admin form for an entity into the current
  admin
• Sonata Options
     • no option!
Form Theme


• Based on the form theme mechanism
• Define a getFormTheme()
   • a set of form templates
   • allows to customize an admin form
Form Theme
•   Just define a custom block with the correct name ...

•   How to know the block name ... ?




•   Provide a custom `block_name` option

•   or use a defined built-in pattern :
    admin_service_id_[type]_[widget|label|errors|row]
    admin_service_id_[fieldName]_[widget|label|errors|row]
Form block
  •       Theme definition

1.        protected $formTheme = array(
2.            'SonataAdminBundle:Form:form_admin_fields.html.twig',
3.            'SonataNewsBundle:Form:form.html.twig'
4.        );


  •       Block definition

1.{% block sonata_user_admin_user_credentialsExpired_text_widget %}
2.   PUT HERE THE WIDGET ...
3.{% endblock %}



  •       Et voila!
Custom List Template
 •   Field definition
1.->add('custom', 'string', array('template' =>
  'SonataMediaBundle:MediaAdmin:list_custom.html.twig'))

 •   Template
1.{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %}
2. 
3.{% block field %}
4.    <div>
5.        <strong>{{ object.name }}</strong> <br />
6.        {{ object.providerName|trans({}, 'SonataMediaBundle') }}:
  {{ object.width }}x{{ object.height }} <br />
7.    </div>
8.{% endblock %}


 •   Et voila!
CRUD Controller
• Use the admin class to generate required
  objects
• contains create, edit, update, delete and
  batch actions
• can be extended to change some logic
• use case : Sonata Media Bundle
Custom CRUD Controller
1. namespace SonataMediaBundleController;
2.  
3. use SonataAdminBundleControllerCRUDController as Controller; Grant check
4. use SymfonyComponentSecurityCoreExceptionAccessDeniedException;
5.  
6. class MediaAdminController extends Controller {
7.     public function createAction() {
8.         if (false === $this->admin->isGranted('CREATE')) {
9.             throw new AccessDeniedException();
10.        }
11. 
12.        $parameters = $this->admin->getPersistentParameters();
13. 
14.        if (!$parameters['provider']) {
                                                         Custom Template
15.            return $this-
   >render('SonataMediaBundle:MediaAdmin:select_provider.html.twig', array(
16.                'providers'     => $this->get('sonata.media.pool')
17.                                         ->getProvidersByContext($this->get('request')-
   >get('context', 'default')),
18.                'base_template' => $this->getBaseTemplate(),
19.                'admin'         => $this->admin,
20.                'action'        => 'create'
21.            ));
22.        }
23. 
24.        return parent::createAction();
25.    }
26.}

                                   Parent action
Nested Admin

• clean url : /admin/sonata/news/post/1/comment/list
• reuse other admin definition
• autofilter with the targeted elements
• only work on one level
• You don’t need to know routing name, as long as
  you use the admin class
Nested Admin
1. class CommentAdmin extends Admin
2. {
3.     protected $parentAssociationMapping = 'post';
4.  
5.     protected function configureFormFields(FormMapper $formMapper)
6.     {
7.         if(!$this->isChild()) {
8.             $formMapper->add('post', 'sonata_type_model', array(), array('edit' => 'list'));
9.         }
10.  
11.         $formMapper
12.             ->add('name')
13.             ->add('email')
14.             ->add('url', null, array('required' => false))
15.             ->add('message')
16.             ->add('status', 'choice', array('choices' => Comment::getStatusList(), 'expanded' => true, 'multiple' => false))
17.         ;
18.     }                                                                                     Display custom
                                                                                                field if the
19.  
20.     protected function configureListFields(ListMapper $listMapper)
21.     {
22.         $listMapper
23.             ->addIdentifier('name')                                                      current admin is
                                                                                              nested or not
24.             ->add('getStatusCode', 'text', array('label' => 'status_code', 'sortable' => 'status'))
25.         ;
26.  
27.         if (!$this->isChild()) {
28.             $listMapper->add('post');
29.         }
30.  
31.         $listMapper
32.             ->add('email')
33.             ->add('url')
34.             ->add('message');
35.     }
36. }
Nested Admin




Comment Admin   Nested Comment Admin
Admin Extension

• Allow to add extra feature or redefine some field to
  the admin
• Good entry point if you extends some entities
• Add extension must implement the
  AdminExtensionInterface and define as
  service with the sonata.admin.extension
Debug
•   Check out external configurations

      •   validator configuration

      •   doctrine schema definition

      •   use firebug to check Ajax errors (missing toString method or
          type hinting)

•   Get information from the Admin command tools

      •   sonata:admin:list

      •   sonata:admin:explain
sonata:admin:list




 service name can be used to
explain the admin configuration
sonata:admin:explain
conclusion




 http://www.flickr.com/photos/rv-bordeaux/5909437215/
small numbers
•   First commit in
    november 2010

•   650 commits

•   49 Contributors!

•   14 Translations



           Thanks!
What’s next ?
• Version 1
   • Stabilize the AdminBundle
   • Add more tests... : 56 tests, 145
      assertions
• Version 1.1
   • Add missing features from the original
      form factoring
    • Add more layer persistency
      (contribution)
Resources


• http://sonata-project.org
• irc : #symfony
• twitter : @sonataproject
• symfony google groups
Questions




 http://www.flickr.com/photos/colinkinner/2200500024/

sfDay Cologne - Sonata Admin Bundle

  • 1.
    Sonata Project AdminBundle
  • 2.
    Who am I? • Thomas Rabaix • Speaker at Symfony Live Conferences • Author of many symfony1 plugins • lead developer of the sonata project • Working at Ekino, a french web agency
  • 3.
    Talk • Sonata Projectpresentation • Quick Tour • Under the hood • Customize / Advanced features • Conclusion
  • 4.
    Sonata Project • Anot so young project • Based on symfony1 plugins • Recoded with the best practices of Symfony2 • Built on top on very strong and powerful framework
  • 5.
    Sonata Project • Anecommerce toolbox • How : • avoiding reinvented the wheel • contribution to the community • built on top of a strong framework
  • 6.
    Sonata’s bundles • PageBundle: a page manager with block as service and strong caching mechanism • MediaBundle : a media manager on steroid, you don’t have to worry about managing files or videos • UserBundle, IntlBundle, etc ... • AdminBundle : A backend generator
  • 7.
  • 8.
  • 9.
    • No admingenerator for Symfony 2.0 • Frustrating by the admin generator provided by symfony1 • Admin is not only about Model; but about providing a consistent and rich user experience for managing data.
  • 10.
  • 11.
    Admin Class • An metadata description of CRUD operations • No code generation • Based on Symfony services + Sonata Admin services
  • 12.
    Dashboard Actions shortcut Group + Model
  • 13.
    Dashboard Register admin classwith the tag “sonata.admin” And admin will appears into the dashboard 1.     <services> 2.         <service id="sonata.news.admin.comment" class="%sonata.news.admin.comment.class%"> 3.             <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="comment"/> 4.             <argument /> 5.             <argument>%sonata.news.admin.comment.entity%</argument> 6.             <argument>%sonata.news.admin.comment.controller%</argument> 7.         </service> 8.   9.         <service id="sonata.news.admin.post" class="%sonata.news.admin.post.class%"> 10.            <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="post"/> 11.            <argument /> 12.            <argument>%sonata.news.admin.post.entity%</argument> 13.            <argument>%sonata.news.admin.post.controller%</argument> 14.        </service> 15.  16.        <service id="sonata.news.admin.tag" class="%sonata.news.admin.tag.class%"> 17.            <tag name="sonata.admin" manager_type="orm" group="sonata_blog" label="tag"/> 18.            <argument /> 19.            <argument>%sonata.news.admin.tag.entity%</argument> 20.            <argument>%sonata.news.admin.tag.controller%</argument> 21.        </service> 22.    </services>
  • 14.
    Breadcrumb List Action Model Actions Batch Actions Filters
  • 15.
    List Action fields,custom fields, type templates, type detection, based detection on Form Component
  • 16.
    List Action 1.    protected function configureDatagridFilters(DatagridMapper $datagridMapper) { 2.         $datagridMapper 3.   4.   5.                         ->add('name')     ->add('providerReference')     ->add('enabled') field guesser 6.             ->add('context') 7.         ; 8.         $providers = array(); 9.   10.        foreach($this->pool->getProviderNamesByContext('default') as $name) { 11.            $providers[$name] = $name; 12.        } 13.  14.        $datagridMapper->add('providerName', 'doctrine_orm_choice', array( 15.            'field_options'=> array( 16.                'choices' => $providers, 17.  18.  19.                            'required' => false,         'multiple' => false,         'expanded' => false, custom filter 20.            ), 21.            'field_type'=> 'choice', 22.        )); 23.    } edit link 24.  25.    protected function configureListFields(ListMapper $listMapper) { 26.        $listMapper 27.            ->addIdentifier('id') 28.            ->add('image', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_image.html.twig')) 29.            ->add('custom', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_custom.html.twig')) 30.            ->add('enabled') 31.  32.              ->add('_action', 'actions', array(             'actions' => array( custom template 33.                    'view' => array(), 34.                    'edit' => array(), 35.                ) 36.            )) 37.  38.          ; } row’s actions
  • 17.
  • 18.
    Edit/Create Form Helper message add relation hide fields save options delete action
  • 19.
    Edit/Create Form 1.     protected function configureFormFields(FormMapper $formMapper) 2.     { 3.         $templates = array(); 4.         foreach ($this->cmsManager->getPageManager()->getTemplates() as $code => $template) { 5.             $templates[$code] = $template->getName(); 6.         } create group 7.   8.         $formMapper 9.             ->with($this->trans('form_page.group_main_label')) 10.                 ->add('name') 11.                 ->add('enabled', null, array('required' => false)) 12.                 ->add('position') 13.                 ->add('templateCode', 'choice', array('required' => true, 'choices' => $templates)) 14.                 ->add('parent', 'sonata_page_selector', array( 15.                     'page'          => $this->getSubject() ?: null, 16.                     'model_manager' => $this->getModelManager(), 17.                     'class'         => $this->getClass(), 18.   19.                           'filter_choice' => array('hierarchy' => 'root'),             'required'      => false Form Component 20.                 )) 21.             ->end() 22.         ; 23.   24.         $formMapper 25.             ->with($this->trans('form_page.group_seo_label'), array('collapsed' => true)) 26.                 ->add('metaKeyword', 'textarea', array('required' => false)) 27.                 ->add('metaDescription', 'textarea', array('required' => false)) 28.             ->end() group options 29.         ; 30.   31.         $formMapper 32.             ->with($this->trans('form_page.group_advanced_label'), array('collapsed' => true)) 33.                 ->add('javascript', null,  array('required' => false)) 34.                 ->add('stylesheet', null, array('required' => false)) 35.                 ->add('rawHeaders', null, array('required' => false)) 36.             ->end() 37.         ; 38.   39.         $formMapper->setHelps(array( 40.             'name' => $this->trans('help_page_name') 41.   42.           )); } Define help messages
  • 20.
    Other Features • Permissionsmanagement • Flash messages • Nested Admin • Command lines utilities • Translated into more than10 languages
  • 21.
    Quick Tour Summary •Dashboard • Consistent Interface across bundles • Easy to configure, but powerful for advanced users • Advanced features • Inspired from the django admin module (user interactions)
  • 22.
    Under the hood http://www.flickr.com/photos/52251564@N08/5937620090
  • 23.
    Admin Class Dependencies Security Builder Sonata Admin Bundle List Symfony Framework Translator Datagrid Admin Show Routing class Form Validator Model Manager Form
  • 24.
    Security • Based onthe SecurityHandlerInterface • 2 built-in implementations • NoopSecurityHandler : use the Symfony’s firewall • AclSecurityHandler : based on ACL - Advanced users only
  • 25.
    Security • Admin Usage 1.     protected function configureFormFields(FormMapper $formMapper) 2.     { 3.         $formMapper 4.             ->with('General') 5.                 ->add('enabled', null, array('required' => false)) 6.                 ->add('author', 'sonata_type_model', array(), array('edit' => 'list')) 7.                 ->add('title') 8.             ->end() 9.         ; 10.        11.        if (!$this->isGranted('CREATE')) { 12.            // do specific code if the user cannot create a new object 13.        } 14.    } • Template Usage 1. {% if admin.isGranted('CREATE') %} 2.    // DO YOUR STUFF 3. {% endif %}
  • 26.
    Security : ACL •Required to have a custom external bundle to manage permissions and user : see FOS/ UserBundle and Sonata/UserBundle • Built on top of a custom MaskBuilder (basic roles : List,View, Edit, Create, Delete) • Command lines : • php app/console init:acl • php app/console sonata:admin:setup-acl
  • 27.
    Routing • Definition set from the Admin class • Can be tweaked by the configureRoute 1.     /** 2.      * @param SonataAdminBundleRouteRouteCollection $collection 3.      * @return void 4.      */ 5.     protected function configureRoutes(RouteCollection $collection) 6.     { 7.         $collection->add('snapshots'); Add the id parameter 8.         $collection->remove('edit'); 9. 10.        $collection->add('test', $this->getRouterIdParameter().'/test'); 11.    } • Template Usage 1. <a href="{{ admin.generateUrl('view', { 'id' : media.id, 'format' : 'reference'}) }}">reference</a> 2. <a href="{{ admin.generateObjectUrl(media, 'view', {'format' : 'reference'}) }}">reference</a>
  • 28.
    Admin Model Manager •Persistency layer abstraction for Admin Bundle. • All Persistencies actions are done in the Model Manager • delete, query, pagination, etc.. • form type manipulation delegation • For now only Doctrine ORM (Propel and Doctrine ODM are work in progress by external contributors)
  • 29.
    Admin Model Manager •Some bundle provides custom Model Manager • You can define your own proxy Admin Model Manager to reuse the Bundle Model Manager • Use case : Sonata Media Bundle (delete action)
  • 30.
    Model Manager 1. class AdminModelManager extends ModelManager { 2.     protected $manager; Sonata Media Bundle 3.   4.     public function __construct($entityManager, $manager) { 5.         parent::__construct($entityManager); 2 Model Managers : 6.         $this->manager = $manager; - entity : deal with deletion and so on ... 7.     } 8.   - admin : proxy some methods to the entity 9.     public function delete($object) { 10.         $this->manager->delete($object); 11.     } How to ? 12. } 1. create a dedicated Admin Model Manager 1. class BundleMediaManager extends AbstractMediaManager {   2. create a Bundle Model Manager 1. public function delete(MediaInterface $media) { 2.         $this->pool 3. redefine only required methods 3. ->getProvider($media->getProviderName()) 4. define services 4. ->preRemove($media); 5.         $this->em->remove($media); 6.         $this->em->flush(); Delete thumbnails 7.   8.         $this->pool 9. ->getProvider($media->getProviderName()) 10. ->postRemove($media); 11.         $this->em->flush(); 12.     } 2. } 1.       <service id="sonata.media.admin.media" class="SonataMediaBundleEntityBundleMediaManager"> 2.             <tag name="sonata.admin" manager_type="orm" group="sonata_media" label="media"/> 3.             <argument /> 4.             <argument>%sonata.media.admin.media.entity%</argument> 5.             <argument>%sonata.media.admin.media.controller%</argument> 6.   7.             <call method="setModelManager"> 8.                 <argument type="service" id="sonata.media.admin.media.manager" /> 9.             </call> 10.         </service> 11.   12.         <service id="sonata.media.admin.media.manager" class="SonataMediaBundleAdminManagerDoctrineModelManager"> 13.             <argument type="service" id="doctrine.orm.default_entity_manager" /> 14.             <argument type="service" id="sonata.media.manager.media" /> 15.         </service>
  • 31.
    Translator • 2 catalogues • SonataAdminBundle used to translate shared messages • messages used to translate current Admin 1.         $formMapper 2.             ->with($this->trans('form_page.group_main_label')) 3.                 ->add('name') 4.             ->end() 5.         ; • Can be set by updating the translationDomain property
  • 32.
    Form • Originally builton the the first implementation • Too bad ..... major refactoring 3 months later • Some admin features has been removed or still broken :( • But the new form implementation is pretty awesome .... once you know how to use it.
  • 33.
    Form Mapper • Interactwith the FormMapper • Proxy class between the Admin Class and the Symfony Form Component • Act as the Symfony FormBuilder component • You can use your custom field types
  • 34.
    Core types Form Type Model Manager Types Form Types • AdminType : used to embedded form from another Admin class • CollectionType : use by one-to-many association • ModelType : select choice (like EntityType) • ModelReferenceType : handle an model id • ImmutableArrayType : specify a form type per array element 1.         $formMapper->add('settings', 'sonata_type_immutable_array', array( 2.             'keys' => array( 3.                 array('layout', 'textarea', array()), 4.                 array('action', 'text', array()), 5.                 array('parameters', 'text', array()), 6.             ) 7.         ));
  • 35.
    Validator • Assert rulescan be defined in the validation. [xml|yml] files (validator component) nothing new ... • Conditional Validation
  • 36.
    Conditional Validation • Notmandatory, but allow to add inline validation a runtime. • ex : check only if a value is set • Validate method inside the admin class • Interact with a new ErrorElement object • The ErrorElement is just a validator service based on the Validator Component
  • 37.
    Validator 1.   /** 2.      * @param SonataAdminBundleValidatorErrorElement $errorElement 3.      * @param $object 4.      * @return void 5.      */ 6.     public function validate(ErrorElement $errorElement, $object) 7.     { 8.         $errorElement 9.             ->with('name') 10.                ->assertMaxLength(array('limit' => 32)) 11.            ->end() 12.        ; symfony constraint 13.        14.        if ($object->getFoo()) { 15.            $errorElement 16.                ->with('test') 17.                    ->addViolation('my_message') 18.                ->end() 19.            ; 20.        } 21.    } custom error
  • 38.
    Menu • Based onKnpMenu lib • Used for Breadcrumb and Side Menu • No magic for sidemenu, need to code your own menu per admin (if required)
  • 39.
    Menu 1. class PostAdminextends Admin 2. { 3.     protected function configureSideMenu(MenuItemInterface $menu, $action, Admin $childAdmin = null) 4.     { 5.         if (!$childAdmin && !in_array($action, array('edit'))) { 6.             return; 7.         } 8.   9.         $admin = $this->isChild() ? $this->getParent() : $this; 10.  11.        $id = $admin->getRequest()->get('id'); 12.  13.        $menu->addChild( 14.            $this->trans('view_post'), 15.            array('uri' => $admin->generateUrl('edit', array('id' => $id))) 16.        ); 17.  18.        $menu->addChild( 19.            $this->trans('link_view_comment'), 20.            array('uri' => $admin->generateUrl('sonata.news.admin.comment.list', array('id' => $id))) 21.        ); 22.    } 23.}
  • 40.
  • 41.
    Form : one-to-many •Type : sonata_type_collection (CollectionType) • Form Option by_reference => false • Sonata Options • edit : standard / inline • inline : table | list • position : field name (if exists)
  • 42.
    Form : many-to-many •Type : sonata_type_model (ModelType) • Sonata Options • no options
  • 43.
    Form : many-to-one •Type : sonata_type_model (ModelType) • Form Options : • expanded : true|false • Sonata Options • edit : standard (select box) / list (popup) • link_parameters : add extra link parameters to the link
  • 44.
    Form : many-to-one •Type : sonata_type_admin (AdminType) • Embed an admin form for an entity into the current admin • Sonata Options • no option!
  • 45.
    Form Theme • Basedon the form theme mechanism • Define a getFormTheme() • a set of form templates • allows to customize an admin form
  • 46.
    Form Theme • Just define a custom block with the correct name ... • How to know the block name ... ? • Provide a custom `block_name` option • or use a defined built-in pattern : admin_service_id_[type]_[widget|label|errors|row] admin_service_id_[fieldName]_[widget|label|errors|row]
  • 47.
    Form block • Theme definition 1.    protected $formTheme = array( 2.        'SonataAdminBundle:Form:form_admin_fields.html.twig', 3.        'SonataNewsBundle:Form:form.html.twig' 4.    ); • Block definition 1.{% block sonata_user_admin_user_credentialsExpired_text_widget %} 2.   PUT HERE THE WIDGET ... 3.{% endblock %} • Et voila!
  • 48.
    Custom List Template • Field definition 1.->add('custom', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_custom.html.twig')) • Template 1.{% extends 'SonataAdminBundle:CRUD:base_list_field.html.twig' %} 2.  3.{% block field %} 4.    <div> 5.        <strong>{{ object.name }}</strong> <br /> 6.        {{ object.providerName|trans({}, 'SonataMediaBundle') }}: {{ object.width }}x{{ object.height }} <br /> 7.    </div> 8.{% endblock %} • Et voila!
  • 49.
    CRUD Controller • Usethe admin class to generate required objects • contains create, edit, update, delete and batch actions • can be extended to change some logic • use case : Sonata Media Bundle
  • 50.
    Custom CRUD Controller 1.namespace SonataMediaBundleController; 2.   3. use SonataAdminBundleControllerCRUDController as Controller; Grant check 4. use SymfonyComponentSecurityCoreExceptionAccessDeniedException; 5.   6. class MediaAdminController extends Controller { 7.     public function createAction() { 8.         if (false === $this->admin->isGranted('CREATE')) { 9.             throw new AccessDeniedException(); 10.        } 11.  12.        $parameters = $this->admin->getPersistentParameters(); 13.  14.        if (!$parameters['provider']) { Custom Template 15.            return $this- >render('SonataMediaBundle:MediaAdmin:select_provider.html.twig', array( 16.                'providers'     => $this->get('sonata.media.pool') 17. ->getProvidersByContext($this->get('request')- >get('context', 'default')), 18.                'base_template' => $this->getBaseTemplate(), 19.                'admin'         => $this->admin, 20.                'action'        => 'create' 21.            )); 22.        } 23.  24.        return parent::createAction(); 25.    } 26.} Parent action
  • 51.
    Nested Admin • cleanurl : /admin/sonata/news/post/1/comment/list • reuse other admin definition • autofilter with the targeted elements • only work on one level • You don’t need to know routing name, as long as you use the admin class
  • 52.
    Nested Admin 1. classCommentAdmin extends Admin 2. { 3.     protected $parentAssociationMapping = 'post'; 4.   5.     protected function configureFormFields(FormMapper $formMapper) 6.     { 7.         if(!$this->isChild()) { 8.             $formMapper->add('post', 'sonata_type_model', array(), array('edit' => 'list')); 9.         } 10.   11.         $formMapper 12.             ->add('name') 13.             ->add('email') 14.             ->add('url', null, array('required' => false)) 15.             ->add('message') 16.             ->add('status', 'choice', array('choices' => Comment::getStatusList(), 'expanded' => true, 'multiple' => false)) 17.         ; 18.     } Display custom field if the 19.   20.     protected function configureListFields(ListMapper $listMapper) 21.     { 22.         $listMapper 23.             ->addIdentifier('name') current admin is nested or not 24.             ->add('getStatusCode', 'text', array('label' => 'status_code', 'sortable' => 'status')) 25.         ; 26.   27.         if (!$this->isChild()) { 28.             $listMapper->add('post'); 29.         } 30.   31.         $listMapper 32.             ->add('email') 33.             ->add('url') 34.             ->add('message'); 35.     } 36. }
  • 53.
    Nested Admin Comment Admin Nested Comment Admin
  • 54.
    Admin Extension • Allowto add extra feature or redefine some field to the admin • Good entry point if you extends some entities • Add extension must implement the AdminExtensionInterface and define as service with the sonata.admin.extension
  • 55.
    Debug • Check out external configurations • validator configuration • doctrine schema definition • use firebug to check Ajax errors (missing toString method or type hinting) • Get information from the Admin command tools • sonata:admin:list • sonata:admin:explain
  • 56.
    sonata:admin:list service namecan be used to explain the admin configuration
  • 57.
  • 58.
  • 59.
    small numbers • First commit in november 2010 • 650 commits • 49 Contributors! • 14 Translations Thanks!
  • 60.
    What’s next ? •Version 1 • Stabilize the AdminBundle • Add more tests... : 56 tests, 145 assertions • Version 1.1 • Add missing features from the original form factoring • Add more layer persistency (contribution)
  • 61.
    Resources • http://sonata-project.org • irc: #symfony • twitter : @sonataproject • symfony google groups
  • 62.