Monday, September 16, 2013

Custom Asset Package for Symfony2 app or how to generate bundle assets dir in Symfony2 Twig app

If you try to google how to get bundle assets dir in your Symfony2 Twig template you find almost nothing about this.

for example to output some image you need to write following in your twig template:

<img alt="" src="/bundles/sitebundle/images/image.jpg" />

But if you prefer Symfony way, you probably write something like:

<img alt="" src="{{asset('/bundles/sitebundle/images/image.jpg')}}" />

But what if you can generate bundle assets path by bundle name, which is so familiar to you like 'YourGreatBundle' for example.

I decided to write some code for this.

Symfony's asset() twig function actually accepts 2nd argument, which is $packageName. $packageName is just named asset Package instance. By default it is PathPackage. Url for assets generated with getUrl() method of Package instance. All we need is create 
custom asset Package class, let's call it BundlePathPackage:


<?php
namespace Vendor\SiteBundle\Asset\Package;

use 
Symfony\Component\Templating\Asset\PathPackage;
use 
Vendor\SiteBundle\Asset\Package\BundlePathPackageInterface;

/**
 * Allows prepending bundle assets directory to base path
 */

class BundlePathPackage extends PathPackage implements BundlePathPackageInterface {

    
/**
     * @var string
     */
    
private $bundleDir;

    
/**
     * {@inheritdoc}
     */
    
public function __construct($basePath null$version null$format null) {
        
parent::__construct($basePath$version$format);
    }

    
/**
     * If relative url detected, also prepend bundle dir to path
     * {@inheritdoc}
     */
    
public function getUrl($path) {
        if (isset(
$this->bundleDir))
            
$path $this->bundleDir '/' ltrim($path'/');
        return 
parent::getUrl($path);
    }

    
/**
     * {@inheritdoc}
     */
    
public function setBundlePath($bundleName) {
        
$this->bundleDir 'bundles/' strtolower(str_replace('Bundle'''$bundleName));
    }

}





You can find that it extends some interface. Here it is:


<?php
namespace Vendor\SiteBundle\Asset\Package;

interface 
BundlePathPackageInterface {

    
/**
     * registers bundle dir by bundlename
     * @param string $bundleName Identical to what
     * Symfony\Component\HttpKernel\Bundle\BundleInterface->getName() gives
     */
    
public function setBundlePath($bundleName);
}
 


Almost done. Last step would be to tell Kernel that something new happens in our app. Something is Kernel event listener:

<?php
namespace Vendor\SiteBundle\Event\Listener\HttpKernel;

use 
Symfony\Component\HttpKernel\Event\KernelEvent;
use 
Vendor\SiteBundle\Asset\Package\BundlePathPackage;

class 
AssetPackageInjector {

    
/**
     * Inject custom Asset package to Kernel assets helper
     * @param \Symfony\Component\HttpKernel\Event\KernelEvent $event
     */
    
public function onKernelRequest(KernelEvent $event) {
        
$container $event->getDispatcher()->getContainer();

        
/** @var Symfony\Component\Templating\Helper\CoreAssetsHelper */
        
$assetsHelper $container->get('templating.helper.assets');

        
$bundles $container->get('kernel')->getBundles();
        foreach (
$bundles as $bundle) {
            
$bundlePathPackage = new BundlePathPackage();
            
$bundlePathPackage->setBundlePath($bundle->getName());
            
$assetsHelper->addPackage($bundle->getName()$bundlePathPackage);
        }
    }

}
 


Finally we register listener in services.yml configuration file:

services:
  vendor.site.event.listener.assetpackage:
    class: Vendor\SiteBundle\Event\Listener\HttpKernel\AssetPackageInjector
    tags:
      - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest }

That's it. Now in Twig we can write following:

<img alt="" src="{{asset('images/image.jpg', 'SiteBundle')}}" />

Or with leading slash (in this case base url will be appended if not standard):

<img alt="" src="{{asset('/images/image.jpg', 'SiteBundle')}}" />

No comments:

Post a Comment