Search code examples
phpsymfonysymfony5

Best approach to generate presigned URL for entity property in Symfony


I have a User object which has a a profilePicturePresignedUrl property. The value of the profilePicturePresignedUrl property needs to be computed using a service.

It would seem that the most efficient way of doing this would be to create a method in the User entity class that would inject the required service like so:

Entity/User.php

public function __construct(FileDownloader $fileDownloader) {
    $this->fileDownloader = $fileDownloader;    
}

public function getProfilePicturePresignedUrl(): string
{
    return $this->fileDownloader->generatePresignedUrl($this->profilePicturePath);
}

However, I have read that it is considered bad practice to inject services in entity classes and can find no references in the documentation of how to do so.

The alternative then would be to generate this value in the controller:

Controller/UserController.php

public function getUserInfo(FileDownloader $fileDownloader)
{
    $user = $this->getUser();
    
    $user->setProfilePicturePresignedUrl($fileDownloader->generatePresignedUrl($user->getProfilePicturePath()));

    return $this->createResponse($user);
}

However, there are many controller methods which call getUser, and it does not seem correct to duplicate this logic in all of them... Is this a situation where it makes sense to inject the service required to generate the presigned URL in the entity class or is there alternative, better approach?


Solution

  • Doctrine Entity Listeners

    Entity listeners are defined as PHP classes that listen to a single Doctrine event on a single entity class. For example, suppose that you want to prepare a specific entity whenever it is loaded from the database. To do so, define a listener for the postLoad Doctrine event. See documentation.

    This way, in your case, everytime you fetch a User entity from the database, the property will be initialized and ready to use everywhere in your code. Note that i do not recommand using this for heavy tasks because it will significantly reduce your app performance.

    src/EventListener/UserListener.php

    namespace App\EventListener;
    
    use App\Entity\User;
    use App\Service\FileDownloader;
    use Doctrine\ORM\EntityManagerInterface;
    use Doctrine\Persistence\Event\LifecycleEventArgs;
    
    class UserListener
    {
      private $entityManager;
      private $fileDownloader;
    
      public function __construct(EntityManagerInterface $entityManager, FileDownloader $fileDownloader)
      {
        $this->entityManager = $entityManager;
        $this->fileDownloader = $fileDownloader; 
      }
    
      public function postLoad(User $user, LifecycleEventArgs $event): void
      {
        $path = $user->getProfilePicturePath();
        $url = $this->fileDownloader->generatePresignedUrl($path);
        $user->setProfilePicturePresignedUrl($url);
      }
    }
    

    config/services.yaml

    services:
      App\EventListener\UserListener:
        tags:
          - {
              name: doctrine.orm.entity_listener,
              entity: 'App\Entity\User',
              event: "postLoad"
            }