Confige service based on session id

204 Views Asked by At

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.

2

There are 2 best solutions below

4
On

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.

0
On

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.