'Symfony HttpFoundation UploadedFile "not uploaded due to unknown error" when using Doctrine DataFixtures

I've been using my Attachment entity based on the cookbook recipie How To Handle File Uploads With Doctrine in Symfony 2.3.

It works well, even in functional tests. However using it with Doctrine DataFixtures is causing me problems.

[Symfony\Component\HttpFoundation\File\Exception\FileException]
The file "o-rly-copy.jpg" was not uploaded due to an unknown error.

This was not helpful, however I did run php app/console doctrine:fixtures:load -v to bring up a stack trace and it appears the exception is thrown not on the persisting method, but on $manager->flush()

Attachment::setFile() requires an instance of UploadedFile so I wonder if there is a way round that.

It appears the error occurs on line 225 of Symfony\Component\HttpFoundation\File\UploadedFile

return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname())

The condition for is_uploaded_file() returns false because the file was already on the server.

<?php

/**
 * Prepopulate the database with image attachments.
 */
final class AttachmentFixtures extends AbstractFixture implements OrderedFixtureInterface, ContainerAwareInterface
{
    private static $imageData = array(
        array(
            'name' => "O RLY?",
            'file' => "o-rly",
            'type' => "jpg",
        ),
        //...
    );

    public function getPathToImages()
    {
        return $this->container->get('kernel')->getRootDir() . '/../src/Acme/DemoBundle/Resources/public/default/images';
    }

    public function getPathToUploads()
    {
        return $this->container->get('kernel')->getRootDir() . '/../web/uploads/fixtures';
    }

    /**
     * {@inheritDoc}
     */
    public function load(ObjectManager $manager)
    {
        $imageReferences = array();
        $filesystem = $this->container->get('filesystem');

        foreach (self::$imageData as $image) {
            $imageFilename         = sprintf('%s.%s',      $image['file'], $image['type']);
            $copiedImageFilename   = sprintf('%s-copy.%s', $image['file'], $image['type']);

            $pathToImageFile = sprintf('%s/%s', $this->getPathToImages(), $imageFilename);

            try {
                $filesystem->copy($pathToImageFile, $pathToCopiedFile = sprintf('%s/%s', $this->getPathToUploads(), $copiedImageFilename));
                $filesystem->chmod($pathToCopiedFile, 0664);
            } catch (IOException $e) {
                $this->container->get('logger')->err("An error occurred while copying the file or changing permissions.");
            }

            $imageFile = new UploadedFile(
                $pathToCopiedFile,                                              // The full temporary path to the file
                $copiedImageFilename,                                           // The original file name
                'image/' . 'jpg' === $image['type'] ? 'jpeg' : $image['type'],  // Mime type - The type of the file as would be provided by PHP
                filesize($pathToCopiedFile),
                null,
                null,
                true
            );

            $imageAttachment = new Attachment();

            $imageAttachment->setName($image['name']);
            $imageAttachment->setFile($imageFile);

            // Populate a reference array for later use
            $imageReferences['attachment-'.$image['file']] = $imageAttachment;

            $manager->persist($imageAttachment);
        }

        $manager->flush(); // <-- Exception throw here


        // Create references for each image to be used by other entities that
        // maintain a relationship with that image.
        foreach ($imageReferences as $referenceName => $image) {
            $this->addReference($referenceName, $image);
        }
    }
}


Solution 1:[1]

Thanks to stof, the solution is to make Attachment::setFile() (or Document::setFile() if using the cookbook example) hint for an instance of UploadedFile's parent class, Symfony\Component\HttpFoundation\File\File, and in the fixtures class, create a new instance and pass it to the setFile method

Attachment.php

<?php

namespace Acme\DemoBundle\Entity;

use Symfony\Component\HttpFoundation\File\File;
//...

class Attachment
{
    /**
     * Sets file.
     *
     * @param File $file
     */
    public function setFile(File $file = null)
    {
        $this->file = $file;
        // check if we have an old image path
        if (isset($this->path)) {
            // store the old name to delete after the update
            $this->temp = $this->path;
            $this->path = null;
        } else {
            $this->path = 'initial';
        }
    }

    //...
}

AttachmentFixtures.php

<?php

namespace Acme\DemoBundle\DataFixtures\ORM;

use Symfony\Component\HttpFoundation\File\File;
//...

class AttachmentFixtures //...
{
    //...

    public function load(ObjectManager $manager)
    {
        //...
        $imageFile = new File($pathToCopiedFile);

        $imageAttachment = new Attachment();

        $imageAttachment->setFile($imageFile);
        //...
    }
}

Solution 2:[2]

There is now a better solution:

The constructor of UploadedFile has a boolean $test parameter which disables the check using is_uploaded_file. This parameter has been added for testing/fixture code.

Just set it to true and the isValid() check of UploadedFile will not be a problem anymore.

Example:

// My data fixture code.
$test = true;
$userPhoto->setImageFile(new UploadedFile($photoDir . $photoFile, $photoFile, null, null, null, $test));

Solution 3:[3]

You can use Filesystem to upload files from fixtures first:

const IMAGE_PATH = "/.../your_image_dir_from_this_dir/your_image.jpg";
const UPLOAD_DIR = "/.../your_upload_dir_from_this_dir";
const FILENAME = "your_image.jpg";

$filesystem = new Filesystem();
$file = __DIR__ . self::IMAGE_PATH;
$filesystem->copy($file, __DIR__ . self::UPLOAD_DIR . self::FILENAME);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 robert
Solution 3 Manolo