2

I am New to Symfony 3 and I have an Issue when I try to activate my account. I extend my User Entity from FOSUserBundle. I have activated the confirmation system of FOS_User. Furthermore, when a user register into my app, he must upload a file. To do this, I created a FileUploader service and a ImageUploadListener listener. The problem is when I click on my activation link from my gmail's email, I get the following error:

Uncaught PHP Exception Symfony\Component\Debug\Exception\ContextErrorException: "Notice: Undefined variable: fileName" at /home/clement/Rendu/tech-web/src/UserBundle/EventListener/ImageUploadListener.php line 49

Could you help me ? Thank you in advance from a french dev with a big headache !

PS: I followed this tutorial to implement my file upload functionality.

User.php

namespace UserBundle\Entity;

use FOS\UserBundle\Model\User as BaseUser;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * @ORM\Entity
 * @ORM\Table(name="fos_user")
 */
class User extends BaseUser
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;

    /**
     * @ORM\Column(type="string", length=255)
     *
     * @Assert\NotBlank(message="Please enter your phone.", groups={"Registration", "Profile"})
     * @Assert\Length(
     *     min=3,
     *     max=255,
     *     minMessage="The phone is too short.",
     *     maxMessage="The phone is too long.",
     *     groups={"Registration", "Profile"}
     * )
     */
    protected $phone;

    /**
     * @ORM\Column(type="string")
     *
     * @Assert\NotBlank(message="Please, upload a file.")
     * @Assert\File(mimeTypes={ "application/pdf", "image/jpeg" })
     */
    private $image;

    public function __construct()
    {
        parent::__construct();
        // your own logic
        $this->roles = array('ROLE_USER');
    }

    public function getPhone() {
        return $this->phone;
    }

    public function setPhone($phone) {
        $this->phone = $phone;  
    }


    public function setImage($image)
    {
        $this->image = $image;
        return $this;
    }

    public function getImage()
    {
        return $this->image;
    }
}

RegistrationType.php

namespace UserBundle\Form;

use UserBundle\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;

class RegistrationType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('phone')
            ->add('image', FileType::class);
    }

    public function getParent()
    {
        return 'FOS\UserBundle\Form\Type\RegistrationFormType';

        // Or for Symfony < 2.8
        // return 'fos_user_registration';
    }

    public function getBlockPrefix()
    {
        return 'app_user_registration';
    }

    // For Symfony 2.x
    public function getName()
    {
        return $this->getBlockPrefix();
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => User::class,
        ));
    }
}

FileUploader.php

namespace UserBundle\Service;

use Symfony\Component\HttpFoundation\File\UploadedFile;

class FileUploader
{
    private $targetDir;
    public function __construct($targetDir)
    {
        $this->targetDir = $targetDir;
    }

    public function upload(UploadedFile $file)
    {

        $fileName = md5(uniqid()).'.'.$file->guessExtension();
        $file->move($this->getTargetDir(), $fileName);
        return $fileName;
    }

    public function getTargetDir()
    {
        return $this->targetDir;
    }
}

ImageUploadListener

namespace UserBundle\EventListener;

use Symfony\Component\HttpFoundation\File\UploadedFile;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use UserBundle\Entity\User;
use UserBundle\Service\FileUploader;
use Symfony\Component\HttpFoundation\File\File;

class ImageUploadListener
{
    private $uploader;
    private $fileName;

    public function __construct(FileUploader $uploader)
    {
        $this->uploader = $uploader;
    }

    public function prePersist(LifecycleEventArgs $args)
    {
        $entity = $args->getEntity();
        $this->uploadFile($entity);
    }

    public function preUpdate(PreUpdateEventArgs $args)
    {
        $entity = $args->getEntity();
        $this->uploadFile($entity);
    }

    private function uploadFile($entity)
    {
        // upload only works for User entities
        if (!$entity instanceof User) {
            return;
        }

        $file = $entity->getImage();

        // only upload new files
        if ($file instanceof UploadedFile) {
            $fileName = $this->uploader->upload($file);
        }

        $entity->setImage($fileName);
    }
}

services.yml

parameters:
    #parameter_name: value
services:
    _defaults:
        autowire: true
        autoconfigure: true
        public: false
    AppBundle\:
        resource: '../../src/AppBundle/*'
        exclude: '../../src/AppBundle/{Entity,Repository,Tests}'
    AppBundle\Controller\:
        resource: '../../src/AppBundle/Controller'
        public: true
        tags: ['controller.service_arguments']
    app.form.registration:
        class: UserBundle\Form\RegistrationType
        tags:
            - { name: form.type, alias: app_user_registration }           
    UserBundle\Service\FileUploader:
        arguments:
            $targetDir: '%images_directory%'             
    UserBundle\EventListener\ImageUploadListener:
        tags:
            - { name: doctrine.event_listener, event: prePersist }
            - { name: doctrine.event_listener, event: preUpdate }

config.yml

fos_user:
    db_driver: orm
    firewall_name: main
    user_class: UserBundle\Entity\User
    service:
        mailer: fos_user.mailer.twig_swift
    from_email:
        address: "%mailer_user%"
        sender_name: "%mailer_user%"
    registration:
        form:
            type: UserBundle\Form\RegistrationType
        confirmation:
            enabled: true
            template:   '@FOSUser/Registration/email.txt.twig'
            from_email:
                address: "%mailer_user%"
                sender_name: "%mailer_user%"
    profile:
        form:
            type: UserBundle\Form\EditType
Fabien Salles
  • 1,101
  • 15
  • 24

1 Answers1

1

Your event is launched each time you save an user. When you activate the user account you update the user with doctrine in the database so the preUpdate event is call.

To avoid the notice you should do this :

// only upload new files
if ($file instanceof UploadedFile) {
    $fileName = $this->uploader->upload($file);
    $entity->setImage($fileName); 
}

And to avoid to call each time your uploader you have to check if the file is already upload or override the register action to upload it instead of use a doctrine event to do this

Fabien Salles
  • 1,101
  • 15
  • 24
  • Thanks a lot dear confrère :-) And it works ! However, I'm not sure to fully understand the rest of your answer. Do yo think I follow a wrong pattern to do what I want ? How can I upgrade my code ? – Clément MARTZLOFF Oct 24 '17 at 15:21
  • Use an event can be a good choice because the registration of a user can be separate to the uploading of an image but I try to avoid as much as possible default symfony/doctrine events because there are `generic` events called all the times. Each time you will update an entity your preUpdate will be called... So if you want to upgrade your code you can dispatch a [custom event](https://stackoverflow.com/questions/11661057/how-to-create-custom-event-in-symfony2) in your register action – Fabien Salles Oct 24 '17 at 15:38
  • Thank you very much – Clément MARTZLOFF Oct 24 '17 at 16:14