Working with the admin
      generator
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)
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
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!
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
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
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
Your thoughts?




PHP!               YAML!
Is it the right tool?



               …maybe.
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
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.
admin
The 10 Commandments
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
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
What’s the object-oriented way
    to become wealthy?
           Inheritance!
John’s Top tips!
1. The URL
  Clients don’t like using:
application.com/admin.php

         Option 1:
  application.com/admin

        Option 2:
  admin.application.com
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
      ...
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
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
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());
}
2. Dynamic MaxPerPage

    Override getPagerMaxPerPage()

class employeeGeneratorConfiguration extends BaseEmployee
{
  public function getPagerMaxPerPage()
  {
    $maxPage = sfContext::getInstance()->getUser()
            ->getAttribute('maxPage', 10);
    return $maxPage;
  }
}
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()
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
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');
     }
   }
Hide employee_id in PhoneForm::configure()
class PhoneForm extends BasePhoneForm
{
  public function configure()
  {
    $this->widgetSchema['employee_id'] =
      new sfWidgetFormInputHidden();
  ...




                                      No Delete?
                                      No Add?
There’s a symfony plugin for that!
 Thanks to ahDoctrineEasyEmbeddedRelationsPlugin by
 Daniel Lohse
4. Translate admin interface
  Add chosen culture to settings.yml
.all:
  .settings:
    i18n: on
    default_culture: fr




  ./symfony cc and delete browser cookies
4. Translate admin interface
Create a new startrek catalogue

Add vulcan XLIFF files to:
   apps/admin/il8n/
    • startrek.vu.xml
    • startrek_forms.vu.xml
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>
...
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');
  }
}
5. Tidy up filters
Filters work great – but the default style is a bit off




A few CSS tweaks
#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
What is the definition of
     programmer?

Programmers are machines that turn
        coffee into code.
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']);
  }
}
6. Timestampable fields
What if you still need to see the value?
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
7. Pre-filter list
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'));
    }
}
8. Row level ownership
Only allow owners of objects access
Example presumes:
  sfGuard plugin is installed
  Objects have a user_id field fk
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!!
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();
}
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;
    }
}
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
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
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
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
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!
Plugins
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
sfAdminThemejRollerPlugin
       Gerald Estadieu
                 Looks stunning
                 jQuery
                 Theme roller system
                 Popup filters
                 Tabs in edit view

                 Completely new admin
                 theme
Optimist : The glass is half full.
Pessimist : The glass is half empty..
  Coder: The glass is twice as big as it needs
                     to be
Extending Methods

What degree of customisation do you need?
Will you need to re-use the functionality?
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
Extending - Override code
Override individual templates and actions
Quick and easy
Can’t be re-used between modules
Can become untidy
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!
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/
Extending – Create a theme
      Name of theme

             Parts – Snippets of code
             included into cache

             Skeleton – copied to admin
             module

                Templates – generated into
                cache
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!
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.
So, what does…


                 …do?
Dashboard




Show
related
models
Object history



Delete related
objects
warning
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?
Thanks for listening!
   Twitter: @jcleveley

