7

I'm trying to create a custom Twig filter in Drupal 8. The goal is to use my custom filter in a template like so:

{{ a_variable|myfilter }}

I have created a custom module with 3 files:

MODULE.info.yml
MODULE.services.yml
src/TwigExtension/MyExtension.php

The MODULE.info.yml file contains the usual values.

The MODULE.services.yml file contains:

services:
  MODULE.twig_extension:
#   arguments: ['@renderer']  #  <-- Initial code (USELESS)
    class: Drupal\MODULE\TwigExtension\MyExtension
    tags:
      - { name: twig.extension }

The src/TwigExtension/MyExtension.php file contains:

namespace Drupal\MODULE\TwigExtension;

// use Drupal\Core\Template\TwigExtension;  // <-- Initial code (WRONG)

class MyExtension extends \Twig_Extension {    
// class MyExtension extends TwigExtension {  // <-- Initial code (WRONG)

  /**
   * Generates a list of all Twig filters that this extension defines.
   */
  public function getFilters() {
    return [
      new \Twig_SimpleFilter('myfilter', array($this, 'myFilter')),
    ];
  }

  /**
   * Gets a unique identifier for this Twig extension.
   */
  public function getName() {
    return 'MODULE.twig_extension';
  }

  /**
   * Replaces all instances of "animal" in a string with "plant".
   */
  public static function myFilter($string) {
    return str_replace(array('animal'), array('plant'), $string);
  }

}

My initial code got following error: "Call to a member function generateFromRoute() on a non-object in /core/lib/Drupal/Core/Template/TwigExtension.php on line 224".

But I have now corrected the code following lauriii's answer and it works perfectly!

3
  • 1
    Hm, I have checked mine implementation and all seems fine. Are yo usure the error comes from your extension? Maybe you are calling bad function in twig somewhere? Try to switch to some core theme and call your function n there.
    – user21641
    Commented Dec 16, 2015 at 15:21
  • It was a subtle error, replacing TwigExtension with \Twig_Extension. ;-) Commented Dec 16, 2015 at 18:55
  • 1
    leopathu.in/content/create-custom-twig-filter-drupal-8 This article explained well about the custom twig filter with the example and also you could download the sample code from the page.
    – Leopathu
    Commented Aug 10, 2016 at 9:00

5 Answers 5

8

The problem in the code above is that your Twig extension class is extending the internal Drupal Twig extension class. That breaks the Twig extensions added by Drupal core and that's why you are getting the error.

To fix this, you should make your Twig extensions extend Twigs internal Twig extension class. Your class should be defined like this:

class MyExtension extends \Twig_Extension {

After making that change, you can also remove arguments set for that class from the MODULE.services.yml

3
  • Awesome, it works! Thank you so much, lauriii. I will update my original code to reflect your answer. Commented Dec 16, 2015 at 18:22
  • Strange... My initial code was modeled after that found in the twig_extension_test core module. Commented Dec 16, 2015 at 18:28
  • There is a patch pending to fix twig_extension_test. Test modules are not always the best examples unfortunately.
    – star-szr
    Commented Dec 27, 2015 at 13:51
1

I was having a ton of issues with this on a site and I think I finally tracked down the problem: it was using twig/twig: ^3.

Here is code for a twig FUNCTION that works with twig/twig 2

my_module/src/TwigExtension/MyModuleInputTwigExtension.php:

<?php

namespace Drupal\my_module\TwigExtension;

/**
 * Custom Twig extension to format my field.
 */
class MyModuleInputTwigExtension extends \Twig_Extension {

  /**
   * {@inheritdoc}
   */
  public function getFunctions() {
    return [
      new \Twig_SimpleFunction('format_hours_msg', [$this, 'formatMsg'], ['is_safe' => ['html']]),
    ];
  }

