Extending the Module¶
Creating Custom Formatters¶
The Image Link Formatter architecture is designed for extension. You can create custom formatters that inherit its link-wrapping functionality.
Extending ImageLinkFormatter¶
Basic Extension¶
Create a new formatter that extends ImageLinkFormatter and adds custom logic:
<?php
namespace Drupal\my_module\Plugin\Field\FieldFormatter;
use Drupal\image_link_formatter\Plugin\Field\FieldFormatter\ImageLinkFormatter;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Custom image formatter that wraps images in links and tracks clicks.
*
* @FieldFormatter(
* id = "tracked_image_link_formatter",
* label = @Translation("Tracked image wrapped in link"),
* field_types = {"image"}
* )
*/
class TrackedImageLinkFormatter extends ImageLinkFormatter {
/**
* Custom tracking service.
*/
private ClickTrackerInterface $clickTracker;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
$instance->clickTracker = $container->get('my_module.click_tracker');
return $instance;
}
/**
* {@inheritdoc}
*/
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = parent::viewElements($items, $langcode);
// Add tracking data to each link
foreach ($elements as $delta => &$element) {
if (isset($element['#url'])) {
$element['#attributes']['data-track'] = 'true';
$element['#attributes']['class'][] = 'tracked-link';
}
}
return $elements;
}
}
Adding Services¶
Inject additional services via the create() factory method:
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition): static {
$instance = parent::create($container, $configuration, $plugin_id, $plugin_definition);
// Add your custom services
$instance->currentUser = $container->get('current_user');
$instance->logger = $container->get('logger.channel.my_module');
$instance->customService = $container->get('my_module.custom_service');
return $instance;
}
Overriding Settings Form¶
Add custom settings to the formatter configuration:
public function settingsForm(array $form, FormStateInterface $form_state): array {
$element = parent::settingsForm($form, $form_state);
// Add custom settings
$element['track_clicks'] = [
'#type' => 'checkbox',
'#title' => $this->t('Track clicks'),
'#default_value' => $this->getSetting('track_clicks'),
'#description' => $this->t('Log when users click linked images.'),
];
return $element;
}
public function settingsSummary(): array {
$summary = parent::settingsSummary();
if ($this->getSetting('track_clicks')) {
$summary[] = $this->t('Tracking enabled');
}
return $summary;
}
public static function defaultSettings(): array {
return parent::defaultSettings() + [
'track_clicks' => FALSE,
];
}
Extending ResponsiveImageLinkFormatter¶
The same patterns apply to responsive images:
<?php
namespace Drupal\my_module\Plugin\Field\FieldFormatter;
use Drupal\responsive_image_link_formatter\Plugin\Field\FieldFormatter\ResponsiveImageLinkFormatter;
/**
* Custom responsive image formatter with link wrapping.
*
* @FieldFormatter(
* id = "custom_responsive_image_link_formatter",
* label = @Translation("Custom responsive image in link"),
* field_types = {"image"}
* )
*/
class CustomResponsiveImageLinkFormatter extends ResponsiveImageLinkFormatter {
// Add custom logic here
// Same patterns as above apply
}
Registering Your Module¶
Create a service definition for any custom services your formatter needs.
my_module.services.yml:
services:
my_module.click_tracker:
class: Drupal\my_module\Service\ClickTracker
arguments:
- '@logger.channel.my_module'
- '@database'
my_module.custom_service:
class: Drupal\my_module\Service\CustomService
arguments:
- '@current_user'
Common Extension Patterns¶
1. Link Manipulation¶
Add attributes, classes, or modify URLs:
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = parent::viewElements($items, $langcode);
foreach ($elements as $delta => &$element) {
if (isset($element['#url'])) {
// Add tracking parameter
$query = $element['#url']->getOption('query') ?? [];
$query['utm_source'] = 'image_link';
$element['#url']->setOption('query', $query);
// Open in new window
$element['#attributes']['target'] = '_blank';
}
}
return $elements;
}
2. Conditional Link Wrapping¶
Only wrap images for certain users or conditions:
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = parent::viewElements($items, $langcode);
// Only wrap if user has permission
if (!$this->currentUser->hasPermission('access external links')) {
// Remove link URLs
foreach ($elements as &$element) {
unset($element['#url']);
}
}
return $elements;
}
3. Custom Link Field Resolution¶
Override link field discovery for dynamic field selection:
protected function getLinkFieldsOptions(): array {
if ($this->imageLinkFieldsOptions === NULL) {
$this->imageLinkFieldsOptions = [];
// Custom logic to find link fields
$entity_type = $this->fieldDefinition->getTargetEntityTypeId();
$bundle = $this->fieldDefinition->getTargetBundle();
// Query only fields matching your criteria
$fields = $this->entityFieldManager
->getFieldDefinitions($entity_type, $bundle);
foreach ($fields as $field_name => $field) {
if ($field->getType() === 'link' && /* your custom condition */) {
$this->imageLinkFieldsOptions[$field_name] = $field->getLabel();
}
}
}
return $this->imageLinkFieldsOptions;
}
4. Logging and Debugging¶
Log formatter operations for troubleshooting:
public function viewElements(FieldItemListInterface $items, $langcode): array {
$elements = parent::viewElements($items, $langcode);
$entity = $items->getEntity();
$this->logger->debug(
'Formatting @count images for @entity_type:@entity_id',
[
'@count' => count($elements),
'@entity_type' => $entity->getEntityTypeId(),
'@entity_id' => $entity->id(),
]
);
return $elements;
}
Testing Custom Formatters¶
Kernel Test Example¶
<?php
namespace Drupal\Tests\my_module\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\entity_test\Entity\EntityTest;
/**
* Tests for custom image link formatter.
*
* @group my_module
*/
class CustomImageLinkFormatterTest extends KernelTestBase {
protected static $modules = [
'system',
'field',
'image',
'link',
'image_link_formatter',
'my_module',
];
public function testTrackedImageFormatter() {
// Create test entity with image and link fields
$entity = EntityTest::create([
'field_image' => ['target_id' => 1],
'field_link' => ['uri' => 'https://example.com', 'title' => 'Example'],
]);
// Render using custom formatter
$formatter = $this->container
->get('plugin.manager.field.formatter')
->getInstance([
'field_definition' => $entity->getFieldDefinition('field_image'),
'view_mode' => 'default',
'configuration' => [
'type' => 'tracked_image_link_formatter',
'image_link' => 'field_link',
'track_clicks' => TRUE,
],
]);
$elements = $formatter->viewElements($entity->field_image, 'en');
// Assert link has tracking class
$this->assertContains('tracked-link', $elements[0]['#attributes']['class']);
}
}