How to use Assetic with CacheBustingWorker and twig - symfony

As i can see Assetic made some progress on CacheBusting:
https://github.com/kriswallsmith/assetic#cache-busting
But i dont really understand how i should use this.
Can this be used from within twig:
{% stylesheets 'bundles/mybundle/css/fonts.css'
'bundles/mybundle/css/style.css'
'bundles/mybundle/css/screen.css'
filter='cssrewrite'
%}
<link rel="stylesheet" type="text/css" href="{{ asset_url }}" />
{% endstylesheets %}
And with the usual assetic:dump command?
Where would i have to hook the CacheBustingWorker in?

Cache buster is now part of symfony/AsseticBundle (Version >= 2.5.0).
Change AsseticBundle version in composer.json like that:
"symfony/assetic-bundle": "2.5.0",
And activate cache busting for assets in your config.yml file like that
assetic:
workers:
cache_busting: ~
My JS files are now looking like that:
web/bundles/js/projectname-876f9ee.js
See https://github.com/symfony/AsseticBundle/pull/119#issuecomment-28877145

I have been recently looking how to do the same.
The solution I came up with was to override Symfony's AssetFactory with my own class and add the CacheBustingWorker in its constructor. Basically you create a file like the following:
<?php
namespace YourSite\YourBundle\Factory;
use Symfony\Bundle\AsseticBundle\Factory\AssetFactory as BaseAssetFactory;
use Assetic\Factory\Worker\CacheBustingWorker;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpKernel\KernelInterface;
class AssetFactory extends BaseAssetFactory
{
public function __construct(KernelInterface $kernel, ContainerInterface $container, ParameterBagInterface $parameterBag, $baseDir, $debug = false)
{
parent::__construct($kernel, $container, $parameterBag, $baseDir, $debug);
// Add CacheBustingWorker
$this->addWorker(new CacheBustingWorker(CacheBustingWorker::STRATEGY_CONTENT));
}
}
and then change the assetic.asset_factory.class parameter to point to this new class in your config. In my case I added the following to config.yml:
parameters:
assetic.asset_factory.class: YourSite\YourBundle\Factory\AssetFactory

With the current implementation of assetic, I needed to update your code to the following to get this to work. Also note if you are using xdebug, you must up the max nesting level - xdebug.max_nesting_level = 200 to more than 100.
<?php
namespace YourSite\YourBundle\Factory;
use Symfony\Bundle\AsseticBundle\Factory\AssetFactory as BaseAssetFactory;
use Assetic\Factory\LazyAssetManager;
use Assetic\Factory\Worker\CacheBustingWorker;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpKernel\KernelInterface;
class AssetFactory extends BaseAssetFactory
{
public function __construct(KernelInterface $kernel, ContainerInterface $container, ParameterBagInterface $parameterBag, $baseDir, $debug = false)
{
parent::__construct($kernel, $container, $parameterBag, $baseDir, $debug);
// Add CacheBustingWorker
$this->addWorker(new CacheBustingWorker(new LazyAssetManager(new BaseAssetFactory($kernel, $container, $parameterBag, $baseDir, $debug))));
}
}
Hope this helps someone

As the assetic code changed again, there is no need of Stategy on LazyAssetManager on the master branch.
Do not forget to change your composer.json file:
{
"kriswallsmith/assetic": "dev-master#dev",
"symfony/assetic-bundle": "dev-master#dev"
}
You now just need this:
namespace YourSite\YourBundle\Factory;
use Symfony\Bundle\AsseticBundle\Factory\AssetFactory as BaseAssetFactory;
use Assetic\Factory\Worker\CacheBustingWorker;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\HttpKernel\KernelInterface;
class AssetFactory extends BaseAssetFactory
{
public function __construct(
KernelInterface $kernel,
ContainerInterface $container,
ParameterBagInterface $parameterBag,
$baseDir,
$debug = false
) {
parent::__construct($kernel, $container, $parameterBag, $baseDir, $debug);
// Add CacheBustingWorker
$this->addWorker(new CacheBustingWorker());
}
}
Do not forget to php app/console cache:clear -e prod before dumping assets one time to avoid standard filenames to be generated.

assetic:
workers:
cache_busting: ~
Is the answer.

Related

Include content of CSS file in twig

Let's say I use Webpack, and builded a Css file properly at "build/theme/mail.css".
I wants to include the CONTENT of this File into my twig.
{% include "https://mysite.io/build/theme/mail.css" %} doesn't work saying it can't find the file ( but it exist).
I don't want that because im using an inliner, and absolutly need the #CONTENT in my twig.
Tried the File get Content, didn't works. Found a solution:
twig:
paths:
'%kernel.project_dir%/public': public
Add a twig path to public directory
and then use :
{% apply inline_css(source(theme_asset('#public/build/theme/email.css'))) %}
and add a webpackconfig to build the scss file into public/build
Thanks you all.
You could create custom twig extension and use file_get_contents.
Extension:
<?php
namespace App\Twig;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class GetPublicFileContentExtension extends AbstractExtension
{
/** #var string */
private $publicPath;
public function __construct(ParameterBagInterface $parameterBag)
{
$this->publicPath = $parameterBag->get('kernel.project_dir') . '/public/';
}
public function getFunctions(): array
{
return [
new TwigFunction('get_public_file_content', [$this, 'getPublicFileContent']),
];
}
public function getPublicFileContent(string $filepath)
{
return file_get_contents($this->publicPath . $filepath);
}
}
Twig:
{{ get_public_file_content('build/theme/mail.css') }}

Substring Count in Twig

