I’m still very new to Symfony 1.2 and its form framework. Reading through documentation, discussion forums and blog posts, I couldn’t find a tutorial which had everything I needed in an image upload. Having finished an image upload for the first time, I decided to write such a tutorial.
This tutorial requires Symfony 1.2, the Doctrine ORM plugin and the PHP GD library. You should already have all of these elements installed, and be familiar with them.
We’ll start at the beginning. I’m going to make an uploader for a background image to attach to a Website model. Make sure you have a field in your model to store the image file name:
Website:
columns:
background_image: string(255)
And rebuild your model:
symfony doctrine:build-all-reload
Now open the form class generated from your schema.
You’re going to add a widget and a validator for the image upload. The widget will also allow the user to view the image after it has been uploaded, and delete it if necessary. The validator will make sure the image upload is not required (it is required by default). There is also a second validator, sfValidatorPass, because the widget we are using adds a checkbox widget giving the user the option to delete the file.
You’ll also override the doSave() method and updateObject() methods. The doSave() method will handle the upload (if there is one), create a new file name for the image, and save the image both to the file system and the background_image field automatically. Since the background_image field is automatically set with the full path of the file, you will use updateObject() to remove the path and just store the file name. If you move your project to another system, the file path will change and so we want to store just the file name.
Also in doSave(), you will handle the case where the user has checked off the “delete image” checkbox, which is automatically named background_image_delete.
Here is the code so far under lib/form/doctrine/WebsiteForm.class.php:
class WebsiteForm extends BaseWebsiteForm
{
public function configure()
{
$this->widgetSchema['background_image'] = new sfWidgetFormInputFileEditable(array(
'file_src' => '/'.basename(sfConfig::get('sf_upload_dir')).'/'.$this->getObject()->getBackgroundImage(),
'is_image' => true,
'edit_mode' => strlen($this->getObject()->getBackgroundImage()) > 0,
'template' => '
<div>%file%
%input%
%delete% %delete_label%</div>
'
));
$this->validatorSchema['background_image'] = new sfValidatorFile(array(
'required' => false,
'mime_types' => 'web_images'
));
$this->validatorSchema['background_image_delete'] = new sfValidatorPass();
}
protected function doSave ( $con = null )
{
$upload = $this->getValue('background_image');
if ( $upload )
{
$filename = sha1($upload->getOriginalName().microtime().rand()).$upload->getExtension($upload->getOriginalExtension());
$filepath = sfConfig::get('sf_upload_dir').'/'.$filename;
$oldfilepath = sfConfig::get('sf_upload_dir').'/'.$this->getObject()->getBackgroundImage();
if ( file_exists($oldfilepath) )
{
unlink($oldfilepath);
}
$upload->save($filepath);
}
$delete = $this->getValue('background_image_delete');
if ( $delete )
{
$filename = $this->getObject()->getBackgroundImage();
$filepath = sfConfig::get('sf_upload_dir').'/'.$filename;
@unlink($filepath);
$this->getObject()->setBackgroundImage(null);
}
return parent::doSave($con);
}
public function updateObject($values = null)
{
$object = parent::updateObject($values);
$object->setBackgroundImage(str_replace(sfConfig::get('sf_upload_dir').'/', '', $object->getBackgroundImage()));
return $object;
}
}
This should work well for the most part. But what if the image is 2000 pixels in width? It will be very disruptive to display such a large image.
That’s where sfThumbnailPlugin comes in.
Install the plugin if you haven’t already done so:
$ symfony plugin:install sfThumbnailPlugin $ php symfony cc
Now create a new directory to store your thumbnails:
$ mkdir /web/uploads/thumbnails
You’ll probably want to use this directory in other modules or applications in your project. Add a new setting called sf_thumbnail_dir in your project configuration:
config/ProjectConfiguration.class.php
...
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
...
sfConfig::set('sf_thumbnail_dir', sfConfig::get('sf_upload_dir').'/thumbnails');
...
}
}
Now all that’s left to do is create and store the thumbnail from the doSave() method of your form class, and make sure that your form displays the thumbnail and not the source image in your configure() method.
Modify the widget settings so that it displays the thumbnail. Note that I’ve …’d most of the parts that don’t change:
lib/form/doctrine/WebsiteForm.class.php
class WebsiteForm extends BaseWebsiteForm
{
public function configure()
{
...
$this->widgetSchema['background_image'] = new sfWidgetFormInputFileEditable(array(
'file_src' => '/'.basename(sfConfig::get('sf_upload_dir')).'/'.basename(sfConfig::get('sf_thumbnail_dir')).'/'.$this->getObject()->getBackgroundImage(),
'is_image' => true,
'edit_mode' => strlen($this->getObject()->getBackgroundImage()) > 0,
'template' => '
<div>%file%
%input%
%delete% %delete_label%</div>
'
));
...
}
protected function doSave ( $con = null )
{
$upload = $this->getValue('background_image');
if ( $upload )
{
...
$thumbnailpath = sfConfig::get('sf_thumbnail_dir').'/'.$filename;
...
$oldthumbnailpath = sfConfig::get('sf_thumbnail_dir').'/'.$this->getObject()->getBackgroundImage();
if ( file_exists($oldthumbnailpath) )
{
unlink($oldthumbnailpath);
}
...
$thumbnail = new sfThumbnail(150, 150, true, true, 75, 'sfGDAdapter');
$thumbnail->loadFile($filepath);
$thumbnail->save($thumbnailpath);
}
$delete = $this->getValue('background_image_delete');
if ( $delete )
{
...
$thumbnailpath = sfConfig::get('sf_thumbnail_dir').'/'.$filename;
@unlink($thumbnailpath);
...
}
}
...
}
And that’s all there is to it. Note that there is no template file here because the form framework handles the view of the form inputs.
Questions? Complaints? Leave a comment.
Here are some related resources:
Here are some other posts you might be interested in:

