kiuega
kiuega

Reputation: 151

Symfony 3 - Serialization of File is not allowed

I am working on a site that allows users to download packages.

I have a Packages entity that contains attributes (title, comment, ...) and File attributes. These files are uploaded thanks to VichUploaderBundle.

I also have two User tables. One for those who connect to the site locally (login form on the site) and a CAS authentication.

When I authenticate in CAS and I want to access the page presenting the packages to download, I come across this error:

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

Yet, when I connect locally, there is no such problem. I do not understand why I have this error.

This is my code :

CAS authentication: success :

/**
     * Traitement personnalisé après récupération du token
     * 
     * Il est possible d'enrichir le token (attributs...) ou d'effectuer des contrôles supplémentaire
     * 
     * @param $token 
     *      Token d'authification généré
     * 
     * @return null
     */
    public function onSuccess($token){

        $mail = $this->ai->getMail();
        $nomComplet = $this->ai->getCompletName();
        $rne = $this->ai->getRne();

        $token->setAttribute('mail', $mail);
        $token->setAttribute('nomComplet', $nomComplet);
        $token->setAttribute('rne', $rne);   
        $token->setAttribute('typeAuth','cas');

        $user = $this->checkBDD($mail); //I retrieve the user object

        $token->setAttribute('user',$user);


    }

The User object is placed as an attribute in the token.

He goes to his profile, which must contain his information, as well as a table representing the packages he has already downloaded.

/**
 * @Route ("/profile", name="user_profile")
 */
public function profileAction()
{
    $token = $this->get('security.token_storage')->getToken();
    //dump($token->getAttribute('nomComplet'));
    $user = $token->getAttribute('user');


    return $this->render('@Pages/cas/profile.html.twig');
}

Profile.html.twig:

{% extends "base.html.twig" %}
{% block body %}

{% set user = app.getToken().getAttribute('user') %}
{% dump(user) %}

{% dump(app.getToken().getAttribute('nomComplet')) %}
<div class="container">
    <h1><u>{{ user.mail }}</u></h1><br/>

    <h2>Profil</h2>

    <br/>

    <table class="table">
        <tbody>
            <tr>
                <th scope="row">Nom d'utilisateur</th>
                <td>{{app.user.username}}</td>
            </tr>
            <tr>
                <th scope="row">Email</th>
                <td>{{user.mail}}</td>
            </tr>
            <tr>
                <th scope="row">Téléchargements restants</th>
                <td>4</td>
            </tr>
        </tbody>
    </table>

    <br/><br/><br/>

    <h3> Liste des packages téléchargés </h3> <br/>
    <table class="table table-stripped">
        <thead>
            <tr>
                <th>Titre</th>
                <th>Package</th>
                <th>Notice</th>
                <th>Commentaire</th>
            </tr>
        </thead>
        <tbody>
                {% for unPackage in user.packages %}
            <tr>
                <td> {{unPackage.titre}} </td>
                <td> {{unPackage.urlPaquet}} </td>
                <td> {{unPackage.urlNotice}} </td>
                <td> {{unPackage.commentaire}} </td>
            </tr>
                {% endfor %}
        </tbody>
    </table>

    <a href="{{path('connexion_index')}}" class="btn btn-primary">Retour à l'accueil</a>
    <a href="{{ path('deconnexion') }}" class="btn btn-primary">Déconnexion</a><br/><br/><br/><br/>
    </div>

in twig, we retrieve the User attribute that was in the token and we display its information, including its packages. (The user has a packages attribute that is an ArrayCollection and is associated in ManyToMany with the Package entity)

My User entity :

<?php

namespace Site\PagesBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Site\PagesBundle\Security\Traits\traitUser;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * UserCas
 *
 * @ORM\Table(name="user_cas")
 * @ORM\Entity(repositoryClass="Site\PagesBundle\Repository\UserCasRepository")
 * @UniqueEntity("mail")
 */
class UserCas
{

    use traitUser;


    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var int
     *
     * @ORM\Column(name="nbTelechargementsAuto", type="integer", nullable=true)
     */
    private $nbTelechargementsAuto;

    /**
     * @var bool
     *
     * @ORM\Column(name="enabled", type="boolean")
     */
    private $enabled;


    /**
     * @ORM\Column(name="mail", type="string")
     */
    private $mail;