I am looking to use substr_count in Twig, does anything exist already? I want to perform something like this;
<?php
$text = 'This is a test';
echo strlen($text); // 14
echo substr_count($text, 'is'); // 2
I can do an extension but it seems this might be something built in already that I have missed.
How about this?
{%set count = text|split('is')|length-1 %}
This doesn't exist in the list of Twig functions or filters.
You'll have to write your own custom function/filter or try a package (note; I've never used this package so can't comment on it, but was on the first page of Google results).
I went for an extension
namespace AppBundle\Twig;
class SubStrCountExtension extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('substr_count', array($this, 'substr_count')),
);
}
public function getName()
{
return 'substr_count_extension';
}
public function substr_count($str, $char)
{
return substr_count($str, $char);
}
}
And in services.yml
app.twig_extension.substr_count_extension:
class: AppBundle\Twig\SubStrCountExtension
tags:
- { name: twig.extension }
I use solution in Symfony 3.2.8 but in the description does not say this block of code should is inside : services
If you does not make show this error:
There is no extension able to load the configuration for .....
This code should is inside services this is correct:
services:
app.twig_extension.substr_count_extension:
class: AppBundle\Twig\SubStrCountExtension
tags:
- { name: twig.extension }
Finally, the correct use in twig is:
tu placa es: {{substr_count(datos.picoyplaca,4)}}
Regards

Why symfony can't find template it rendered in other function

I have a function in my controller like this:
<?php
namespace GradeBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use GradeBundle\Entity\User;
use GradeBundle\Entity\SchoolClass;
class MainController extends Controller
{
/**
* #Route("/", name="index_page")
*/
public function index()
{
return $this->render('GradeBundle:Default:index.html.twig');
}
It renders the twig template correctly. However when I use other function:
/**
* #Route("/adminNew", name="add_admin")
*/
public function addAdmin()
{
$session = new Session();
if($session->get('loggedIn') == null)
return $this->redirect($this->generateUrl('index_page'));
else
return $this->render('GradeBundle:Add:newAdmin.html.twig');
}
I have the following error:
Unable to find template "GradeBundle:Default:index.twig.html".
Does anybody have any idea what might be wrong?
It's a typo somewhere you call template:
GradeBundle:Default:index.twig.html
But you have only GradeBundle:Default:index.html.twig template.
Note the difference: html.twig twig.html
I suspect that you extend it in GradeBundle:Add:newAdmin.html.twig by:
{% extends 'GradeBundle:Default:index.twig.html' %}
but should be:
{% extends 'GradeBundle:Default:index.html.twig' %}
Have you made sure to use the correct namespace for the Controller you're using? And are you including the correct files? Also I'm not sure I understand the question correctly - are you saying if you add another function with a different twig file render, the first one no longer renders? Could I see your class names and the namespaces / use statements?
Usually in these instances, it's that the templates are in the wrong place or the correct file is not included in order to find it.
Michael

How I can don't use |raw filter in the template for services

I have a service, that generate and return simple html code of breadcrumb. But in template I always need to use |raw filter:
{{ $breadcrumb|raw }}
otherwise I can see escaped html code, not a real links:
Home / Contacts
With |raw filter it's work well, but maybe I do something wrong and there are any other ways to do this without |raw filter? Or it's a normally to use raw filter in this case? I think there is more suitable solution, is't it? help me better understand it, please.
Every argument passed to twig template is by default escaped. So it is normally that you need use raw filter. But if using raw filter is annoying for you, then you have two options.
First - disable autoescaping (Not recommended):
Turn autoescaping off globally by setting the autoescape option to false in config.yml:
twig:
autoescape: false
Second - Create twig extension (Recommended):
Create twig extension with function which will render content and it be html safe.
<?php
// src/Acme/DemoBundle/Twig/BreadcrumbExtension.php
namespace Acme\DemoBundle\Twig;
class BreadcrumbExtension extends \Twig_Extension
{
protected $breadcrumbService;
public function __construct(BreadcrumbService $breadcrumbService)
{
$this->breadcrumbService = $breadcrumbService;
}
/**
* Returns a list of functions to add to the existing list.
*
* #return array An array of functions
*/
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('acme_breadcrumbs',
array($this, 'renderBreadcrumbs'),
array('is_safe' => array('html'))
),
);
}
public function renderBreadcrumbs()
{
return $this->breadcrumbService->renderHtml();
}
public function getName()
{
return 'breadcrumb_extension';
}
}
Register service:
<service id="acme.breadcrumb.twig.extension" class="Acme\DemoBundle\Twig\BreadcrumbExtension" public="false">
<tag name="twig.extension" />
<argument type="service" id="acme.breadcrumb.service" />
</service>
And now you can use it in twig template like this:
{{ acme_breadcrumbs() }}

How to add some event to twig in symfony 2.1

I want add my control code before rendering template.
Example:
Have templates:
user.html.twig
Controller:
return $this->render('....:user.html.twig', array(/* variables */))
I want add other variables before rendering template.
You can override Symfony\Bundle\FrameworkBundle\Controller\Controller::render method in your controller and pass additional variables or arrange some events hooks there or in Twig.
Solution this problem:
Create a new Twig extension and register this extension.
Example:
Twig extension:
class UserExtension extends \Twig_Extension
{
/**
* Get globals variables
*
* #return array
*/
public function getGlobals()
{
return array(
// Other variables
);
}
}
Register extension as service.
<service id="sps.twig.user_extension" class="SPS\Bundles\HomeBundle\Twig\Extensions\UserExtension">
<tag name="twig.extension" />
</service>

Resources