0

I have an app which allows users to upload a CV when applying for courses. The input form is returned and displayed without an error. When the form is submitted no error is thrown. The uploaded file is in the POST data - as an instance of UploadedFile and cvUpdatedAt field is set. However the column used to store the filename is set to NULL when the record is saved to the DB.

Here is my Entity:

<?php

namespace My\CamsBundle\Entity;

use DateTime;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @ORM\Entity(repositoryClass="My\CamsBundle\Entity\Repository\ApplicationRepository")
 * @ORM\Table(name="application")
 * @Vich\Uploadable
 * @ORM\HasLifecycleCallbacks()
 */
class Application {

  /**
   * @ORM\Id
   * @ORM\Column(type="integer")
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  protected $id;

  /**
   * @Assert\File(
   *     maxSize="2M",
   * )
   * @Vich\UploadableField(mapping="cv_file", fileNameProperty="cvName")
   *
   * This is not a mapped field of entity metadata, just a simple property.
   *
   * @var File $cvFile
   */
  protected $cvFile;

  /**
   * @ORM\Column(type="string", length=255, name="cv_name", nullable=true)
   *
   * @var string $cvName
   */
  protected $cvName;

  /**
   * @ORM\Column(type="datetime")
   *
   * @var \DateTime $cvUpdatedAt
   */
  protected $cvUpdatedAt;


  public function __construct() {  }

  /**
   * Get id
   *
   * @return integer
   */
  public function getId() {
    return $this->id;
  }

  /**
   * If manually uploading a file (i.e. not using Symfony Form) ensure an instance
   * of 'UploadedFile' is injected into this setter to trigger the  update. If this
   * bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
   * must be able to accept an instance of 'File' as the bundle will inject one here
   * during Doctrine hydration.
   *
   * @param File|\Symfony\Component\HttpFoundation\File\UploadedFile $cv
   */
  public function setCvFile(File $cv = null) {
    $this->cvFile = $cv;

    if ($cv) {
      // It is required that at least one field changes if you are using doctrine
      // otherwise the event listeners won't be called and the file is lost
      $this->cvUpdatedAt = new \DateTime('now');
    }
  }

  /**
   * @return File
   */
  public function getCvFile() {
    return $this->cvFile;
  }

  /**
   * @param string $cvName
   */
  public function setCvName($cvName) {
    $this->cvName = $cvName;
  }

  /**
   * @return string
   */
  public function getCvName() {
    return $this->cvName;
  }

  /**
   * Set cvUpdatedAt
   *
   * @param \DateTime $cvUpdatedAt
   * @return Application
   */
  public function setCvUpdatedAt($cvUpdatedAt) {
    $this->cvUpdatedAt = $updatcvUpdatedAt;

    return $this;
  }

  /**
   * Get cvUpdatedAt
   *
   * @return \DateTime 
   */
  public function getCvUpdatedAt() {
    return $this->cvUpdatedAt;
  }

}

Here is the form:

<?php

namespace My\CamsBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class ApplicationType extends AbstractType {

  public function buildForm(FormBuilderInterface $builder, array $options) {
    if (array_key_exists('action', $options)) {
      $builder->setAction($options['action']);
    }

    // See http://lrotherfield.com/blog/symfony2-forms-hidden-entity-type-part-2/
    $builder->add('cvFile', 'vich_file', array(
        'required'      => false,
        'allow_delete'  => true, // not mandatory, default is true
        'download_link' => true, // not mandatory, default is true
        'label'         => 'Upload CV',
      ));

    $builder->add('save', 'submit', array(
      'label' => 'Save',
      'attr' => array(
        'class' => 'btn btn-action'
      )
    ));

    // Basic query builder solution. Not much better than the documentation.
    // http://inchoo.net/dev-talk/symfony2-entity-field-type/
  }

  public function getName() {
    return 'application';
  }

  public function setDefaultOptions(OptionsResolverInterface $resolver) {
    $resolver->setDefaults(array(
      'data_class' => 'My\CamsBundle\Entity\Application',
    ));
  }

}

Here is the config:

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: @MyCamsBundle/Resources/config/config.yml }

framework:
    #esi:             ~
    #translator:      { fallback: "%locale%" }
    secret:          "%secret%"
    router:
        resource: "%kernel.root_dir%/config/routing.yml"
        strict_requirements: ~
    form:            ~
    csrf_protection: ~
    validation:      { enable_annotations: true }
    templating:
        engines: ['twig']
        #assets_version: SomeVersionScheme
    default_locale:  "%locale%"
    trusted_proxies: ~
    session:         ~
    fragments:       ~
    http_method_override: true

# Twig Configuration
twig:
    debug:            "%kernel.debug%"
    strict_variables: "%kernel.debug%"
    form:
        resources:
            - 'VichUploaderBundle:Form:fields.html.twig'


# Doctrine Configuration
doctrine:
    dbal:
        default_connection: default
        connections:
            default:
                driver:   "%database_driver%"
                host:     "%database_host%"
                port:     "%database_port%"
                dbname:   "%database_name%"
                user:     "%database_user%"
                password: "%database_password%"
                charset:  UTF8
            customer:
                driver:   "%database_driver2%"
                host:     "%database_host2%"
                port:     "%database_port2%"
                dbname:   "%database_name2%"
                user:     "%database_user2%"
                password: "%database_password2%"
                charset:  UTF8

    orm:
        auto_generate_proxy_classes: "%kernel.debug%"
        entity_managers:
            default:
                auto_mapping: true
                mappings:
                  MyCamsBundle: ~


