Rikijs Rikijs - 14 days ago 6
PHP Question

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.

Answer

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();
Comments