Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Serialization of 'Symfony\Component\HttpFoundation\File\File' is not allowed, Symfony4

I've added an avatar image to my User class. When I wanted to render my edit form, I got this error

Serialization of 'Symfony\Component\HttpFoundation\File\File' is not allowed

I tried to solve the problem by implementing \Serializable in my User class according to Symfony Official Documentation. But when I implemented that,It redirected to login page and the Authentication turned to anon. and by logging in again, it redirected to login page again and stay anon. too.

I should mention that I have set some Authorizations. It will redirect you to the log in page if you are "anon." and want to access some protected routes.

Here is my UserEntity, User.php:

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;


/**
 * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
 * @ORM\Table(name="user")
 * @UniqueEntity(fields={"username"}, message="This username has been taken!")
 */
class User implements UserInterface
{
    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", unique=true,length=191)
     * @Assert\NotBlank()
     * @Assert\Length(min="5", minMessage="Username most contain at least 5 characters!")
     */
    private $username;

    /**
     * @ORM\Column(type="string")
     */
    private $password;

    /**
     * @ORM\Column(type="string")
     */
    private $displayName;

    /**
     * @ORM\Column(type="boolean")
     */
    private $showAdminBar;

    /**
     * @ORM\OneToMany(targetEntity="Post", mappedBy="owner")
     */
    private $posts;

    /**
     * @ORM\Column(type="string")
     */
    private $avatar;

    /**
     * @Assert\NotBlank(groups={"Registration"})
     * @Assert\Length(min="6", minMessage="Password most contain at least 6 characters!")
     */
    private $plainPassword;

    public function getUsername()
    {
        return $this->username;
    }

    public function getRoles()
    {
        return ['ROLE_ADMIN'];
    }

    public function getPassword()
    {
        return $this->password;
    }

    public function getSalt()
    {
    }

    public function eraseCredentials()
    {
        $this->plainPassword = null;
    }

    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->displayName,
            $this->avatar,
            // see section on salt below
            // $this->salt,
        ));
    }

    /**
     * @param mixed $username
     */
    public function setUsername($username)
    {
        $this->username = $username;
    }

    /**
     * @param mixed $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * @return mixed
     */
    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    /**
     * @param mixed $plainPassword
     */
    public function setPlainPassword($plainPassword)
    {
        $this->plainPassword = $plainPassword;
        //To make sure that Doctrine see the entity as "dirty"
        $this->password = null;
    }

    /**
     * @return mixed
     */
    public function getDisplayName()
    {
        return $this->displayName;
    }

    /**
     * @param mixed $displayName
     */
    public function setDisplayName($displayName)
    {
        $this->displayName = $displayName;
    }

    /**
     * @return mixed
     */
    public function getShowAdminBar()
    {
        return $this->showAdminBar;
    }

    /**
     * @param mixed $showAdminBar
     */
    public function setShowAdminBar($showAdminBar)
    {
        $this->showAdminBar = $showAdminBar;
    }

    /**
     * @return mixed
     */
    public function getPosts()
    {
        return $this->posts;
    }

    /**
     * @param mixed $posts
     */
    public function setPosts($posts)
    {
        $this->posts = $posts;
    }

    /**
     * @return mixed
     */
    public function getAvatar()
    {
        return $this->avatar;
    }

    /**
     * @param mixed $avatar
     */
    public function setAvatar($avatar)
    {
        $this->avatar = $avatar;
    }

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }
}

Here is my UserController.php

<?php

namespace App\Controller\Admin;

use App\Constants;
use App\Entity\User;
use App\Form\UserType;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;


/**
 * @Route("/admin/user")
 * @Security("is_granted('ROLE_ADMIN')")
 */
class UserController extends Controller
{
    /**
     * @Route("/profile", name="admin_user_profile")
     */
    public function profileAction(Request $request)
    {
        $user = $this->getUser();

        $user->setAvatar(
            new File(Constants::UPLOAD_AVATAR.'/'.$user->getAvatar())
        );


        $form = $this->createForm(UserType::class, $user);

        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $user = $form->getData();


            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            $this->addFlash('success', 'Your Info Has Been Updated!');

            return $this->redirectToRoute('admin');
        }


        return $this->render('admin/user/profile.html.twig', [
            'user' => $user,
            'form' => $form->createView()
        ]);
    }

    /**
     * @Route("/list", name="admin_user_list")
     */
    public function listAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $users = $em->getRepository(User::class)
            ->findAll();

        return $this->renderView('admin/user/list.html,twig',[
            'users' => $users
        ]);
    }
}

Here is my UserForm, UserType.php

<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('displayName')
            ->add('plainPassword', RepeatedType::class, [
                'type' => PasswordType::class
            ])
            ->add('avatar',FileType::class)
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}
like image 418
Soheil Avatar asked Apr 11 '18 18:04

Soheil


Video Answer


3 Answers

After some debugging I found the solution myself.

The problem is, when User Entity was implementing the UserInterface, the user provider(actually the Doctrine, behind the scene) tried to Serializing the User object to store it in the session but because of the file that I assigned it to this class, it fails it's career!

To solve the problem, first I tried to fetch separate User object from database but unfortunately Doctrine gave me the exact reference of the User object again.(That's not a bug. Thanks to Doctrine. It's too smart to query as less as possible).

Second, I clone the User object myself in the controller before sending it to the UserType form, and then everything went well.

But that is not the best practice because you may have some other problems with registration, profile update or other scenarios that you may have with User class.

In my application, I added another entity called Media and it stores the files with the file system and each entity like User which need some media (Like user avatar here), just have a ManyToOne relationship with this entity. In this case you can just save the name file as string in avatar field in User class.

You may have some other designs in your application but as I experienced, Do not assign a File field directly to the User entity which is implementing UserInterface!

like image 57
Soheil Avatar answered Sep 22 '22 13:09

Soheil


I've done something like this:

class User implements UserInterface, Serializable {

    // our image
    private $profileImage;

    /*
    Rest of our awesome entity
    */

    public function serialize()
    {
        $this->profileImage = base64_encode($this->profileImage);
    }

    public function unserialize($serialized)
    {
        $this->profileImage = base64_decode($this->profileImage);

    }
}

And it's working pretty well.

like image 40
Oskar Barcz Avatar answered Sep 22 '22 13:09

Oskar Barcz


Very good answer Sohell, you summed it up perfectly.

I had the same problem and found a workaround, after flushing the entityManager, I just set the imageFile property of the user object to null.

It let's the user object update and persist the right object in session, the File object excluded

like image 30
Simon30 Avatar answered Sep 22 '22 13:09

Simon30