Working With The Symfony Admin Generator

  • 1.
    Working with theadmin generator
  • 2.
    My background John Cleveley Trainedas 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)
  • 4.
    Objectives Provide information beyondthe 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
  • 5.
    It’s super awesome. Saveshuge 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!
  • 6.
    What’s new since1.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
  • 7.
    New PHP configurationfile 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
  • 8.
    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
  • 9.
  • 10.
    Is it theright tool? …maybe.
  • 11.
    Think requirements! Don’t jumpto 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
  • 12.
    How do wedecide? 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.
  • 13.
  • 14.
    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
  • 15.
    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
  • 16.
    What’s the object-orientedway to become wealthy? Inheritance!
  • 17.
  • 18.
    1. The URL Clients don’t like using: application.com/admin.php Option 1: application.com/admin Option 2: admin.application.com
  • 19.
    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 ...
  • 20.
    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
  • 21.
    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
  • 22.
    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()); }
  • 23.
    2. Dynamic MaxPerPage Override getPagerMaxPerPage() class employeeGeneratorConfiguration extends BaseEmployee { public function getPagerMaxPerPage() { $maxPage = sfContext::getInstance()->getUser() ->getAttribute('maxPage', 10); return $maxPage; } }
  • 24.
    3. Adding relations Fewestclicks for common tasks Relevant data placed together Currently unsupported by the generator Symfony provides functions to help sfForm : : mergeForm() sfForm : : embedForm() sfFormDoctrine : : embedRelation()
  • 25.
    Employee has manyphones 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
  • 26.
    3. Adding relations Addability 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'); } }
  • 27.
    Hide employee_id inPhoneForm::configure() class PhoneForm extends BasePhoneForm { public function configure() { $this->widgetSchema['employee_id'] = new sfWidgetFormInputHidden(); ... No Delete? No Add?
  • 28.
    There’s a symfonyplugin for that! Thanks to ahDoctrineEasyEmbeddedRelationsPlugin by Daniel Lohse
  • 29.
    4. Translate admininterface Add chosen culture to settings.yml .all: .settings: i18n: on default_culture: fr ./symfony cc and delete browser cookies
  • 30.
    4. Translate admininterface Create a new startrek catalogue Add vulcan XLIFF files to: apps/admin/il8n/ • startrek.vu.xml • startrek_forms.vu.xml
  • 31.
    4. Translate admininterface <?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> ...
  • 32.
    4. Translate admininterface 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'); } }
  • 33.
    5. Tidy upfilters Filters work great – but the default style is a bit off A few CSS tweaks
  • 34.
    #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
  • 35.
    What is thedefinition of programmer? Programmers are machines that turn coffee into code.
  • 36.
    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']); } }
  • 37.
    6. Timestampable fields Whatif you still need to see the value?
  • 38.
    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
  • 39.
  • 40.
    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')); } }
  • 41.
    8. Row levelownership Only allow owners of objects access Example presumes: sfGuard plugin is installed Objects have a user_id field fk
  • 42.
    8. Row levelownership 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!!
  • 43.
    8. Row levelownership 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(); }
  • 44.
    8. Row levelownership 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; } }
  • 45.
    8. Row levelownership What about the user_id field in the form? Don’t want users to change owner Also be careful with injected data
  • 46.
    8. Row levelownership 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
  • 47.
    9. Custom filters Findout 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
  • 48.
    9. Custom filters Createa 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
  • 49.
    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!
  • 50.
  • 51.
    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
  • 52.
    sfAdminThemejRollerPlugin Gerald Estadieu Looks stunning jQuery Theme roller system Popup filters Tabs in edit view Completely new admin theme
  • 53.
    Optimist : Theglass is half full. Pessimist : The glass is half empty.. Coder: The glass is twice as big as it needs to be
  • 54.
    Extending Methods What degreeof customisation do you need? Will you need to re-use the functionality?
  • 55.
    Extending - CSS Definean 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
  • 56.
    Extending - Overridecode Override individual templates and actions Quick and easy Can’t be re-used between modules Can become untidy
  • 57.
    Extending – Createa theme More work upfront Can be used for multiple modules / projects Much more scope for customising Steep learning curve – PHP in PHP!
  • 58.
    Extending – Createa 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/
  • 59.
    Extending – Createa theme Name of theme Parts – Snippets of code included into cache Skeleton – copied to admin module Templates – generated into cache
  • 60.
    Extending – Createa theme Change theme name in generator.yml generator: class: sfDoctrineGenerator param: model_class: News theme: newtheme ... Clear cache You’ve made your own theme!
  • 61.
    Extending – Adminevents 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.
  • 62.
  • 63.
  • 64.
  • 65.
    Future…. What do youwant 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?
  • 66.
    Thanks for listening! Twitter: @jcleveley