Share
Embed
sfDay - Sonata Admin Bundle
Thomas R.
October 21, 2011
Transcript
62 slides
-
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 -
• 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. -
Admin Class • An metadata description of CRUD operations •
No code generation • Based on Symfony services + Sonata Admin services -
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 -
List Action fields, custom templates, type detection fields, type detection,
based on Form Component -
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 -
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 -
• Permissions management • Flash messages • Nested Admin •
Command lines utilities • Translated into more than10 languages Other Features -
• 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 -
Admin class Routing Translator Model Manager Symfony Framework Security Validator
Form Sonata Admin Bundle List Datagrid Show Form Builder Admin Class Dependencies -
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. } 1. {% if admin.isGranted('CREATE') %} 2. // DO YOUR STUFF 3. {% endif %} • Template Usage -
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 \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 -
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 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 -
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 -
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 -
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 \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 -
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.} -
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 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! -
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 %} -
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 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 -
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. } 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 -
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 -
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