14 Comments
Thanks for the tutorial, it saved my time.
To avoid a conflicts in file names I would change a function that generates file names to something similar to:
sha1($upload->getOriginalName() . microtime() . rand())
Thanks santini. This is definitely a good idea and something I hadn’t considered. Which is strange because I get people uploading images with the same file name _all_ the _time_!
They will sooner or later figure out that something is wrong.
Probably sooner than you think
.
Try to check in the database identical sha1 hashes and you’ll see the scale of the problem. Maybe you got luck so far…
Wish you luck
hello, i have a question:
what if the form is embedded in another one?
form A embeds an arbitrary number of forms containing images, but in this way the object is saved by the A form method saveembeddedforms,with the call of ->getObject()->save, so bypassing the form doSave Method.
how could you manage this?
editing saveembeddedforms?
Not sure how to answer this one. I posted this tutorial right after figuring out how to do the full image upload, so I’m just getting started myself. I also haven’t had any need to embed or merge forms that have file upload.
Can anyone else answer this question?
Hey there,
I’ve been working with Symfony since version 1.1 and immediately jumped ship to 1.2 when it came out. It’s now much easier to handle forms that upload files. You don’t actually have to code all that you’ve done so far. Do have a look at sfFormDoctrine.class.php in your Symfony installation. Generating your filename and storing the file; as well as deleting when the user activates the checkbox!
Also, try to store your default values in app.yml where it’s easy to modify, you can use PHP code in your yaml files (have a look at the Jobeet fixture files for how to handle this).
And thirdly: for thumbnail management, use a method in your form that’s automatically called when it updates the respective field: updateBackgroundColumn(), don’t use updateObject() or the like, please.
Cheers, Annis
Hey Annis, I’m starting with symfony 1.2 , I’ve been working with symfony since version 0.9 I think. I find new symfony forms very confusing when you want to customize it., right now I want to use the thumbnail plugin to resize some images, it was very easy back in symfony 1.0. You say we only need to create a an update method for the respective field, I wonder if you have some example code it would come in really handy for me.
Thanks in advance
Carlos Mafla
I found a nice solution here for image resizing using a custom validator and sfThumbnailPlugin http://forum.symfony-project.org/index.php/m/84735/
Hope it helps someone else
Cheers
I have problem i am struggling with for some time now:
Is there a way to save file with filename same as primary key of inserted record?
For example such method works only for updates couse primary id already exists:
public function generateImageFilename(sfValidatedFile $file)
{
return $this->getId().$file->getExtension($file->getOriginalExtension());
}
Do u have any solution for situation when new record is being saved?
I haven’t tried this out myself, but here are my first thoughts.
In order to use the primary key in your file name, you have to save the object before executing the file upload so that the primary key is generated.
In my post above, for the function doSave(), take the parent call “parent::doSave($con)” and put it at the top of the function:
$returnVal = parent::doSave($con);
Then at the bottom of the function:
return $returnVal;
This should work in both cases whether you are updating or saving a new data object.
Your solution indeed works but creates new issues:
First thing is how to prevent from saving file during $returnVal = parent::doSave($con);?
Becouse i save file after it doing [...] $upload->save($filepath); [...] and after that i have 2 files created in directory.
Second matter is that i still need to save filename in database couse i need to know extension of file even when i know that name is same as primary id. Do do that i need to make something like update on newly created record…
thats how i see it
Thank you for this tutorial!
i got this error , any idea ?
Fatal error: Call to a member function beginTransaction() on a non-object in F:\lavoro\WEB\hotel\lib\vendor\symfony\lib\plugins\sfDoctrinePlugin\lib\form\sfFormDoctrine.class.php on line 177
solved , that was my mistake, this script is very good tnx.
The problem now is the file name, it’s the coded one…