Friday, August 28, 2009

symfony admin generator upload reloaded

the good old admin_input_file_tag



When you hear that symfony "admin generator system has improved", it usually means
that they got rid of some useful feature (ie. upload/form helpers in generator.yml, validation in yaml etc :)

To "emulate" the deprecated admin_input_file_tag behaviour without fall back to the sfCompat10Plugin (whose features will be deprecated in the next releases of the framework) I did something like this in lib/form/doctrine/BaseFormDoctrine.class.php:



abstract class BaseFormDoctrine extends sfFormDoctrine
{
protected function _getWebUploadDirPath($fieldName)
{
return '/'. basename(sfConfig::get('sf_upload_dir')) .'/files/'.
$this->getModelName() .'/'. $fieldName .'/';
}

protected function _getUploadDirPath($fieldName)
{
return sfConfig::get('sf_upload_dir') .'/files/'. $this->getModelName() .'/'. $fieldName .'/';
}

/**
* Get generator.yml configuration
*/
protected function _getFieldsForm() {
static $generatorYml = null;
if ($generatorYml == null) {
try {
$configClassName = sfContext::getInstance()->getModuleName() . 'GeneratorConfiguration';
$configuration = new $configClassName();
$generatorYml = $configuration->getFieldsForm();
}
catch (sfException $e) {
return array();
}
}
return $generatorYml;
}

public function setup()
{
parent::setup();

foreach ($this->_getFieldsForm() as $fieldName => $config) {
if (isset($config['ntcType']) and $config['ntcType'] == 'image_upload') {
$getterFunction = 'get'. sfInflector::camelize($fieldName);
$this->validatorSchema[$fieldName] = new sfValidatorFile(array(
'required' => FALSE,
'path' => $this->_getUploadDirPath($fieldName),
'mime_types' => 'web_images',
));
$this->validatorSchema[$fieldName .'_delete'] = new sfValidatorBoolean();
$this->widgetSchema[$fieldName] = new sfWidgetFormInputFileEditable(array(
'label' => sfInflector::humanize($fieldName),
'is_image' => TRUE,
'with_delete' => TRUE,
'delete_label' => 'remove current file',
'edit_mode' => !$this->isNew(),
'template' => '%input% %delete% %delete_label% %file%',
'file_src' => $this->_getWebUploadDirPath($fieldName) .
pathinfo($this->getObject()->$getterFunction(), PATHINFO_FILENAME) .'_thumb.jpg',
));
}
}
}

protected function processUploadedFile($field, $filename = null, $values = null)
{
$generatorYml = $this->_getFieldsForm();
$fileName = parent::processUploadedFile($field, $filename, $values);
/*
* create a thumbnail to show in the form
*/
if (isset($generatorYml[$field]['ntcType']) and
$generatorYml[$field]['ntcType'] == 'image_upload')
{
$dirPath = $this->_getUploadDirPath($field);
if (!empty($fileName)) {
$thumbnail = new sfThumbnail(150, 150);
$thumbnail->loadFile($dirPath . $fileName);
$thumbName = pathinfo($fileName, PATHINFO_FILENAME) . '_thumb.jpg';
$thumbnail->save($dirPath . $thumbName, 'image/jpeg');
}
}
return $fileName;
}
}



Then put in generator.yml of your model:


...

form:
fields:
my_image_field: {ntcType: image_upload}



You have to install sfThumbnailPlugin to make this work (or you just can drop the thumbnail generation code)

This will activate a file upload widget for every field configured with type "image_upload".

Thanks to searbe post for the inspiration.

Tuesday, August 18, 2009

cakephp vs symfony vs django admin site: why i chose to not choose the "best"

Recently I had to choose a framework to implement a web data entry application (dozens of forms and database tables). I looked for a rapid application development environment that does code generation for basic administration tasks (CRUD trough web forms) so I first discarded all the frameworks that does not have such a functionality: codeigniter, kohana, zend framework.

I neither want to use Ruby on Rails because i know PHP pretty well and, having a moderate understanding of python too, I didn't want to learn another language.

My research ended up comparing the code generation features of these three frameworks:

  • django

  • cakephp

  • symfony