  /**
   * Formats a string for proper output.
   *
   * @param string $string
   *   The input string.
   *
   * @return string
   *   The formatted string.
   */
  public function formatMsg($string) {
    // string formatting code  for $string
    return $string;
  }
}

my_module.services.yml:

services:
  my_module.function:
    class: Drupal\my_module\TwigExtension\MyModuleInputTwigExtension
    tags:
      - { name: twig.extension }

But when I tried to use a similar method with /Twig_SimpleFilter or even /Twig_SimpleFunction and whatnot on the other site, it threw:

Uncaught Error: Class "Twig_Extension" not found in /var/www/docroot/modules/custom/my_module/src/TwigExtension/MyModuleInputTwigExtension

When I removed the /TwigExtension/ from the routing file, it threw:

Service 'my_module.twig_extension' for consumer 'twig' does not implement Twig\Extension\ExtensionInterface.

Here is code for a twig FILTER that works with twig/twig 3:

my_module/src/TwigExtension/MyModuleInputTwigExtension.php:

<?php

namespace Drupal\my_module\TwigExtension;

use Twig\Extension\AbstractExtension;
use Twig\TwigFilter;
use Twig\TwigFunction;


class MyModuleTwigExtension extends AbstractExtension {
  
  public function getFilters() {
    return [
      new TwigFilter('to_https', [$this, 'toHttpsFilter']),
    ];
  }

  public function toHttpsFilter($url) {
    // Check if the URL starts with 'http:' or 'https:' and replace it with 'https:'
    $url = preg_replace('/^(http:|https:)?/', 'https:', $url);

    return $url;
  }
}

I've also see the return line for getFilters() look like this:

'to_https' => new TwigFilter('to_https', [$this, 'toHttpsFilter']),

my_module.services.yml:

services:
  my_module.twig_extension:
    class: Drupal\my_module\TwigExtension\MyModuleTwigExtension
    tags:
      - { name: twig.extension }
0

the above sample is still off.. (ie: the 'arguments' clause in .services does seem to be needed)

take a look at /core/modules/system/tests/modules/twig_extension_test to model a twig filter and function from!

1
  • follow up: for whatever reason... the twig_extension_test seems to only works when viewed under an /admin path...
    – skaught
    Commented Apr 20, 2016 at 12:51
0

it seems we need to use \Twig_Extension class more directly.

MODULE.services.yml services: MODULE.twig.filters: arguments: ['@renderer'] class: Drupal\MODULE\TwigExtension\HexToRgb tags: - { name: twig.extension }

/src/TwigExtension/HexToRgb.php

/**
 * @file
 * Contains Drupal\MODULE\TwigExtension.
 */

namespace Drupal\MODULE\TwigExtension;


/**
 * A Twig extension (filter) converts hex color to rgb.
 */
class HexToRgb extends \Twig_Extension {

  /**
   * Generates a list of all Twig filters that this extension defines.
   */
  public function getFilters() {
    return array(
      new \Twig_SimpleFilter('hexToRGB', [$this, 'hexToRGB'], ['is_safe' => ['html']]),
      new \Twig_SimpleFilter('rangeToPercent', [$this, 'rangeToPercent'], ['is_safe' => ['html']]),
    );
  }

  /**
   * Gets a unique identifier for this Twig extension.
   */
  public function getName() {
    return 'MODULE.twig.filters';
  }

  /**
   * convert 3 or 6 char hex color to comma seperated RGB colors
   */
  public static function hexToRgb($hex=000000) {
    $r = $g = $b = FALSE;
    $hex = str_replace("#", "", $hex);

    if(strlen($hex) == 3) {
      $r = hexdec(substr($hex, 0, 1).substr($hex, 0, 1));
      $g = hexdec(substr($hex, 1, 1).substr($hex, 1, 1));
      $b = hexdec(substr($hex, 2, 1).substr($hex, 2, 1));
    } else {
      $r = hexdec(substr($hex, 0, 2));
      $g = hexdec(substr($hex, 2, 2));
      $b = hexdec(substr($hex, 4, 2));
    }

    $rgb = array($r, $g, $b);

    return implode(",", $rgb);
  }

  /**
   * convert range (0 to 100) to a percent with 2 decimals
   */
  public static function rangeToPercent($range=0) {
    return round(($range/100), 2);
  }


}

1
  • arguments: ['@renderer'] is unnecessary because the \Twig_Extension class has no constructor.
    – Tim
    Commented Mar 27, 2020 at 7:51
-1

Try https://www.drupal.org/project/twig_extender . Add a plugin system for custom twig functions and filters

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.