phpsymfonysymfony4

Where do you store user uploaded content within a Symfony 4 application?


I have a section within my site where the user can upload their own profile pictures which is stored in the output directory and tracked in the database like so:

    $form = $this->createForm(ProfileUpdateForm::class);
    $form->handleRequest($request);
    if ($form->isSubmitted() && $form->isValid())
    {
        $user = $this->getUser();
        $firstname = $form->get('firstname')->getData();
        $lastname = $form->get('lastname')->getData();
        $picture = $form->get('profilepicture')->getData();

        if($picture == null)
        {
            $user
            ->setFirstName($firstname)
            ->setLastName($lastname);
        }
        else
        {
            $originalFilename = pathinfo($picture->getClientOriginalName(), PATHINFO_FILENAME);
            // this is needed to safely include the file name as part of the URL
            $safeFilename = strtolower(str_replace(' ', '', $originalFilename));
            $newFilename = $safeFilename.'-'.uniqid().'.'.$picture->guessExtension();

            try {
                $picture->move(
                    'build/images/user_profiles/',
                    $newFilename
                );
            } catch (FileException $e) {
                $this->addFlash("error", "Something happened with the file upload, try again.");
                return $this->redirect($request->getUri());
            }

            // updates the 'picture' property to store the image file name
            // instead of its contents
            $user
            ->setProfilePicture($newFilename)
            ->setFirstName($firstname)
            ->setLastName($lastname);
        }

        $entityManager = $this->getDoctrine()->getManager();
        $entityManager->persist($user);
        $entityManager->flush();

        $this->addFlash("success", "Your profile was updated!");
        return $this->redirectToRoute('account');
    }

    return $this->render('account/profile.html.twig', [
        'profileform' => $form->createView()
    ]);

That issue I've found is that every time I compile my (local) project, the image is then deleted (because the public/build directory gets built by deleting and creating again).

If I'm not mistaken, isn't that how deployments work too? And if so, is that the right way to upload an image? What's the right way of going about this?


Solution

  • I'm not sure why, but your public/ directory shouldn't be deleted.
    If you're using Webpack Encore, then public/build/ content is deleted and created again when you compile assets. But not public/ itself.

    For uploads, we create public/upload/ directory.
    Then, most of the time, we set some globals, which allow us to save the file name only.

    Globals for Twig in config/packages/twig.yaml which "root" will be in your public/ directory

    twig:
        globals:
            app_ul_avatar: '/upload/avatar/'
            app_ul_document: '/upload/document/'
    

    And globals for your controllers, repositories, etc in config/services.yaml

    parameters:
        app_ul_avatar: '%kernel.root_dir%/../public/upload/avatar/'
        app_ul_document: '%kernel.root_dir%/../public/upload/document/'
    

    It's handy because, as I just said, you only get to save the file name in the database.

    Which mean that, if you got a public/upload/img/ folder, and want to also generates thumbnails, you can then create public/upload/img/thumbnail/ and nothing will change in your database, nor do you have to save an extra path.

    Just create a new global app_ul_img_thumbnail, and you're set.

    Then all you have to do is call your globals when you need them, and contact with the file name:

    In Twig:

    {{ app_ul_avatar~dbResult.filename }}
    

    Or in Controller:

    $this->getParameter('app_ul_avatar').$dbResult->getFilename();