I've used cakephp in the past for a couple of small projects, and, imho, it's a nice framework with some limits:


  • cakephp generates code for models, views, controller and templates. merely this code is customizable only modifying the already generated sources, so if you change something in your database you cannot just re-generate the admin interface because this will overwrite all of your customization

  • i don't like cakephp "convention over configuration" philosophy. expecially i don't like the fact that I must fight against the pluralization of model names which are by default inflected in english (that's not my language as you can guess)

  • no filters at all (ie. date range) are produced by the CRUD generator

  • development is very tight to the construction of models. it's not easy to implement something outside the mvc pattern (which is good for somebody but not in my case)



Once stunned by django admin generator I considered to use this amazing framework which has some very good points over symfony (the last framework I tried):


  • django it's very fast

  • the killer feature: django does not have code generation, it simply creates its admin site "on the fly" introspecting the models and the Admin classes. everything is magically reflected on the admin site without manually invoking some nasty cli command every time you change something.

  • django admin site has a nice clean design with calendar javascript widgets for dates and times

  • symfony+doctrine admin generator ships with ugly selects for date widgets and an ugly default admin theme

  • django admin site has a dashboard (a page with a list of all your models and a link to the common crud operations) with a list of the latest actions

  • django has a configurable automatic formsets generation to edit multiple entry of related data (ie. you have a book form and several categories text fields in the same page).

  • symfony comes with a similar functionality (called embedded forms) but they are harder to manage and there's no support at all in the admin generator

  • django admin site CRUD keeps revisions and records actions automatically (with symfony you can use a doctrine/propel behaviour for version history but I never tested it)

  • django syntax for ORM and database querying is just perfect

  • symfony default ORM (propel) has an insane syntax to cope with (Doctrine ORM is simple anyway and fun to code with)

  • django directory structure could not be simpler: you have a directory per module and everything is contained there (usually you have just three files for each module: admin.py, models.py, views.py) apart from templates

  • symfony module generation acts like a bomb dropped on your filesystem. it spreads files everywhere (filters, forms, modules, models, templates, actions, base classes, a dozen of yaml configuration files) and it's not easy, expecially for beginners, to understand what does what and where it is located (the worst thing is that the cli truncates the paths in the console output when the creation process is running, so you have to figure out yourself where everything's gone)

  • i was diffident about symfony YAML and configuration files



Finally, I explain why I ended up using symfony and not django for my project.


  • I'm more skilled with php than with python.

  • A huge limit of the admin site in django is that it does not have a "view" permission on models. You can assign "change", "delete", "create" rights on objects but the django developers think that admin users that can "view" something then automatically have the right to modify it.
    I find this absurd (I also wrote a trac ticket about this).

  • With django I haven't found an easy way to have a "dynamic" owner-like permission (something like row level access with symfony). There are a couple of plugins for this, but I feel they don't really adapt to the scope.

  • Let me say django admin filters are bad for your health. I wonder why an admin filter must be constrained to a multiple choice. What if I want to search with a __custom__ date range or a custom string ? Symfony has these two gadgets in the generator.

  • Once you get used to YAML, symfony generator.yml becomes your closest friend. I like the level of customization that you can reach editing that single file.

  • Symfony cli provides some extras out of the box: db migration (django has an external plugin called evolution), remote deplyment sync with rsync, etc.



My final opinion:

  • cakephp is a good, fast, relative small framework (well i know code igniter is faster and smaller but it does not have code generation). It's fun to work with it and easy to learn. The documentation has reached a good level.

  • django probably will be the best framework in the world, I'll use it when I've mastered python

  • symfony is the most complete php RAD out there that ships with code generation (if you relax this constraint I think that Zend Framework comes first) but it has a steep learning curve

Monday, August 17, 2009

Symfony: jquery calendar widget for doctrine generated filters

Javascript calendar widget for filters



This is a quick way to have a javascript calendar instead of the nasty
3 selects (y/m/d) for every date filter:

1. install sfFormExtraPlugin

2. generate admin backend as usual

3. put the following code in lib/filter/doctrine/BaseFormFilterDoctrine.class.php:


abstract class BaseFormFilterDoctrine extends sfFormFilterDoctrine
{
public function setup()
{
foreach ($this->widgetSchema->getFields() as $name => $widget) {
if ($widget instanceof sfWidgetFormFilterDate) {
$this->setWidget($name, new sfWidgetFormJQueryDate(array(
'image'=>'/images/calendar.gif',
'format' => '%day%/%month%/%year%'
)));
}
}
}
}