Buy
Buy

Absolute Asset Paths

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

One of the things I've noticed is that this word uploads - the directory where uploads are being stored - is starting to show up in a few places. We have it here in our liip_imagine config file, the oneup_flysystem.yaml file and in UploaderHelper: it's used in getPublicPath().

Centralizing the uploads/ Path

It's not a huge problem, but repetition is a bummer and this will cause some issues when moving to S3: we'll need to hunt down all of these paths and change them to point to the S3 domain.

Let's tighten this up. In services.yaml, create two new parameters: The first will be uploads_dir_name set to uploads - this is the name of the directory where we are storing uploaded files. Call the second one uploads_base_url and set this to almost the same thing: / and then %uploads_dir_name%. This represents the base URL to the uploaded assets.

... lines 1 - 5
parameters:
... lines 7 - 8
uploads_dir_name: 'uploads'
uploads_base_url: '/%uploads_dir_name%'
... lines 11 - 51

Thanks to these, we can do some cleanup! In liip_imagine.yaml, we need the URL. Copy uploads_base_url and then use %uploads_base_url%.

liip_imagine:
... lines 2 - 13
resolvers:
flysystem_resolver:
flysystem:
... lines 17 - 18
root_url: '%uploads_base_url%'
... lines 20 - 67

Next, in oneup_flysystem.yaml, we need the directory name. Copy the other parameter: %uploads_dir_name%.

... line 1
oneup_flysystem:
adapters:
public_uploads_adapter:
local:
directory: '%kernel.project_dir%/public/%uploads_dir_name%'
... lines 7 - 10

The last place is in UploaderHelper. The getBasePath() call will give us the directory where the site is installed - usually an empty string. Then we need to pass in the uploads_base_url parameter.

Add a new argument to the constructor: string $uploadedAssetsBaseUrl. I'll create the property by hand and give it a slightly different name: $publicAssetBaseUrl, not for any particular reason. Set that in the constructor:

... lines 1 - 12
class UploaderHelper
{
... lines 15 - 22
private $publicAssetBaseUrl;
public function __construct(FilesystemInterface $publicUploadsFilesystem, RequestStackContext $requestStackContext, LoggerInterface $logger, string $uploadedAssetsBaseUrl)
{
... lines 27 - 29
$this->publicAssetBaseUrl = $uploadedAssetsBaseUrl;
}
... lines 32 - 78

Back in getPublicPath(), use this: getBasePath() then $this->publicAssetsBaseUrl, which will contain the / at the beginning.

... lines 1 - 12
class UploaderHelper
{
... lines 15 - 70
public function getPublicPath(string $path): string
{
// needed if you deploy under a subdirectory
return $this->requestStackContext
->getBasePath().$this->publicAssetBaseUrl.'/'.$path;
}
}

Cool! But, Symfony will not be able to autowire this string argument. You can see the error if you try to reload any page. Yep!

We know how to fix that: back in services.yaml, add a bind: $uploadedAssetsBaseUrl set to %uploads_base_url%. Now... it works!

... lines 1 - 11
services:
# default configuration for services in *this* file
_defaults:
... lines 15 - 21
bind:
... lines 23 - 25
$uploadedAssetsBaseUrl: '%uploads_base_url%'
... lines 27 - 51

Linking to the Full Image

Small step, but with all this config in one spot, we can do something kinda cool... with almost no effort. But first, I want to triple check that all this public path stuff is setup correctly. Our getPublicPath() method is currently used in one spot: by the uploaded_asset() Twig function. But, we're not actually using this Twig function anywhere at the moment.

So try this: in the form, we're showing the thumbnail. It might be useful to allow the user to click this and see the original image. That's pretty easy: add <a href=""> and use uploaded_asset(articleForm.vars.data.imagePath).

That's it! Wrap this around the img tag and let's also add target="_blank".

{{ form_start(articleForm) }}
... lines 2 - 5
<div class="row">
... lines 7 - 13
<div class="col-sm-3">
{% if articleForm.vars.data.imageFilename %}
<a href="{{ uploaded_asset(articleForm.vars.data.imagePath) }}" target="_blank">
<img src="{{ articleForm.vars.data.imagePath|imagine_filter('squared_thumbnail_small') }}" height="100">
</a>
{% endif %}
</div>
</div>
... lines 22 - 40
{{ form_end(articleForm) }}

Cool. Test that - refresh and... click. Nice! This sends us directly to the source image.

Absolute URLs

Thanks to our setup, we can now solve a really annoying problem. Inspect element on the image: notice that both the href and the image src paths do not contain the domain name. That's not a problem at all in a normal web context. But if you ever try to render a page into a PDF with something like wkhtmltopdf or create a console command to send an email that references an uploaded file, well... suddenly, those paths will start to break! In those contexts, you need the URLs to be absolute.

There are a few ways to solve this... and honestly, I went back and forth on the best approach. I finally settled on something that we've used here on SymfonyCasts for years. Open your .env file. We're going to create a brand new, custom environment variable called SITE_BASE_URL. Set the default value to https://localhost:8000.

35 lines .env
... lines 1 - 33
SITE_BASE_URL=https://localhost:8000

Remember: this file is committed to git, so this is the default value. You can create a .env.local file to override this value locally or on production. Or, of course, if it's easy, you can override this by setting a real environment variable.

Next, go back to services.yaml. And for the uploads_base_url, use %env()% and inside, SITE_BASE_URL: that's the syntax for referencing an environment variable.

... lines 1 - 5
parameters:
... lines 7 - 9
uploads_base_url: '%env(SITE_BASE_URL)%/%uploads_dir_name%'
... lines 11 - 51

And... just like that - every single path to every single uploaded asset will now be absolute. Seriously! Test it out! Boom! Both the link href and the image src contain the https://localhost:8000 part.

And, sure, you could add some config so that you could turn this on only when you need it... but I don't really see the point. I'll keep absolute URLs always.

Next: let's start uploading private assets: stuff that can't be put into the public/ directory because we need to check security before we let a user download it.

Leave a comment!