Completed
Pull Request — master (#732)
by 12345
03:41
created

FilterManager   A

Complexity

Total Complexity 22

Size/Duplication

Total Lines 189
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 3
Bugs 1 Features 1
Metric Value
wmc 22
c 3
b 1
f 1
lcom 1
cbo 9
dl 0
loc 189
rs 10

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 1
A addLoader() 0 4 1
A addPostProcessor() 0 4 1
A getFilterConfiguration() 0 4 1
A applyPostProcessors() 0 14 3
A applyFilter() 0 9 1
F apply() 0 64 14
1
<?php
2
3
namespace Liip\ImagineBundle\Imagine\Filter;
4
5
use Imagine\Image\ImagineInterface;
6
use Liip\ImagineBundle\Binary\BinaryInterface;
7
use Liip\ImagineBundle\Binary\FileBinaryInterface;
8
use Liip\ImagineBundle\Binary\MimeTypeGuesserInterface;
9
use Liip\ImagineBundle\Imagine\Filter\PostProcessor\PostProcessorInterface;
10
use Liip\ImagineBundle\Imagine\Filter\Loader\LoaderInterface;
11
use Liip\ImagineBundle\Model\Binary;
12
13
class FilterManager
14
{
15
    /**
16
     * @var FilterConfiguration
17
     */
18
    protected $filterConfig;
19
20
    /**
21
     * @var ImagineInterface
22
     */
23
    protected $imagine;
24
25
    /**
26
     * @var MimeTypeGuesserInterface
27
     */
28
    protected $mimeTypeGuesser;
29
30
    /**
31
     * @var LoaderInterface[]
32
     */
33
    protected $loaders = array();
34
35
    /**
36
     * @var PostProcessorInterface[]
37
     */
38
    protected $postProcessors = array();
39
40
    /**
41
     * @param FilterConfiguration      $filterConfig
42
     * @param ImagineInterface         $imagine
43
     * @param MimeTypeGuesserInterface $mimeTypeGuesser
44
     */
45
    public function __construct(
46
        FilterConfiguration $filterConfig,
47
        ImagineInterface $imagine,
48
        MimeTypeGuesserInterface $mimeTypeGuesser
49
    ) {
50
        $this->filterConfig = $filterConfig;
51
        $this->imagine = $imagine;
52
        $this->mimeTypeGuesser = $mimeTypeGuesser;
53
    }
54
55
    /**
56
     * Adds a loader to handle the given filter.
57
     *
58
     * @param string          $filter
59
     * @param LoaderInterface $loader
60
     */
61
    public function addLoader($filter, LoaderInterface $loader)
62
    {
63
        $this->loaders[$filter] = $loader;
64
    }
65
66
    /**
67
     * Adds a post-processor to handle binaries.
68
     *
69
     * @param string                 $name
70
     * @param PostProcessorInterface $postProcessor
71
     */
72
    public function addPostProcessor($name, PostProcessorInterface $postProcessor)
73
    {
74
        $this->postProcessors[$name] = $postProcessor;
75
    }
76
77
    /**
78
     * @return FilterConfiguration
79
     */
80
    public function getFilterConfiguration()
81
    {
82
        return $this->filterConfig;
83
    }
84
85
    /**
86
     * @param BinaryInterface $binary
87
     * @param array           $config
88
     *
89
     * @throws \InvalidArgumentException
90
     *
91
     * @return Binary
92
     */
93
    public function apply(BinaryInterface $binary, array $config)
94
    {
95
        $config = array_replace(
96
            array(
97
                'filters' => array(),
98
                'quality' => 100,
99
                'animated' => false,
100
            ),
101
            $config
102
        );
103
104
        if ($binary instanceof FileBinaryInterface) {
105
            $image = $this->imagine->open($binary->getPath());
106
        } else {
107
            $image = $this->imagine->load($binary->getContent());
108
        }
109
110
        foreach ($config['filters'] as $eachFilter => $eachOptions) {
111
            if (!isset($this->loaders[$eachFilter])) {
112
                throw new \InvalidArgumentException(sprintf(
113
                    'Could not find filter loader for "%s" filter type', $eachFilter
114
                ));
115
            }
116
117
            $prevImage = $image;
118
            $image = $this->loaders[$eachFilter]->load($image, $eachOptions);
119
120
            // If the filter returns a different image object destruct the old one because imagick keeps consuming memory if we don't
121
            // See https://github.com/liip/LiipImagineBundle/pull/682
122
            if ($prevImage !== $image && method_exists($prevImage, '__destruct')) {
123
                $prevImage->__destruct();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Imagine\Image\ImageInterface as the method __destruct() does only exist in the following implementations of said interface: Imagine\Gd\Image, Imagine\Gmagick\Image, Imagine\Imagick\Image.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
124
            }
125
        }
126
127
        $options = array(
128
            'quality' => $config['quality'],
129
        );
130
131
        if (isset($config['jpeg_quality'])) {
132
            $options['jpeg_quality'] = $config['jpeg_quality'];
133
        }
134
        if (isset($config['png_compression_level'])) {
135
            $options['png_compression_level'] = $config['png_compression_level'];
136
        }
137
        if (isset($config['png_compression_filter'])) {
138
            $options['png_compression_filter'] = $config['png_compression_filter'];
139
        }
140
141
        if ($binary->getFormat() === 'gif' && $config['animated']) {
142
            $options['animated'] = $config['animated'];
143
        }
144
145
        $filteredFormat = isset($config['format']) ? $config['format'] : $binary->getFormat();
146
        $filteredContent = $image->get($filteredFormat, $options);
147
        $filteredMimeType = $filteredFormat === $binary->getFormat() ? $binary->getMimeType() : $this->mimeTypeGuesser->guess($filteredContent);
148
149
        // We are done with the image object so we can destruct the this because imagick keeps consuming memory if we don't
150
        // See https://github.com/liip/LiipImagineBundle/pull/682
151
        if (method_exists($image, '__destruct')) {
152
            $image->__destruct();
153
        }
154
155
        return $this->applyPostProcessors(new Binary($filteredContent, $filteredMimeType, $filteredFormat), $config);
156
    }
157
158
    /**
159
     * @param BinaryInterface $binary
160
     * @param array           $config
161
     *
162
     * @throws \InvalidArgumentException
163
     *
164
     * @return BinaryInterface
165
     */
166
    public function applyPostProcessors(BinaryInterface $binary, $config)
167
    {
168
        $config += array('post_processors' => array());
169
        foreach ($config['post_processors'] as $postProcessorName => $postProcessorOptions) {
170
            if (!isset($this->postProcessors[$postProcessorName])) {
171
                throw new \InvalidArgumentException(sprintf(
172
                    'Could not find post processor "%s"', $postProcessorName
173
                ));
174
            }
175
            $binary = $this->postProcessors[$postProcessorName]->process($binary);
176
        }
177
178
        return $binary;
179
    }
180
181
    /**
182
     * Apply the provided filter set on the given binary.
183
     *
184
     * @param BinaryInterface $binary
185
     * @param string          $filter
186
     * @param array           $runtimeConfig
187
     *
188
     * @throws \InvalidArgumentException
189
     *
190
     * @return BinaryInterface
191
     */
192
    public function applyFilter(BinaryInterface $binary, $filter, array $runtimeConfig = array())
193
    {
194
        $config = array_replace_recursive(
195
            $this->getFilterConfiguration()->get($filter),
196
            $runtimeConfig
197
        );
198
199
        return $this->apply($binary, $config);
200
    }
201
}
202