    /**  
     * @var \Doctrine\Common\Collections\Collection
     * @ORM\ManyToMany(targetEntity="Paquet")  
     * @ORM\JoinTable(name="paquetsDDLUserCas") 
     * @ORM\JoinColumn(nullable=false)
     */  
    private $packages;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->packages = new ArrayCollection();
        $this->setEnabled(true);

    }




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


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

    public function setMail($mail)
    {
        $this->mail = $mail;
    }


    /**
     * Set enabled
     *
     * @param boolean $enabled
     *
     * @return UserCas
     */
    public function setEnabled($enabled)
    {
        $this->enabled = $enabled;

        return $this;
    }

    public function isEnabled()
    {
        return $this->enabled;
    }

}

My Package entity :

<?php

namespace Site\PagesBundle\Entity;

use DateTimeImmutable;
use Doctrine\ORM\Mapping as ORM;
use Site\PagesBundle\Entity\Paquet;
use Site\PagesBundle\Entity\TypeUser;
use Symfony\Component\HttpFoundation\File\File;
use Doctrine\Common\Collections\ArrayCollection;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\Validator\Constraints as Assert;

/**
 * Paquet
 *
 * @ORM\Table(name="paquet")
 * @ORM\Entity(repositoryClass="Site\PagesBundle\Repository\PaquetRepository")
 * @Vich\Uploadable
 */
class Paquet
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;


    /**  
     * @var \Doctrine\Common\Collections\Collection
     * @ORM\ManyToMany(targetEntity="TypeUser")  
     * @ORM\JoinTable(name="Packages_des_TypesUser") 
     * @ORM\JoinColumn(nullable=false)
     */  
    private $typeUser;

    public function __construct()
    {
        $this->typeUser = new ArrayCollection();
    }

    /** 
     * Get TypeUser 
     * 
     * @return Site\PagesBundle\Entity\TypeUser 
     */ 
    public function getTypeUser() 
    { 
        return $this->typeUser; 
    }

    public function deleteTypeFromTypesUser(TypeUser $type)
    {
        $this->typeUser->removeElement($type);
    }



    /**
     * Set typeUser
     *
     * @param Site\PagesBundle\Entity\TypeUser $typeUser
     *
     * @return Paquet
     */
    public function setTypeUser(Site\PagesBundle\Entity\TypeUser $typeUser)
    {
        $this->typeUser = $typeUser;

        return $this;
    }


    /**
     * @var string
     *
     * @ORM\Column(name="titre", type="string", length=255)
     * @Assert\Length(min=5, max=255, minMessage="Le titre doit comporter au minimum 5 caractères")
     */
    private $titre;

    /**
     * @var string
     *
     * @ORM\Column(name="urlPaquet", type="string", length=255)
     */
    private $urlPaquet;

    /**
     * @Vich\UploadableField(mapping="paquet", fileNameProperty="urlPaquet")
     * @var File
     */
    private $paquetFile;

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

    /**
 * @param File|UploadedFile $unPaquetFile
 *
 * @return Paquet
*/
public function setPaquetFile(File $unPaquetFile = null)
{
    $this->paquetFile = $unPaquetFile;

    if ($unPaquetFile) 
    {
        $this->updatedAt = new \DateTimeImmutable();
    }


    return $this;
}

    /**
     * Set updatedAt
     *
     * @param \DateTime $updatedAt
     *
     * @return Paquet
     */
    public function setUpdatedAt($updatedAt)
    {
        $this->updatedAt = $updatedAt;

        return $this;
    }

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

/**
 * @return File|null
 */
