Symfony2 file upload step by step

Do you know now what are the ‘important’ function to add to be able to upload the photo and rename it?

See the official documentation on how to do this. There are good working examples for a simple file upload. Also check the doctrine documentation for lifecycle callbacks.

How do you check the extension to see if the upload is possible?

There is some HTML-Form validation in each browser. See this question for the HTML accept="" attribute in input elements. Also in Symfony2 you can specify the MIME-type of an uploaded file using this annotation:

/**
 * @Assert\File(
 *     maxSize = "1024k",
 *     mimeTypes = {"application/pdf", "application/x-pdf"},
 *     mimeTypesMessage = "Please upload a valid PDF"
 * )
 */

Even though you did not want to use any bundles I’ll have to recommend you the KnpDoctrineBehavioursBundle which makes file uploading way easier.


Step-by-step:

Because you read the documentation already I’ll give you a step by step code-example.

First of all you need an entity. Let’s call it Image:

/**
 * Class Image
 *
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks
 */
class Image extends BaseEntity
{

Note the @ORM\HasLifecycleCallbacks annotation. It is very important and you need it later. We create all the basic fields like ID and what not. Also we need a field to store the file path in:

    /**
     * Image path
     *
     * @var string
     *
     * @ORM\Column(type="text", length=255, nullable=false)
     */
    protected $path;
    

And one for the Image itself. Here we also define the Validation for the images. In my example it has to be 5M big and of one of the defined mimeTypes. It should be self-explanatory. Otherwise the official docs help as always.

    /**
     * Image file
     *
     * @var File
     *
     * @Assert\File(
     *     maxSize = "5M",
     *     mimeTypes = {"image/jpeg", "image/gif", "image/png", "image/tiff"},
     *     maxSizeMessage = "The maxmimum allowed file size is 5MB.",
     *     mimeTypesMessage = "Only the filetypes image are allowed."
     * )
     */
    protected $file;

Add all the Getters & Setters and update your database schema with this command:

php app/console doctrine:schema:update --force

Next we need the lifecycles. They are methods in the Entity that are called on certain events. For example the @ORM\PreUpdate() annotation before a method says that this method is being called right before the entity gets updated.

/**
 * Called before saving the entity
 * 
 * @ORM\PrePersist()
 * @ORM\PreUpdate()
 */
public function preUpload()
{   
    if (null !== $this->file) {
        // do whatever you want to generate a unique name
        $filename = sha1(uniqid(mt_rand(), true));
        $this->path = $filename.'.'.$this->file->guessExtension();
    }
}

Before the entity gets stored or updated this method is called. You can use it to e.g. generate a unique file name.

/**
 * Called before entity removal
 *
 * @ORM\PreRemove()
 */
public function removeUpload()
{
    if ($file = $this->getAbsolutePath()) {
        unlink($file); 
    }
}

Called before the entity gets removed. This gives you time to delete the image from your folders or log a message if you want to.

/**
 * Called after entity persistence
 *
 * @ORM\PostPersist()
 * @ORM\PostUpdate()
 */
public function upload()
{
    // The file property can be empty if the field is not required
    if (null === $this->file) {
        return;
    }

    // Use the original file name here but you should
    // sanitize it at least to avoid any security issues

    // move takes the target directory and then the
    // target filename to move to
    $this->file->move(
        $this->getUploadRootDir(),
        $this->path
    );

    // Set the path property to the filename where you've saved the file
    //$this->path = $this->file->getClientOriginalName();

    // Clean up the file property as you won't need it anymore
    $this->file = null;
}

This is the important part where your file is actually moved to the right directory. Note that I have used some additional methods. You can all get them from the official docs.

Next thing you need is a form. The form class itself is very simple. Just make sure you set the default data_class like this:

public function setDefaultOptions(OptionsResolverInterface $resolver)
{
    $resolver->setDefaults(
        array(
            'data_class' => 'FSchubert\SiyabongaBundle\Entity\Image',
       )
    );
}

A file upload field can be created very easily in the buildForm() method:

$builder->add('file', 'file');

The methods for your Controller are a little long for just pasting them here and IMHO it’s not part of answering your question. There are countless examples out there for writing a proper Controller Action for your purpose.


More things you have to keep in mind:

  • You need to give your app writing permissions to the folders you upload the files to. Although it seems obvious it can be annoying if you have multiple servers you run the application on.
  • There is an Image Constraint for your entity as well. You can find it here. But since you were talking about a file upload I used the File Constraint instead.
  • As I mentioned in the top of this post, there are many Bundles that handle all these things for you. Check them out if you want an easy life.

Edit:

  • Changed from DoctrineExtensionsBundle to DoctrineBehaviours since development on the old one stopped in favour of the DoctrineBehaviours bundle.

Leave a Comment