Search code examples
phpsymfonysymfony-sonata

Sonata Media Bundle - How to write a custom image resizer


I'm using the Sonata Media Bundle and I need to code a specific behavior for the image resizer, because the default SimpleResizer and SquareResizer classes don't fit my needs.

I would like a simple image resizer that lets me resize the image exactly if I specify both the width and height parameters. I would also like that it can fallback on the simple resizer behavior if I don't specify a height parameter.

I've just searched for the docs but I wasn't able to find a solution.


Solution

  • First of all you have to create a resizer service in your bundle in order to put it in the Sonata Media Bundle configuration.

    # Acme/Bundle/CoreBundle/Resources/config/services.yml
    
    services:
        sonata.media.resizer.custom:
            class: Acme\Bundle\CoreBundle\Resizer\CustomResizer
            arguments: [@sonata.media.adapter.image.gd, 'outbound', @sonata.media.metadata.proxy]
    

    The second service argument, in this case, must be 'outbound'. Allowed parameters are ImageInterface::THUMBNAIL_INSET and ImageInterface::THUMBNAIL_OUTBOUND.

    Now the Acme\Bundle\CoreBundle\Resizer\CustomResizer code:

    <?php
    
        namespace Acme\Bundle\CoreBundle\Resizer;
    
        use Imagine\Image\ImagineInterface;
        use Imagine\Image\Box;
        use Gaufrette\File;
        use Sonata\MediaBundle\Model\MediaInterface;
        use Sonata\MediaBundle\Resizer\ResizerInterface;
        use Imagine\Image\ImageInterface;
        use Imagine\Exception\InvalidArgumentException;
        use Sonata\MediaBundle\Metadata\MetadataBuilderInterface;
    
        class CustomResizer implements ResizerInterface
        {
            protected $adapter;
            protected $mode;
            protected $metadata;
    
            /**
             * @param ImagineInterface $adapter
             * @param string $mode
             */
            public function __construct(ImagineInterface $adapter, $mode, MetadataBuilderInterface $metadata)
            {
                $this->adapter = $adapter;
                $this->mode = $mode;
                $this->metadata = $metadata;
            }
    
            /**
             * {@inheritdoc}
             */
            public function resize(MediaInterface $media, File $in, File $out, $format, array $settings)
            {
                if (!(isset($settings['width']) && $settings['width']))
                    throw new \RuntimeException(sprintf('Width parameter is missing in context "%s" for provider "%s"', $media->getContext(), $media->getProviderName()));
    
                $image = $this->adapter->load($in->getContent());
    
                $content = $image
                           ->thumbnail($this->getBox($media, $settings), $this->mode)
                           ->get($format, array('quality' => $settings['quality']));
    
                $out->setContent($content, $this->metadata->get($media, $out->getName()));
            }
    
            /**
             * {@inheritdoc}
             */
            public function getBox(MediaInterface $media, array $settings)
            {
                $size = $media->getBox();
                $hasWidth = isset($settings['width']) && $settings['width'];
                $hasHeight = isset($settings['height']) && $settings['height'];
    
                if (!$hasWidth && !$hasHeight)
                    throw new \RuntimeException(sprintf('Width/Height parameter is missing in context "%s" for provider "%s". Please add at least one parameter.', $media->getContext(), $media->getProviderName()));
    
                if ($hasWidth && $hasHeight)
                    return new Box($settings['width'], $settings['height']);
    
                if (!$hasHeight)
                    $settings['height'] = intval($settings['width'] * $size->getHeight() / $size->getWidth());
    
                if (!$hasWidth)
                    $settings['width'] = intval($settings['height'] * $size->getWidth() / $size->getHeight());
    
                return $this->computeBox($media, $settings);
            }
    
            /**
             * @throws InvalidArgumentException
             *
             * @param MediaInterface $media
             * @param array $settings
             *
             * @return Box
             */
            private function computeBox(MediaInterface $media, array $settings)
            {
                if ($this->mode !== ImageInterface::THUMBNAIL_INSET && $this->mode !== ImageInterface::THUMBNAIL_OUTBOUND)
                    throw new InvalidArgumentException('Invalid mode specified');
    
                $size = $media->getBox();
    
                $ratios = [
                    $settings['width'] / $size->getWidth(),
                    $settings['height'] / $size->getHeight()
                ];
    
                if ($this->mode === ImageInterface::THUMBNAIL_INSET)
                    $ratio = min($ratios);
                else
                    $ratio = max($ratios);
    
                return $size->scale($ratio);
            }
        }
    

    Well done. Your service is defined. You have to link it in the app/config.yml and all is done. I've included the whole sonata_media configuration in order to provide a good example, but remember you only need the last three lines.

    sonata_media:
        default_context: default
        db_driver: doctrine_orm # or doctrine_mongodb, doctrine_phpcr
        contexts:
            default:  # the default context is mandatory
                providers:
                    - sonata.media.provider.dailymotion
                    - sonata.media.provider.youtube
                    - sonata.media.provider.image
                    - sonata.media.provider.file
    
                formats:
                    small: { width: 100, height: 100, quality: 70 }
                    big:   { width: 500, height: 300, quality: 70 }
                download:
                    strategy: sonata.media.security.public_strategy
        cdn:
            server:
                path: /uploads/media # http://media.sonata-project.org/
        filesystem:
            local:
                directory:  %kernel.root_dir%/../web/uploads/media
                create:     true
        providers:
            image:
                resizer: sonata.media.resizer.custom # THIS IS OUR NEW RESIZER SERVICE