Working With The Symfony Admin Generator

50,996 views
49,919 views

Published on

Advanced usage of the symfony admin generator.

Published in: Technology
5 Comments
39 Likes
Statistics
Notes
No Downloads
Views
Total views
50,996
On SlideShare
0
From Embeds
0
Number of Embeds
2,458
Actions
Shares
0
Downloads
870
Comments
5
Likes
39
Embeds 0
No embeds

No notes for slide

Working With The Symfony Admin Generator

  1. Working with the admin generator
  2. My background John Cleveley Trained as a Systems Engineer with BAE Systems Started my own business in 2006 Created symfony apps for NHS, V&A Museum and Hornby. Currently work at the BBC using their Forge platform (ZF)
  3. Objectives Provide information beyond the docs Update on what’s new Suggest a few best practices Real world examples of customising How to get the best from the form framework Utilise the various ways of extending
  4. It’s super awesome. Saves huge amount of development time and costs Provides most common admin requirements out of the box Can be extended to provide bespoke needs Fully tested and documented It’s free!
  5. What’s new since 1.0? Completely re-written for the form framework Relationships including m2m work without configuration Generator.yml is validated Batch actions (delete) Less reliance on generator.yml (DRY) More templates and actions to override Different configuration for the edit and new form Adds a REST route for your module
  6. New PHP configuration file Configure generator via PHP to be more dynamic: lib/newsGeneratorConfiguration.class.php The generated class in the cache lists functions cache/backend/dev/modules/autoNews/lib/BaseNe wsGeneratorConfiguration.class.php You can also mix configuration between the two
  7. New helper file Provides html snippets for action links lib/[module]GeneratorHelper.class.php getUrlForAction () linkToDelete () linkToEdit () linkToList () linkToNew () linkToSave () linkToSaveAndAdd () Create custom links with extra javascript etc
  8. Your thoughts? PHP! YAML!
  9. Is it the right tool? …maybe.
  10. Think requirements! Don’t jump to use the admin generator Analyse what’s needed first A bespoke solution may be more appropriate Misusing the admin generator could cause big problems in the future
  11. How do we decide? Admin Bespoke Normal CRUD operations Public interface to data Non – technical users Sophisticated sorting need to add data and searching of data Trusted site administrators Ownership of all records The admin site can take you a long way…. …. But be careful it doesn’t become a mess.
  12. admin The 10 Commandments
  13. 10 Commandments (1-5) 1. Understand the client’s workflow and customise admin to suit 2. Think about security from the start 3. Look through and understand the cached php files 4. Change table_method to reduce db calls 5. Use bespoke Form class for admin if different
  14. 10 Commandments (6-10) 6. Keep all form form configuration in the Form Class 7. If you need to make changes to multiple admin modules – create a theme. 8. Think about small screens and target browser 9. Create functional tests – guard against regression 10. Maintain good MVC and decoupling practices
  15. What’s the object-oriented way to become wealthy? Inheritance!
  16. John’s Top tips!
  17. 1. The URL Clients don’t like using: application.com/admin.php Option 1: application.com/admin Option 2: admin.application.com
  18. 1. The URL: /admin Modify web/.htaccess RewriteCond %{REQUEST_URI} ^/admin/? RewriteRule ^(.*)$ admin.php [QSA,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteRule ^(.*)$ index.php [QSA,L] Change all your standard routes – routing.yml homepage: url: /admin param: { module: default, action: index } test: url: /admin/test ...
  19. 1. The URL: /admin Change your route collections – routing.yml prefix_path: /admin/module_name Remove script name in urls – settings.yml prod: .settings: no_script_name: true
  20. 1. The URL: admin.app Create a new virtual host in httpd.conf <VirtualHost *:80> ServerName admin.application.com DirectoryIndex admin.php DocumentRoot "/path/to/web/folder" <Directory "/path/to/web/folder"> AllowOverride All Allow from All </Directory> </VirtualHost> Tell symfony not to output admin.php in urls Prod .settings: no_script_name: true
  21. 2. Dynamic MaxPerPage Add a select box within the _list_header.php js onchange submits to action Add an action to set a user attribute public function executeChangeMaxPage(sfWebRequest $request){ $this->getUser()->setAttribute('maxPage', $request->getParameter('maxPage')); $this->redirect($request->getReferer()); }
  22. 2. Dynamic MaxPerPage Override getPagerMaxPerPage() class employeeGeneratorConfiguration extends BaseEmployee { public function getPagerMaxPerPage() { $maxPage = sfContext::getInstance()->getUser() ->getAttribute('maxPage', 10); return $maxPage; } }
  23. 3. Adding relations Fewest clicks for common tasks Relevant data placed together Currently unsupported by the generator Symfony provides functions to help sfForm : : mergeForm() sfForm : : embedForm() sfFormDoctrine : : embedRelation()
  24. Employee has many phones Employee: Phone: columns: columns: id: id: type: integer(4) type: integer(4) primary: true primary: true autoincrement: true autoincrement: true name: number: type: string(255) type: string(255) notnull: true notnull: true relations: employee_id: Phones: type: integer(4) type: many notnull: true class: Phone type: local: id type: enum foreign: employee_id values: [mobile, home, work] onDelete: CASCADE
  25. 3. Adding relations Add ability to edit existing phone numbers from within employee form Embed the ‘Phones’ relation in EmployeeForm::configure class EmployeeForm extends BaseEmployeeForm { public function configure() { $this->embedRelation('Phones'); } }
  26. Hide employee_id in PhoneForm::configure() class PhoneForm extends BasePhoneForm { public function configure() { $this->widgetSchema['employee_id'] = new sfWidgetFormInputHidden(); ... No Delete? No Add?
  27. There’s a symfony plugin for that! Thanks to ahDoctrineEasyEmbeddedRelationsPlugin by Daniel Lohse
  28. 4. Translate admin interface Add chosen culture to settings.yml .all: .settings: i18n: on default_culture: fr ./symfony cc and delete browser cookies
  29. 4. Translate admin interface Create a new startrek catalogue Add vulcan XLIFF files to: apps/admin/il8n/ • startrek.vu.xml • startrek_forms.vu.xml
  30. 4. Translate admin interface <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE xliff PUBLIC "-//XLIFF//DTD XLIFF//EN" "http://www.oasis- open.org/committees/xliff/documents/xliff.dtd" > <xliff version="1.0"> <file original="global" source-language="en" target- language="vu_VU" datatype="plaintext"> <header /> <body> <!-- Actions --> <trans-unit> <source>New</source> startrek.vu.xml <target>Uzh</target> </trans-unit> <trans-unit> <source>Edit</source> <target>Ver-tor</target> </trans-unit> ...
  31. 4. Translate admin interface Tell admin generator to use alternative catalogue generator: class: sfDoctrineGenerator param: i18n_catalogue: startrek Tell the forms as well – sfFormDoctrine::setup() abstract class BaseFormDoctrine extends sfFormDoctrine { public function setup() { $this->widgetSchema->getFormFormatter() ->setTranslationCatalogue('startrek_forms'); } }
  32. 5. Tidy up filters Filters work great – but the default style is a bit off A few CSS tweaks
  33. #sf_admin_container #sf_admin_bar { float:none; margin-left: 0px; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tr { clear: none; border: 1px solid #DDD; padding: 0px; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tr td { height: 50px; vertical-align: middle; border: none; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tbody { clear: none; float: left; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tbody tr { float: left; border-right: none; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tfoot { clear: none; float: right; } #sf_admin_container #sf_admin_bar .sf_admin_filter table tfoot tr { float: right; } Thanks to Sebastien
  34. What is the definition of programmer? Programmers are machines that turn coffee into code.
  35. 6. Timestampable fields Generally don’t need to edit these Simply unset them in form class class NewsForm extends BaseNewsForm { public function configure() { unset($this['created_at'], $this['updated_at']); } }
  36. 6. Timestampable fields What if you still need to see the value?
  37. 6. Timestampable fields Use sfWidgetFormPlain widget http://trac.symfony-project.org/attachment/ticket/7963/sfWidgetPlain.diff public function configure() { $this->setWidget('created_at', new sfWidgetFormPlain(array('value'=>$this->getObject()->created_at))); unset($this->validatorSchema['created_at']); $this->setWidget('updated_at', new sfWidgetFormPlain(array('value'=>$this->getObject()->updated_at))); unset($this->validatorSchema['updated_at']); ... Thanks to Stephen.Ostrow
  38. 7. Pre-filter list
  39. 7. Pre-filter list Add an object action to generator.yml list: object_actions: _edit: ~ viewPhones: { label: Phone numbers, action: viewPhones } Set filter atribute in user session class employeeActions extends autoEmployeeActions { public function executeViewPhones($request){ $this->getUser()->setAttribute( 'phone.filters', array('employee_id' => $request->getParameter('id')), 'admin_module' ); $this->redirect($this->generateUrl('phone')); } }
  40. 8. Row level ownership Only allow owners of objects access Example presumes: sfGuard plugin is installed Objects have a user_id field fk
  41. 8. Row level ownership Secure the list page - moduleActions::buildquery() protected function buildQuery(){ $query = parent::buildQuery(); $query->andWhere( 'user_id = ?', $this->getUser()->getId() ); return $query; } This belongs in the model!!
  42. 8. Row level ownership Secure all other actions - moduleActions::preExecute() public function preExecute(){ if($this->getActionName()!= 'new' && $this->getActionName()!= 'index'){ $this->forward404Unless( $this->getUser()->isOwner($this->getRoute()->getObject()) ); } parent::preExecute(); }
  43. 8. Row level ownership Add a new method to user class - myUser::isOwner() class myUser extends sfGuardSecurityUser { public function isOwner($obj){ if(is_object($obj)){ if($this->getId() == $obj->getUserId()) return true; } return false; } }
  44. 8. Row level ownership What about the user_id field in the form? Don’t want users to change owner Also be careful with injected data
  45. 8. Row level ownership We need to remove the widget - Form::configure() public function configure() { unset($this['user_id']); ... Set the user_id manually – Form::doUpdateObject() public function doUpdateObject($values){ $userId = sfContext::getInstance()->getUser()->getId(); $this->getObject()->setUserId($userId); return parent::doUpdateObject($values); } eatmymonkeydust.com
  46. 9. Custom filters Find out who has a birthday today Add the new filter name to generator.yml filter: display: [ name, birthday_today ] fields: birthday_today: help: Employees who have a birthday today! Based on info from Tomasz Ducin and dlepage
  47. 9. Custom filters Create a new widget in - xxFormFilter::configure() public function configure() { $this->widgetSchema['birthday_today'] = new sfWidgetFormInputCheckbox(); $this->validatorSchema['birthday_today'] = new sfValidatorPass(); } Filter form is now displayed
  48. 9. Custom filters Add a add*ColumnQuery to FormFilter class public function addBirthdayTodayColumnQuery($query,$field,$value) { if($value){ $query->andWhere("SUBSTRING(`birthday`, 6, 5) = SUBSTRING(NOW(), 6, 5)"); } return $query; } Now buy the presents!
  49. Plugins
  50. sfAdminDashPlugin Kevin Bond Joomla style admin Adds a dashboard Configurable admin navigation Replaces the admin css Manually add header component and footer partial to layout
  51. sfAdminThemejRollerPlugin Gerald Estadieu Looks stunning jQuery Theme roller system Popup filters Tabs in edit view Completely new admin theme
  52. Optimist : The glass is half full. Pessimist : The glass is half empty.. Coder: The glass is twice as big as it needs to be
  53. Extending Methods What degree of customisation do you need? Will you need to re-use the functionality?
  54. Extending - CSS Define an alternative CSS generator: class: sfDoctrineGenerator param: model_class: News theme: admin non_verbose_templates: true with_show: false singular: ~ plural: ~ route_prefix: news with_doctrine_route: 1 css: funkystyle
  55. Extending - Override code Override individual templates and actions Quick and easy Can’t be re-used between modules Can become untidy
  56. Extending – Create a theme More work upfront Can be used for multiple modules / projects Much more scope for customising Steep learning curve – PHP in PHP!
  57. Extending – Create a theme Create container folder for new theme mkdir -p data/generator/sfDoctrineModule/newtheme Copy the generator files from sfDoctrine plugin cp -r lib/vendor/symfony/lib/plugins/ → sfDoctrinePlugin/data/generator/sfDoctrineModule/admin/* → data/generator/sfDoctrineModule/newtheme/
  58. Extending – Create a theme Name of theme Parts – Snippets of code included into cache Skeleton – copied to admin module Templates – generated into cache
  59. Extending – Create a theme Change theme name in generator.yml generator: class: sfDoctrineGenerator param: model_class: News theme: newtheme ... Clear cache You’ve made your own theme!
  60. Extending – Admin events admin.pre_execute: Notified before any action is executed. admin.build_criteria: Filters the Criteria used for the list view. admin.save_object: Notified just after an object is saved. admin.delete_object: Notified just before an object will be deleted.
  61. So, what does… …do?
  62. Dashboard Show related models
  63. Object history Delete related objects warning
  64. Future…. What do you want the admin generator to do? What should the scope of the generator be? Better support for embedded forms? More customisable list view (sfGrid)? Fulltext search in the fields? Saving goes back to list view? Dashboard? Nested sets? Ordering? Inherit from multiple themes?
  65. Thanks for listening! Twitter: @jcleveley

×