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.