Upgrade to Pro — share decks privately, control downloads, hide ads and more …

sfDay - Sonata Admin Bundle

Thomas R.
October 21, 2011

sfDay - Sonata Admin Bundle

Thomas R.

October 21, 2011
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. Talk • Sonata Project presentation • Quick Tour • Under

    the hood • Customize / Advanced features • Conclusion
  3. 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
  4. Sonata Project • An ecommerce toolbox • How : •

    avoiding reinvented the wheel • contribution to the community • built on top of a strong framework
  5. 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
  6. • 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.
  7. Admin Class • An metadata description of CRUD operations •

    No code generation • Based on Symfony services + Sonata Admin services
  8. 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> Register admin class with the tag “sonata.admin” And admin will appears into the dashboard
  9. List Action 1. protected function configureDatagridFilters(DatagridMapper $datagridMapper) { 2. $datagridMapper

    3. ->add('name') 4. ->add('providerReference') 5. ->add('enabled') 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. 'required' => false, 18. 'multiple' => false, 19. 'expanded' => false, 20. ), 21. 'field_type'=> 'choice', 22. )); 23. } 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. ->add('_action', 'actions', array( 32. 'actions' => array( 33. 'view' => array(), 34. 'edit' => array(), 35. ) 36. )) 37. ; 38. } field guesser custom filter edit link custom template row’s actions
  10. 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. } 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. 'filter_choice' => array('hierarchy' => 'root'), 19. 'required' => false 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() 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. } create group Form Component Define help messages group options
  11. • Permissions management • Flash messages • Nested Admin •

    Command lines utilities • Translated into more than10 languages Other Features
  12. • Dashboard • Consistent Interface across bundles • Easy to

    configure, but powerful for advanced users • Advanced features • Inspired from the django admin module (user interactions) Quick Tour Summary
  13. Admin class Routing Translator Model Manager Symfony Framework Security Validator

    Form Sonata Admin Bundle List Datagrid Show Form Builder Admin Class Dependencies
  14. Security • Based on the SecurityHandlerInterface • 2 built-in implementations

    • NoopSecurityHandler : use the Symfony’s firewall • AclSecurityHandler : based on ACL - Advanced users only
  15. 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. } 1. {% if admin.isGranted('CREATE') %} 2. // DO YOUR STUFF 3. {% endif %} • Template Usage
  16. 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
  17. Routing • Definition set from the Admin class • Can

    be tweaked by the configureRoute 1. /** 2. * @param \Sonata\AdminBundle\Route\RouteCollection $collection 3. * @return void 4. */ 5. protected function configureRoutes(RouteCollection $collection) 6. { 7. $collection->add('snapshots'); 8. $collection->remove('edit'); 9. 10. $collection->add('test', $this->getRouterIdParameter().'/test'); 11. } Add the id parameter 1. <a href="{{ admin.generateUrl('view', { 'id' : media.id, 'format' : 'reference'}) }}">reference</a> 2. <a href="{{ admin.generateObjectUrl(media, 'view', {'format' : 'reference'}) }}">reference</a> • Template Usage
  18. 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)
  19. 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)
  20. Model Manager Sonata Media Bundle 2 Model Managers : -

    entity : deal with deletion and so on ... - admin : proxy some methods to the entity How to ? 1. create a dedicated Admin Model Manager 2. create a Bundle Model Manager 3. redefine only required methods 4. define services 1. class AdminModelManager extends ModelManager { 2. protected $manager; 3. 4. public function __construct($entityManager, $manager) { 5. parent::__construct($entityManager); 6. $this->manager = $manager; 7. } 8. 9. public function delete($object) { 10. $this->manager->delete($object); 11. } 12. } 1. <service id="sonata.media.admin.media" class="Sonata\MediaBundle\Entity\BundleMediaManager"> 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="Sonata\MediaBundle\Admin\Manager\DoctrineModelManager"> 13. <argument type="service" id="doctrine.orm.default_entity_manager" /> 14. <argument type="service" id="sonata.media.manager.media" /> 15. </service> 1. class BundleMediaManager extends AbstractMediaManager { 1. public function delete(MediaInterface $media) { 2. $this->pool 3. ->getProvider($media->getProviderName()) 4. ->preRemove($media); 5. $this->em->remove($media); 6. $this->em->flush(); 7. 8. $this->pool 9. ->getProvider($media->getProviderName()) 10. ->postRemove($media); 11. $this->em->flush(); 12. } 2. } Delete thumbnails
  21. 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
  22. 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.
  23. 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
  24. Form Type • 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. )); Core types Model Manager Types Form Types
  25. Validator • Assert rules can be defined in the validation.

    [xml|yml] files (validator component) nothing new ... • Conditional Validation
  26. 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
  27. Validator 1. /** 2. * @param \Sonata\AdminBundle\Validator\ErrorElement $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. ; 13. 14. if ($object->getFoo()) { 15. $errorElement 16. ->with('test') 17. ->addViolation('my_message') 18. ->end() 19. ; 20. } 21. } symfony constraint custom error
  28. 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)
  29. 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.}
  30. 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)
  31. 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
  32. Form : many-to-one • Type : sonata_type_admin (AdminType) • Embed

    an admin form for an entity into the current admin • Sonata Options • no option!
  33. Form Theme • Based on the form theme mechanism •

    Define a getFormTheme() • a set of form templates • allows to customize an admin form
  34. 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]
  35. Form block 1.{% block sonata_user_admin_user_credentialsExpired_text_widget %} 2. PUT HERE THE

    WIDGET ... 3.{% endblock %} 1. protected $formTheme = array( 2. 'SonataAdminBundle:Form:form_admin_fields.html.twig', 3. 'SonataNewsBundle:Form:form.html.twig' 4. ); • Theme definition • Block definition • Et voila!
  36. Custom List Template 1.->add('custom', 'string', array('template' => 'SonataMediaBundle:MediaAdmin:list_custom.html.twig')) • Field

    definition • Template • Et voila! 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 %}
  37. 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
  38. Custom CRUD Controller 1. namespace Sonata\MediaBundle\Controller; 2. 3. use Sonata\AdminBundle\Controller\CRUDController

    as Controller; 4. use Symfony\Component\Security\Core\Exception\AccessDeniedException; 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']) { 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.} Grant check Custom Template Parent action
  39. 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
  40. 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. } 19. 20. protected function configureListFields(ListMapper $listMapper) 21. { 22. $listMapper 23. ->addIdentifier('name') 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. } Display custom field if the current admin is nested or not Display custom field if the current admin is nested or not
  41. 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
  42. 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
  43. small numbers • First commit in november 2010 • 650

    commits • 49 Contributors! • 14 Translations Thanks!
  44. 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)