public function getPaquetFile()
{
    return $this->paquetFile;
}


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

    /**
     * @Vich\UploadableField(mapping="notice", fileNameProperty="urlNotice",nullable=true)
     * @var File
     */
    private $noticeFile;

    /**
     * @var string
     *
     * @ORM\Column(name="commentaire", type="text")
     */
    private $commentaire;


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

    /**
     * Set titre
     *
     * @param string $titre
     *
     * @return Paquet
     */
    public function setTitre($titre)
    {
        $this->titre = $titre;

        return $this;
    }

    /**
     * Get titre
     *
     * @return string
     */
    public function getTitre()
    {
        return $this->titre;
    }

    /**
     * Set urlPaquet
     *
     * @param string $urlPaquet
     *
     * @return Paquet
     */
    public function setUrlPaquet($urlPaquet)
    {
        $this->urlPaquet = $urlPaquet;

        return $this;
    }

    /**
     * Get urlPaquet
     *
     * @return string|null
     */
    public function getUrlPaquet()
    {
        return $this->urlPaquet;
    }

    /**
     * @return File|null
     */
    public function getNoticeFile()
    {
        return $this->noticeFile;
    }

        /**
     * @param File|UploadedFile $uneNoticeFile
     *
     * @return Paquet
    */
    public function setNoticeFile(File $uneNoticeFile = null)
    {
        $this->noticeFile = $uneNoticeFile;

        if ($uneNoticeFile) 
        {
            $this->updatedAt = new \DateTimeImmutable();
        }


        return $this;
}

    /**
     * Set urlNotice
     *
     * @param string $urlNotice
     *
     * @return Paquet
     */
    public function setUrlNotice($urlNotice)
    {
        $this->urlNotice = $urlNotice;

        return $this;
    }

    /**
     * Get urlNotice
     *
     * @return string
     */
    public function getUrlNotice()
    {
        return $this->urlNotice;
    }

    /**
     * Set commentaire
     *
     * @param string $commentaire
     *
     * @return Paquet
     */
    public function setCommentaire($commentaire)
    {
        $this->commentaire = $commentaire;

        return $this;
    }

    /**
     * Get commentaire
     *
     * @return string
     */
    public function getCommentaire()
    {
        return $this->commentaire;
    }
}

That's it, I think I put all the information that was needed. And I repeat that when I authenticate locally (and not in CAS), I do not have this problem. And I should not have to serialize any file normally, right?


EDIT :

In Paquet.php, I've implemented \Serializable, and two methods :

  /** @see \Serializable::serialize() */
  public function serialize()
  {
      return serialize(array(
          $this->id,
          $this->noticeFile,
          $this->paquetFile,

      ));
  }

  /** @see \Serializable::unserialize() */
  public function unserialize($serialized)
  {
      list (
          $this->id,
          $this->noticeFile,
          $this->paquetFile,
      ) = unserialize($serialized, array('allowed_classes' => false));
  }

Same error, my methods are they good ?


EDIT: Okay it works, I've just added these two functions in my UserCas.php :

public function serialize()
{
    return serialize($this->id);
}

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

}

Upvotes: 2

Views: 2382

Answers (2)

Herz3h
Herz3h

Reputation: 712

Check this out, one of your User's relation has a File property, which is essentially filestream which aren't allowed to be serialized. Therefore you need to specify what you want to serialized:

https://symfony.com/doc/3.3/security/entity_provider.html

Basically you only neeed to serialize id, username and password (three is an example code on the above page)

Upvotes: 0

Andrei
Andrei

Reputation: 3598

This is major guestimation on my part, it may or may not be the correct answer. I'm leaning on may not be the correct answer. It's close to impossible for me to test it locally using your code.

I'm assuming that at some point the following happens:

$serlialization = serialize($userClass);

Your user class has a reference to a resource stream, in this case a File. Which can't be serialised.

See here

Resources are not intended to be serialized and cannot be persisted across page loads via session variables. They are basically just handles on some system resource. PHP will de-allocate these resource handles automatically at the end of script execution.

You'll have to implement the Serializable interface on your classes that will get serialized and have resource references.

Lets take for example the Paquet class.

class Paquet implements \Serializable
{
    ...

    public function __clone()
    {
        // now it's a string reference not a resournce reference
        $this->noticeFile = $this->noticeFile->getPath();
         // Alternatively i think you can use base64_encode 
        $this->paquetFile = $this->paquetFile->getPath();

        // the rest can stay the same
    }

    public function serialize()
    {
        return serialize(clone $this);
    }

    public function unserialize($serialized)
    {
        // TODO: Implement unserialize() method.
    }
}

Of course oyu don't have to do it with clone, there are other ways.

class Paquet implements \Serializable
{
    public function serialize()
    {
        $toSerialize = new \stdClass();

        $toSerialize->noticeFile = $this->noticeFile->getPath();
        $toSerialize->paquetFile = $this->paquetFile->getPath();

        return serialize($toSerialize);
    }

    public function unserialize($serialized)
    {
        $unseriliazed = unserialize($serialized);

        $this->noticeFile = new File($unseriliazed->noticeFile);
    }
}

That should take care of the resource stream reference since it's no longer a reference, it's a string.

Note that you can also use __sleep(), but you'll also have to use __wakeup. They do more or less the same thing as the Serializable interface, you'll just have to write your own implementation.


Again, this is major guestimating on my part. It won't probably work 100% without tweaking.

Upvotes: 1

Related Questions