1

In my Symfony 6 project I need to store big uploaded files per user session.

As it's not a good idea to directly store these files in the session I'm using flysystem with a directory per session id and a cleanup process.

So far so good

Now as I don't want generate the file path per session id every time I want to directly configure a flysystem storage as service using the current session id as base directory like this:

flysystem:
    storages:
        session.storage:
            adapter: 'local'
            options:
                directory: '%env(APP_SESSION_STORAGE_PATH)%/%sessionId%'

This obviously does not work as there is no %sessionId% but how can I do this?

I also tried to use a factory but this also feels to be over complicated as I would have to copy the logic from flysystem-bundle to initialize this service.

I know this service only works within http context.

yivi
  • 42,438
  • 18
  • 116
  • 138
mabe.berlin
  • 1,043
  • 7
  • 22

2 Answers2

1

Just the code-idea. Just after your "..have to copy the logic from flysystem-bundle..". Wow. I think you try to make it over complicated

I don't know your app-logic. However just like:

// config/services.yaml
services:
     _defaults:
        //..
        bind:
            $bigFileSessionStoragePath: '%env(APP_SESSION_BIG_FILE_STORAGE_PATH)%'

Your service :

// BigFileSessionStorage.php


namespace Acme/Storage;

use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\RequestStack;

class BigFileSessionStorage
{
   private string $bigFileSessionDirPath;

   public function __construct(
     private RequestStack $requestStack,
     private Filesystem $filesystem,
     private string $bigFileSessionStoragePath
   ){}

  public function storeBigSessionFile(File $bigSessionFile){
     $sessionBifFileDir = $this->getBigFileSessionDirectory();
     // move a Big File ..
     $this->filesystem->copy(...)
  }

  public function getBigFileSessionDirectory(): string{
    $sessionId = $this->requestStack->getSession()->getId();
    $path = $this->bigFileSessionDirPath.DIRECTORY_SEPARATOR.$sessionId;
    if (!$this->filesystem->exists($path)){
        $this->filesystem->mkdir($path);
        $this->bigFileSessionDirPath = $path;
    }
    return $this->bigFileSessionDirPath;
  }

  // TODO & so on 
  public function removeBigFileSessionDirectory(){}  
   
}

Then inject this service where U need it.

yivi
  • 42,438
  • 18
  • 116
  • 138
voodoo417
  • 11,861
  • 3
  • 36
  • 40
  • yea this works but I was hoping to get a normal flysystem instance registered as service with a chrooted location of the current session id. I'm using flysystem because of such nice features and the ability to store the files on S3. – mabe.berlin Mar 11 '22 at 08:30
  • @mabe.berlin "... the files on S3" I got. Maybe look yet at `SessionHandlerProxy`.Yes, guess -> wrapping/decorating is the simplest way. Not to complicate much. *** or make a branch and extend what U need ^) – voodoo417 Mar 11 '22 at 12:29
  • I don't want to store all session data in a file and further more I would mean loading the full file into memory – mabe.berlin Mar 11 '22 at 17:21
  • @mabe.berlin Sorry, I don't tell/point U "to store session data in a file". Imho instead of wasting time -> just decorate/wrap Flysystem as a service + creating session id-based folders or in memory (related to session id) or how do U want. In fact -> many different ways to make what U need *** I mean -> imho don't try to implement via basic Flysystem config. Otherwise U need to make a branch for your requirements. – voodoo417 Mar 11 '22 at 17:29
0

Based on the idea of @voodoo417 answer I ended up creating a decorator for the FilesystemOperator. I'm not very happy about this solution as it contains a lot of boilerplate decorator code just to modify the root directory where it would be possible to configure the root directory in the first place.

But it works and the result is exactly what I was looking for.

flysystem:
    storages:
        baseSession.storage:
            adapter: 'local'
            options:
                directory: '%env(APP_SESSION_STORAGE_PATH)%'

class SessionStorage implements FilesystemOperator
{
    public function __construct(
        private FilesystemOperator $baseSessionStorage,
        private RequestStack       $requestStack,
    ) {}

    private function chroot(string $location): string
    {
        $session   = $this->requestStack->getSession();
        $sessionId = $session->getId();

        // TODO: Don't allow /../ syntax to access other sessions

        return "/{$sessionId}/" . \ltrim($location, '/');
    }

    public function fileExists(string $location): bool
    {
        return $this->baseSessionStorage->fileExists($this->chroot($location));
    }

    // ... decorate all the other methods of FilesystemOperator
}

Now I can use SessionStorage everywhere in my application (within http context) and I can use FilesystemOperator $baseSessionStorage in a console command (cron) to clean up files.

mabe.berlin
  • 1,043
  • 7
  • 22