vich_uploader:
    db_driver: orm

    mappings:
        cv_file:
            uri_prefix:         /uploads/documents
            upload_destination: %kernel.root_dir%/../web/uploads/documents
            #namer:              vich_uploader.namer_uniqid

            inject_on_load:     false
            delete_on_update:   true
            delete_on_remove:   true

Form template:

{% extends 'MyCamsBundle::layout.html.twig' %}
{% form_theme form 'MyCamsBundle:Form:fields.html.twig' %}

{% block body %}
<div class="main">
    {{ form_errors(form) }}
    <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
            <div class="heading">Application</div>
        </div>
    </div>


    <section id="application-form">
        {{ form_start(form) }}
    <div class="row well demonstrator-experience">
        <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 cv-details">

        <span class="btn btn-action btn-file">
            {{ form_label(form.cvFile) }} {{ form_widget(form.cvFile) }}
        </span>

        </div>

    <div class="row">
        <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 text-right">
        </div>
        {{ form_widget(form.save) }}
    </div>
    {{ form_end(form) }}
    </section>

</div>
{% endblock %}

Controller:

<?php

namespace My\CamsBundle\Controller;

use DateTime;
use Doctrine\ORM\EntityManager;
use Exception;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Response;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Component\HttpFoundation\Request;
use My\CamsBundle\Entity\Application;
use My\CamsBundle\Form\ApplicationType;

/**
 * Apply for a casual position.
 *
 * Class ApplicationController
 * @package My\CamsBundle\Controller
 */
class ApplicationController extends Controller
{

    /**
     * Application form.
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/application/create")
     */
    public function createAction()
    {
        $application = new Application();
        $em = $this->getDoctrine()->getManager();
        $form = $this->createFormModel($application);

        return $this->render('MyCamsBundle:Application:create.html.twig', array(
            'form' => $form->createView(),
        ));
    }

    /**
     * Application save form.
     *
     * @param \Symfony\Component\HttpFoundation\Request $request
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/application/save")
     */
    public function saveAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $raw_application = $request->get('application');
        $request->request->set('application', $raw_application);
        $application = new Application();
        $form = $this->createFormModel($application);
        $form->handleRequest($request);
        $application_id = 0;

        try {
            if ($form->isValid()) {
                $application = $form->getData();

                $em->merge($application);
                $em->flush();
                $application_id = $application->getId();
            } else {
                return $this->render('MyCamsBundle:Application:create.html.twig', array(
                    'form' => $form->createView(),
                ));
            }
        } catch (Exception $e) {
            $request->getSession()->getFlashBag()->add(
                'notice', 'Failed to save changes'
            );
            $logger = $this->get('logger');
            $logger->error($e->getMessage());

            return $this->render('MyCamsBundle:Application:create.html.twig', array(
                'form' => $form->createView(),
            ));
        }

        return $this->redirect($this->generateUrl('my_cams_application_update'));
    }

    /**
     * Application edit form. This is the applicant's dashboard.
     *
     * @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
     *
     * @Route("/application/update")
     */
    public function updateAction()
    {
        $em = $this->getDoctrine()->getManager();

        // etc.
    }

    /**
     * @param $application
     * @return \Symfony\Component\Form\Form
     */
    private function createFormModel($application)
    {
        $form = $this->createForm(new ApplicationType(), $application, array(
                'action' => $this->generateUrl('my_cams_application_save')
            )
        );

        return $form;
    }

}

And Repository:

<?php

namespace My\CamsBundle\Entity\Repository;

use Doctrine\ORM\EntityRepository;

/**
 * ApplicationRepository
 *
 * This class was generated by the Doctrine ORM. Add your own custom
 * repository methods below.
 */
class ApplicationRepository extends EntityRepository
{
}

I'm unsure whether the mapping for the vich_uploader is loading correctly or not. I can't see anything missing in the Entity and form definitions.

chapay
  • 1,315
  • 1
  • 13
  • 20
boombana
  • 11
  • 3
  • 3
    Welcome to Stack Overflow! That looks like more code than strictly needed. Can you reduce the code further until no code can be removed while still running into the problem? (Also, the code snippet markdown is strictly wrong; leave those as code *blocks* only.) – Nathan Tuggy Feb 15 '15 at 02:30
  • What version of vich/uploader-bundle do you use ? I was not able to use the latest version and had to use v0.14.0. This one works fine compared to 1.0.*@dev. Maybe this helps. Add this `"vich/uploader-bundle": "0.14.0"` to your composer.json. – cb0 Mar 15 '15 at 14:21
  • Version 1.0.x-dev of VichUploader. Have moved to VlabsMediaBundle. – boombana Jun 18 '15 at 01:15

1 Answers1

0

Not really sure if this helps but change: $cvUpdatedAt with the $updatedAt. In my case it worked.

Cfun
  • 8,442
  • 4
  • 30
  • 62