SRIORestUploadBundle

Handle multiple upload ways on your Symfony2 REST API

View on GitHub

The SRIORestUploadBundle provide you a simple ways to handle uploads on the server side. The main idea is that an upload is always an form (at least the name of the file) and a binary, and can be submitted by different ways. Currently, it supports the simple, multipart and resumable ways.

Installation

  1. Add SRIORestUploadBundle in your dependencies

    {
        "require": {
            "srio/rest-upload-bundle": "dev-master"
        }
    }

    Now tell composer to download the bundle by running the command:

    $ composer update srio/rest-upload-bundle
  1. Enable the bundle in the kernel

    // app/AppKernel.php
    public function registerBundles()
    {
        $bundles = array(
            // ...
            new SRIO\RestUploadBundle\SRIORestUploadBundle(),
        );
    }
  1. Create your ResumableUploadSession entity

    The entity will contains the resumable upload sessions and is required if you want the resumable way of upload to work. Note that if you don't want, you can skip that step.

    <?php
    namespace My\Bundle\Entity;
    
    use SRIO\RestUploadBundle\Entity\ResumableUploadSession as BaseResumableUploadSession;
    use Doctrine\ORM\Mapping as ORM;
    
    /**
     * @ORM\Entity
     * @ORM\Table(name="resumable_upload_session")
     */
    class ResumableUploadSession extends BaseResumableUploadSession
    {
        /**
         * @ORM\Id
         * @ORM\Column(type="integer")
         * @ORM\GeneratedValue(strategy="AUTO")
         */
        public $id;
    }

    Copy this entity in your bundle and update the schema.

  2. Configure the SRIORestUploadBundle

    Copy and change to your needs the configuration reference at the end of your app/config/config.yml file.

  3. Create a media form (or any name) which contains the fields you want to link to the binary.

    This form must be linked with an object that implements UploadableFileInterface.

    <?php
    namespace My\Bundle\Form\Type;
    
    use Symfony\Component\Form\AbstractType;
    use Symfony\Component\Form\FormBuilderInterface;
    use Symfony\Component\OptionsResolver\OptionsResolverInterface;
    use Symfony\Component\Validator\Constraints\NotBlank;
    
    class MediaFormType extends AbstractType
    {
        /**
         * {@inheritdoc}
         */
        public function buildForm(FormBuilderInterface $builder, array $options)
        {
            $builder
                ->add('name', 'text', array(
                    'required' => true,
                    'constraints' => array(new NotBlank())
                ))
            ;
        }
    
        /**
         * {@inheritdoc}
         */
        public function setDefaultOptions(OptionsResolverInterface $resolver)
        {
            $resolver->setDefaults(array(
                'csrf_protection'   => false,
                'data_class' => 'My\Bundle\Entity\Media'
            ));
        }
    
        public function getName ()
        {
            return null;
        }
    }
  4. Create your upload action in a controller

    The basics of SRIORestUploadBundle is that everything is handled by the upload manager, a service named srio_rest_upload.upload_manager.

    // src/My/Bundle/MyBundle/Controller/YourController.php
    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\HttpFoundation\Response;
    use Symfony\Component\HttpFoundation\JsonResponse;
    use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
    
    use My\Bundle\Form\Type\MediaFormType;
    
    class MyController extends Controller
    {
        // ...
        public function uploadMediaAction (Request $request)
        {
            $form = $this->createForm(new MediaFormType());
            $uploadManager = $this->get('srio_rest_upload.upload_manager');
            $result = $uploadManager->handleRequest($form, $request);
    
            if ($result instanceof Response) {
                return $result;
            } else if ($form->isValid()) {
                $media = $form->getData();
    
                // Some media-specific operations like user association
                // $media->setUser($this->getUser());
    
                $em = $this->getDoctrine()->getManager();
                $em->persist($media);
                $em->flush();
    
                return new JsonResponse($media);
            } else {
                return new NotAcceptableHttpException();
            }
        }
    }
    
  5. Let's upload files with available upload ways !

Supported upload ways

The SRIORestUploadBundle supports three ways of file upload.

  1. The simple way: send binary data to an URL and use query parameters to submit additional data.
  2. The multipart way: send both JSON and binary data using the multipart Content-Type.
  3. The resumable way: start a resumable upload session by sending JSON data and then send file entirely or by chunks. It allow to restart a failed upload where it stops.

Configuration reference

# app/config/config.yml
# RestUploadBundle configuration
srio_rest_upload:
    # The directory where files will be uploaded
    upload_dir: %kernel.root_dir%/../web/uploads

    # If you want to use the resumable upload way, you must set
    # the class name of your entity which store the upload sessions.
    resumable_entity: My\Bundle\Entity\ResumableUploadSession

    # A key map of the parameter and the effective parameter name in queries
    parameters:
        # Parameter the define the upload way, internally the provider selector
        uploadType: uploadType

Testing

In order to launch tests of SRIORestUploadBundle which are based on PHPUnit you have to:

  • Clone the GitHub repository and cd into
  • Install vendors

    $ composer install
  • Create database, allow access to a user, and set configuration in Tests/Fixtures/App/app/config/parameters.yml file
  • Create the database schema for the test environment

    $ php Tests/Fixtures/App/app/console doctrine:schema:update --force --env=test
  • Run PHPUnit

    $ phpunit

Custom upload processor

You can easily create your custom upload providers (and feel free to PR them on GitHub) by creating a tagged service, with the rest_upload.processor tag

<parameters>
    <parameter key="acme.my.processor.class">Acme\AcmeBundle\Upload\Processor\MyUploadProcessor</parameter>
</parameters>

<services>
    <service id="acme.my.processor" class="%acme.my.processor.class%">
        <argument type="service" id="doctrine.orm.entity_manager" />
        <tag name="rest_upload.processor" uploadType="acme" />
    </service>
</services>

Note the uploadType attribute that define the unique name of the upload way, set in the uploadType query parameters.

Your MyUploadProcessor class should then implements the ProcessorInterface or extends the AbstractUploadProcessor