Using @asset with php variables?

I’m trying to use the shorthand @asset in an acf repeater to dynamically load assets. Is this possible?

@asset is used to load assets that are part of your theme, not images that are stored in WordPress’s media library. Unless your ACF repeater is passing strings that point to images in your theme, no, it will not work.

Most of the functionality in Sage is pretty straightforward, and with a little elbow grease you can figure out what it’s doing and exactly how it works. For instance, I worked back through the files to determine exactly how @asset worked to answer this question:

It’s defined in theme/app/setup.php:

/**
 * Create @asset() Blade directive
 */
sage('blade')->compiler()->directive('asset', function ($asset) {
    return "<?= " . __NAMESPACE__ . "\\asset_path({$asset}); ?>";
});

Essentially, it prints out a PHP call to a function in the App namespace called asset_path(). You can find that function in theme/app/helpers.php:

/**
 * @param $asset
 * @return string
 */
function asset_path($asset)
{
    return sage('assets')->getUri($asset);
}

asset_path() is using an object returned by sage() to call the getUri() method. sage() is also defined in helpers.php:

use Roots\Sage\Container;
/**
 * Get the sage container.
 *
 * @param string $abstract
 * @param array  $parameters
 * @param Container $container
 * @return Container|mixed
 */
function sage($abstract = null, $parameters = [], Container $container = null)
{
    $container = $container ?: Container::getInstance();
    if (!$abstract) {
        return $container;
    }
    return $container->bound($abstract)
        ? $container->makeWith($abstract, $parameters)
        : $container->makeWith("sage.{$abstract}", $parameters);
}

sage() is returning a Container object, and by looking at the top of the file, we can see that container is coming from use Roots\Sage\Container. A little bit of reasoning (or help from your IDE) will tell you that class is defined in the sage-lib composer package, which means you can find it in theme/vendor/roots/sage-lib/Assets/JsonManifest.php. And sure enough, there’s the getUri() method:

/** @inheritdoc */
public function getUri($asset)
{
    return "{$this->dist}/{$this->get($asset)}";
}

We can look at this method and see that it is concatenating the string for the dist folder with the string that we passed to it as our asset. This means that, while we could theoretically pass a string with lots of ../s in it and somehow navigate to the WordPress media gallery, that’s not really the intent of the method.

If you want to use directives for your loop, you could pretty easily create your own directive to wrap wp_get_attachment_image() or a similar function.

3 Likes

Thanks for the in-depth explanation. So, yes I’m passing strings with ACF that point to icons in my theme. I don’t want to put icons in the media library so maybe I should look for another method to do this properly.

If you’re using icons that are in your theme files (i.e. theme/resources/assets/), then @asset would work. It won’t work for items that are stored in WP’s media library (i.e. /wp-content/uploads/). Sorry if that wasn’t clear.

It’s very clear. I think it’s me who haven’t been clear. My bad.

I have some icons in the images folder of my theme. Let’s say cog.svg and key.svg.

I’m using ACF text fields inside a repeater where I type cog or key so my theme know which one to load and where.

I was wondering if there is a possibility to use the @asset shorthand with a php variable that gets the ACF field inside a repeater. Example (which doesn’t work):

@php $var = get_sub_field('icon') @endphp
<img src="@asset('images/icons/{{ $var }}.svg')">

I would process your data at the controller level to create the entire path, then pass a single variable to @asset:

// your controller puts this into $processed_icons
return array_map(function($item) {
   return "images/icons/$item.svg";
}, $icons);

// your partial
@foreach($processed_icons as $icon)
   <img src="@asset($icon)">
@endforeach

@asset will print whatever you give it into the PHP call it generates: The code you posted would (I believe) result in:

<img src="dist/images/icons{{$var}}.svg">

…Which PHP cannot process (it doesn’t understand Blade echo tags).

One of the most valuable tools I’ve found for debugging/figuring out Blade is to look at the PHP files Blade generates from your templates. Seeing what PHP they result it often helps to make it clear how various Blade things work. Unless you’ve changed the configuration, you should be able to find those files in /wp-content/uploads/cache.

5 Likes

You’re awesome. Thanks!

1 Like

Glad I could help! If you can, please mark the post that solved it as the answer to help other folks.

Apologies for restarting this thread - is there a way to change the @asset path as shown above, but also use it when working with ACF repeaters?

The repeater controller below works (from another @alwaysblank post I found useful!), but I’m not sure how I merge the above controller so that my ‘svg_icon’ path is changed.

public function services()
{
   return array_map(function($service) {
      return [
         'service' => $service['service'] ?? null,
         'description' => $service['description'] ?? null,
         'link_text' => $service['link_text'] ?? null,
         'link_url' => $service['link'] ?? null,
         'svg_icon' => $service['svg_icon'] ?? null,
      ];
   }, get_field('services') ?? []);
}

:slight_smile:

I’m not totally sure what you’re trying to do here, but it’s early and I’ve only had one coffee.

@asset() just takes a string that tells it where to look for a file. The big thing it does is take into account cache-busting hashes that are added to files that run through the production build process. If you’re processing your SVGs just like your other asset files, and are just keeping them in an svg subfolder, you should be able to do the following:

// controller
public function services()
{
   return array_map(function($service) {
      return [
         ...
         'svg_icon' => 'images/svg/' . $service['svg_icon'] ?? null,
      ];
   }, get_field('services') ?? []);
}

// blade
<img src="@asset($svg_icon)" >

Thank you. I am still trying to learn how controllers work and sometimes get stuck focusing too much on the wrong thing when the answer I need is much more simple. :sweat_smile:

Bump

asset_path gives the full URI to a file. But how does one get the file name and path of the file relative to a template? To use as an include, for example.