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)
4. 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
5. 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!
6. 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
7. 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
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
11. 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
12. 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.
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
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
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()
26. 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');
}
}
27. Hide employee_id in PhoneForm::configure()
class PhoneForm extends BasePhoneForm
{
public function configure()
{
$this->widgetSchema['employee_id'] =
new sfWidgetFormInputHidden();
...
No Delete?
No Add?
28. There’s a symfony plugin for that!
Thanks to ahDoctrineEasyEmbeddedRelationsPlugin by
Daniel Lohse
29. 4. Translate admin interface
Add chosen culture to settings.yml
.all:
.settings:
i18n: on
default_culture: fr
./symfony cc and delete browser cookies
30. 4. Translate admin interface
Create a new startrek catalogue
Add vulcan XLIFF files to:
apps/admin/il8n/
• startrek.vu.xml
• startrek_forms.vu.xml
32. 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');
}
}
33. 5. Tidy up filters
Filters work great – but the default style is a bit off
A few CSS tweaks
35. What is the definition 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']);
}
}
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
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 level ownership
Only allow owners of objects access
Example presumes:
sfGuard plugin is installed
Objects have a user_id field fk
42. 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!!
43. 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();
}
44. 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;
}
}
45. 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
46. 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
47. 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
48. 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
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!
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 : The glass is half full.
Pessimist : The glass is half empty..
Coder: The glass is twice as big as it needs
to be
56. Extending - Override code
Override individual templates and actions
Quick and easy
Can’t be re-used between modules
Can become untidy
57. 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!
58. 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/
59. Extending – Create a theme
Name of theme
Parts – Snippets of code
included into cache
Skeleton – copied to admin
module
Templates – generated into
cache
60. 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!
61. 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.
65. 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?