Search code examples
phpdownloadsymfonyflysystem

Serving files for download with Symfony3 and Flysystem


Introduction

I am using:

  • Windows 10 Pro
  • XAMPP with PHP v7.0.9
  • Symfony v3.1.6
  • Doctrine v2.5.4
  • StofDoctrineExtensionsBundle [1] in order to manage Tree structure.
  • OneupUploaderBundle [2] in order to Upload files
  • OneupFlysystemBundle [3] for filesystem abstraction

Setting up

I did setup the tree structure (that represents directories and files) and file upload with OneupUploaderBundle and Flysystem endpoint.

File upload is organized to upload to folder named data that exists in project root folder. Uploading is working fine and even paths with custom sub-directories for different uploads work (for example data/project_001/test1.txt and data/project_002/test2.txt)!

Problem

I need to serve files that users had uploaded earlier.

At the moment

  • I am getting error (with controller ending 1)

    The file "Project_9999/2_Divi/bbb.pdf" does not exist
    500 Internal Server Error - FileNotFoundException
    
  • and error (with controller ending 2)

    Catchable Fatal Error: Object of class League\Flysystem\File could not be converted to string
    500 Internal Server Error - ContextErrorException
    

Note that $exists return true - so file is definitely there!

Code

relevant part of config.yml

oneup_uploader:
    mappings:
        gallery:
            storage:
                type: flysystem
                filesystem: oneup_flysystem.gallery_filesystem

            frontend: blueimp
            enable_progress: true
            namer: app.upload_unique_namer

            allowed_mimetypes: [ image/png, image/jpg, image/jpeg, image/gif ]
            max_size: 10485760s

oneup_flysystem:
    adapters:
        my_adapter:
            local:
                directory: "%kernel.root_dir%/../data"

    filesystems:
        gallery:
            adapter: my_adapter

Controller action

<?php

public function projectDownloadFileAction($file_id, Request $request)
{
    $em = $this->getDoctrine()->getManager();
    $repo = $em->getRepository('AppBundle:FileTree');
    $ultra = $this->get('app.ultra_helpers');

    $selected_file = $repo->getFileTreeNodeByIdArray($file_id);
    $item_name = $selected_file[0]['item_name'];
    $item_extension = $selected_file[0]['item_extension'];

    $activeProject = $this->get('session')->get('active_project');
    $activeProjectSelectedNodePath = $this->get('session')->get('active_project_selected_node_path');
    $item_file_name = $item_name .'.'. $item_extension;
    $complete_file_path = $activeProject['secret_path'] . $activeProjectSelectedNodePath .'/'. $item_file_name;
    dump($complete_file_path);

    ENDING -1-
    ENDING -2-
}

Ending 1

    $downloadable_file = new \SplFileInfo($complete_file_path);
    dump($downloadable_file);
    $response = new BinaryFileResponse($downloadable_file);

    // Set headers
    $response->headers->set('Cache-Control', 'private');
    $response->headers->set('Content-Type', mime_content_type($downloadable_file));
    $response->headers->set('Content-Disposition', $response->headers->makeDisposition(
        ResponseHeaderBag::DISPOSITION_ATTACHMENT,
        $downloadable_file->getFilename()
    ));

    return $response;

Ending 2

    $filesystem = $this->get('oneup_flysystem.gallery_filesystem');
    $exists = $filesystem->has($complete_file_path);
    if (!$exists)
    {
        throw $this->createNotFoundException();
    }
    dump($exists);

    $downloadable_file = $filesystem->get($complete_file_path);
    dump($downloadable_file);

    $response = new BinaryFileResponse($complete_file_path);
    $response->trustXSendfileTypeHeader();
    $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT);

    return $response;

Update 1

I tried to supply absolute path to the Ending 1 variable `$complete_file_path, but got only errors. For example:

  • File not found at path: C:\DEV\tree_and_upload\data\Project_9999\1_Viens\test1.txt 500 Internal Server Error - FileNotFoundException

  • File not found at path: C:/DEV/tree_and_upload/data/Project_9999/1_Viens/test1.txt 500 Internal Server Error - FileNotFoundException

But it did not make any diference - I got access error. Perhaps Symfony is actively restricting access to data folder in the root of the project...

Question

How do one would serve files from data folder that is situated in project root folder and using Flysystem filesystem abstraction layer with Local adapter?

Conclusion

What am I missing?

Please advise.

Thank you for your time and knowledge.


Solution

  • As Flysystem with local adapter has no methods to get downloadable file path! It is suggested to move or store files in the public web folder and retrieve path the same way as with assets.

    I ended up using StreamedResponse [1] instead of BinaryFileResponse.

    use Symfony\Component\HttpFoundation\StreamedResponse;
    
    $response = new StreamedResponse();
    $response->setCallback(function () {
        echo $downloadable_file_stream_contents;
        flush();
    });
    $response->send();
    

    How do you get $downloadable_file_stream_contents;

    $fs = new Filesystem($localAdapter);
    $downloadable_file_stream = $fs->readStream($public_path);
    $downloadable_file_stream_contents = stream_get_contents($downloadable_file_stream);