Image Placeholder Configuration

Intro

To define your own entity image placeholder or redefine the existing one, use one of the following three options.

All examples are illustrated for the Product entity, but you can do the same for any other entity.

There are three services that enable you to set a placeholder for the image that is already defined in the system for the Product entity.

Default Image Placeholder Provider

Take a look at the image_placeholder.yml file on Github.

Perform the following steps to override DefaultImagePlaceholderProvider that is located at the very bottom of this chain:

  1. Create an appropriate service definition.

    1
    2
    3
    4
    5
    6
    7
    8
    # Resources/config/services.yml
    services:
    
        acme_demo.provider.demo_image_placeholder.default:
            parent: oro_layout.provider.image_placeholder.default.abstract
            public: false
            arguments:
                - '/bundles/acmedemo/images/demo_placeholder_default.png'
    
  2. Add your newly created service to the oro_product.provider.product_image_placeholder chain service. You can do it via DI CompilerPass.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // DependencyInjection/Compiler/ImagePlaceholderProviderPass.php
    namespace ACME\Bundle\DemoBundle\DependencyInjection\Compiler;
    
    use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
    use Symfony\Component\DependencyInjection\ContainerBuilder;
    use Symfony\Component\DependencyInjection\Reference;
    
    /**
     * The ImagePlaceholderProviderPass compiler pass class.
     */
    class ImagePlaceholderProviderPass implements CompilerPassInterface
    {
        public function process(ContainerBuilder $container)
        {
            if (!$container->hasDefinition('oro_product.provider.product_image_placeholder')) {
            }
    
            $definition->setMethodCalls(array_merge(
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.config')]]],
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.theme')]]],
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.default')]]],
                $definition->getMethodCalls()
    

Pay attention to the way the chain works. It gets the first suitable value from providers, so we have put our own provider to the very top of the chain via ContainerBuilder::setMethodCalls and array_merge. You can locate your own provider where required.

Make sure to insert CompilerPass to the bundle root file.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ACMEDemoBundle.php
namespace ACME\Bundle\DemoBundle;

use ACME\Bundle\DemoBundle\DependencyInjection\Compiler\ImagePlaceholderProviderPass;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Bundle\Bundle;

/**
 * The ACMEDemoBundle bundle class.
 */
class ACMEDemoBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

Theme Image Placeholder Provider

The second way to define an image placeholder is to set it on the theme layer.

To do this, perform the following:

  1. Define one more service and name it as acme_demo.provider.demo_image_placeholder.theme. The argument which this service receives is the placeholder name of our placeholder image. You can name this argument the same as the entity name, product in our case, to avoid any confusion.

    1
    2
    3
    4
    5
    6
    7
    8
    # Resources/config/services.yml
    services:
    
        acme_demo.provider.demo_image_placeholder.theme:
           parent: oro_layout.provider.image_placeholder.theme.abstract
           public: false
           arguments:
               - 'product'
    
  2. Add the acme_demo.provider.demo_image_placeholder.theme service definition to CompilerPass.

    1
    2
    3
    4
    5
    6
    7
    // DependencyInjection/Compiler/ImagePlaceholderProviderPass.php
            if (!$container->hasDefinition('oro_product.provider.product_image_placeholder')) {
                return;
    
            $definition = $container->getDefinition('oro_product.provider.product_image_placeholder');
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.config')]]],
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.theme')]]],
    
  3. Create theme.yml

    1
    2
    3
    # Resources/views/layouts/default/theme.yml
    image_placeholders:
        product: '/bundles/acmedemo/images/demo_placeholder_theme.png'
    

Note

Pay attention that the product key in the YAML file is the value that we have passed to acme_demo.provider.demo_image_placeholder.theme as the first argument.

Config Image Placeholder Provider

The third way to define an image placeholder is through the system configuration parameters.

To do this, perform the following:

  1. Define one more service with the acme_demo.provider.demo_image_placeholder.config name. The argument which this service receives is the system configuration key.

  2. Define this configuration key in the system. More details on how to do it are described in the System Configuration article.

    1
    2
    3
    4
    5
    6
    7
    8
    # Resources/config/services.yml
    services:
    
        acme_demo.provider.demo_image_placeholder.config:
           parent: oro_layout.provider.image_placeholder.config.abstract
           public: false
           arguments:
               - 'acme_demo.provider.demo_image_placeholder.config.param'
    
  3. Add the acme_demo.provider.demo_image_placeholder.config service definition to CompilerPass.

    1
    2
    3
    4
    5
    6
    7
    // DependencyInjection/Compiler/ImagePlaceholderProviderPass.php
            if (!$container->hasDefinition('oro_product.provider.product_image_placeholder')) {
                return;
    
            // ...
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.config')]]],
                [['addProvider', [new Reference('acme_demo.provider.demo_image_placeholder.theme')]]],
    

TwigExtension and template examples

To use the providers we have created previously, we need to create TwigExtension that fetches the Product image in the appropriate dimension or, if the main image is unavailable, provides the placeholder instead.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Twig/ProductImageExtension.php
namespace ACME\Bundle\DemoBundle\Twig;

use Oro\Bundle\AttachmentBundle\Entity\File;
use Oro\Bundle\AttachmentBundle\Manager\AttachmentManager;
use Oro\Bundle\LayoutBundle\Provider\Image\ImagePlaceholderProviderInterface;
use Oro\Bundle\ProductBundle\Helper\ProductImageHelper;
use Psr\Container\ContainerInterface;
use Symfony\Contracts\Service\ServiceSubscriberInterface;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;

/**
 * Provides Twig functions to get image placeholder and type images for a product entity:
 *   - product_filtered_image
 *   - product_image_placeholder
 */
class ProductImageExtension extends AbstractExtension implements ServiceSubscriberInterface
{
    const NAME = 'acme_product_image';

    /** @var ContainerInterface */
    private $container;

    /**
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function getFunctions()
    {
        return [
            new TwigFunction('product_filtered_image', [$this, 'getProductFilteredImage']),
            new TwigFunction('product_image_placeholder', [$this, 'getProductImagePlaceholder'])
        ];
    }

    /**
     * @param File|null $file
     * @param string $filter
     * @return string
     */
    public function getProductFilteredImage(?File $file, string $filter): string
    {
        if ($file) {
            $attachmentManager = $this->container->get('oro_attachment.manager');

            return $attachmentManager->getFilteredImageUrl($file, $filter);
        }

        return $this->getProductImagePlaceholder($filter);
    }

    /**
     * @param string $filter
     * @return string
     */
    public function getProductImagePlaceholder(string $filter): string
    {
        $imagePlaceholderProvider = $this->container->get('oro_product.provider.product_image_placeholder');

        return $imagePlaceholderProvider->getPath($filter);
    }

    /**
     * {@inheritdoc}
     */
    public function getName()
    {
        return self::NAME;
    }

    /**
     * {@inheritdoc}
     */
    public static function getSubscribedServices()
    {
        return [
            'oro_attachment.manager' => AttachmentManager::class,
            'oro_product.provider.product_image_placeholder' => ImagePlaceholderProviderInterface::class,
            'oro_product.helper.product_image_helper' => ProductImageHelper::class,
        ];
    }
}

You can use Twig functions declared in the extension for your templates.

1
2
3
4
5
6
7
8
9
{# Resources/views/layouts/default/imports/oro_product_list_item/oro_product_list_item.html.twig #}

{# ... #}
{% set productImage = product.imagesByType('listing').first.image|default(null) %}
{% set productImageUrl = product_filtered_image(productImage, product_image_size) %}

<picture>
    <img src="{{ productImageUrl }}" alt="Product Image" itemprop="image">
</picture>