Completed
Push — master ( bee941...29f099 )
by Łukasz
29:47
created

ResizeOriginalImagesCommand::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 9
dl 0
loc 23
rs 9.552
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/**
4
 * @copyright Copyright (C) eZ Systems AS. All rights reserved.
5
 * @license For full copyright and license information view LICENSE file distributed with this source code.
6
 */
7
declare(strict_types=1);
8
9
namespace eZ\Bundle\EzPublishCoreBundle\Command;
10
11
use eZ\Publish\API\Repository\ContentService;
12
use eZ\Publish\API\Repository\ContentTypeService;
13
use eZ\Publish\API\Repository\PermissionResolver;
14
use eZ\Publish\API\Repository\SearchService;
15
use eZ\Publish\API\Repository\UserService;
16
use eZ\Publish\API\Repository\Values\Content\Query;
17
use eZ\Publish\API\Repository\Values\Content\Search\SearchHit;
18
use eZ\Publish\Core\FieldType\Image\Value;
19
use eZ\Publish\Core\IO\IOServiceInterface;
20
use eZ\Publish\Core\IO\Values\BinaryFile;
21
use Imagine\Image\ImagineInterface;
22
use Liip\ImagineBundle\Binary\BinaryInterface;
23
use Liip\ImagineBundle\Exception\Imagine\Filter\NonExistingFilterException;
24
use Liip\ImagineBundle\Imagine\Filter\FilterManager;
25
use Liip\ImagineBundle\Model\Binary;
26
use Symfony\Component\Console\Command\Command;
27
use Symfony\Component\Console\Helper\ProgressBar;
28
use Symfony\Component\Console\Input\InputArgument;
29
use Symfony\Component\Console\Input\InputInterface;
30
use Symfony\Component\Console\Input\InputOption;
31
use Symfony\Component\Console\Output\OutputInterface;
32
use Symfony\Component\Console\Question\ConfirmationQuestion;
33
use Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface;
34
use Exception;
35
36
/**
37
 * This command resizes original images stored in ezimage FieldType in given ContentType using the selected filter.
38
 */
39
class ResizeOriginalImagesCommand extends Command
40
{
41
    const DEFAULT_ITERATION_COUNT = 25;
42
    const DEFAULT_REPOSITORY_USER = 'admin';
43
44
    /**
45
     * @var \eZ\Publish\API\Repository\PermissionResolver
46
     */
47
    private $permissionResolver;
48
49
    /**
50
     * @var \eZ\Publish\API\Repository\UserService
51
     */
52
    private $userService;
53
54
    /**
55
     * @var \eZ\Publish\API\Repository\ContentTypeService
56
     */
57
    private $contentTypeService;
58
59
    /**
60
     * @var \eZ\Publish\API\Repository\ContentService
61
     */
62
    private $contentService;
63
64
    /**
65
     * @var \eZ\Publish\API\Repository\SearchService
66
     */
67
    private $searchService;
68
69
    /**
70
     * @var \Liip\ImagineBundle\Imagine\Filter\FilterManager
71
     */
72
    private $filterManager;
73
74
    /**
75
     * @var \eZ\Publish\Core\IO\IOServiceInterface
76
     */
77
    private $ioService;
78
79
    /**
80
     * @var \Symfony\Component\HttpFoundation\File\MimeType\ExtensionGuesserInterface
81
     */
82
    private $extensionGuesser;
83
84
    /**
85
     * @var \Imagine\Image\ImagineInterface
86
     */
87
    private $imagine;
88
89
    public function __construct(
90
        PermissionResolver $permissionResolver,
91
        UserService $userService,
92
        ContentTypeService $contentTypeService,
93
        ContentService $contentService,
94
        SearchService $searchService,
95
        FilterManager $filterManager,
96
        IOServiceInterface $ioService,
97
        ExtensionGuesserInterface $extensionGuesser,
98
        ImagineInterface $imagine
99
    ) {
100
        $this->permissionResolver = $permissionResolver;
101
        $this->userService = $userService;
102
        $this->contentTypeService = $contentTypeService;
103
        $this->contentService = $contentService;
104
        $this->searchService = $searchService;
105
        $this->filterManager = $filterManager;
106
        $this->ioService = $ioService;
107
        $this->extensionGuesser = $extensionGuesser;
108
        $this->imagine = $imagine;
109
110
        parent::__construct();
111
    }
112
113
    protected function initialize(InputInterface $input, OutputInterface $output)
114
    {
115
        parent::initialize($input, $output);
116
117
        $this->permissionResolver->setCurrentUserReference(
118
            $this->userService->loadUserByLogin($input->getOption('user'))
119
        );
120
    }
121
122
    protected function configure()
123
    {
124
        $this->setName('ezplatform:images:resize-original')->setDefinition(
125
            [
126
                new InputArgument('contentTypeIdentifier', InputArgument::REQUIRED,
127
                    'Indentifier of ContentType which has ezimage FieldType.'),
128
                new InputArgument('imageFieldIdentifier', InputArgument::REQUIRED,
129
                    'Identifier of field of ezimage type.'),
130
            ]
131
        )
132
        ->addOption(
133
                'filter',
134
                'f',
135
                InputOption::VALUE_REQUIRED,
136
                'Filter which will be used for original images.'
137
        )
138
        ->addOption(
139
            'iteration-count',
140
            'i',
141
            InputOption::VALUE_OPTIONAL,
142
            'Iteration count. Number of images to be recreated in a single iteration, for avoiding using too much memory.',
143
            self::DEFAULT_ITERATION_COUNT
144
        )
145
        ->addOption(
146
            'user',
147
            'u',
148
            InputOption::VALUE_OPTIONAL,
149
            'eZ Platform username (with Role containing at least Content policies: read, versionread, edit, publish)',
150
            self::DEFAULT_REPOSITORY_USER
151
        );
152
    }
153
154
    protected function execute(InputInterface $input, OutputInterface $output)
155
    {
156
        $contentTypeIdentifier = $input->getArgument('contentTypeIdentifier');
157
        $imageFieldIdentifier = $input->getArgument('imageFieldIdentifier');
158
        $filter = $input->getOption('filter');
159
        $iterationCount = (int)$input->getOption('iteration-count');
160
161
        $contentType = $this->contentTypeService->loadContentTypeByIdentifier($contentTypeIdentifier);
162
        $fieldType = $contentType->getFieldDefinition($imageFieldIdentifier);
163
        if (!$fieldType || $fieldType->fieldTypeIdentifier !== 'ezimage') {
164
            $output->writeln(
165
                sprintf(
166
                    "<error>FieldType of identifier '%s' of ContentType '%s' has to be 'ezimage', '%s' given.</error>",
167
                    $imageFieldIdentifier,
168
                    $contentType->identifier,
169
                    $fieldType ? $fieldType->fieldTypeIdentifier : ''
170
                )
171
            );
172
173
            return;
174
        }
175
176
        try {
177
            $this->filterManager->getFilterConfiguration()->get($filter);
178
        } catch (NonExistingFilterException $e) {
179
            $output->writeln(
180
                sprintf(
181
                    '<error>%s</error>',
182
                    $e->getMessage()
183
                )
184
            );
185
186
            return;
187
        }
188
189
        $query = new Query();
190
        $query->filter = new Query\Criterion\ContentTypeIdentifier($contentType->identifier);
191
192
        $totalCount = $this->searchService->findContent($query)->totalCount;
193
        $query->limit = $iterationCount;
194
195
        if ($totalCount > 0) {
196
            $output->writeln(
197
                sprintf(
198
                    '<info>Found %d images matching given criteria.</info>',
199
                    $totalCount
200
                )
201
            );
202
        } else {
203
            $output->writeln(
204
                sprintf(
205
                    '<info>No images matching given criteria (ContentType: %s, FieldType %s) found. Exiting.</info>',
206
                    $contentTypeIdentifier,
207
                    $imageFieldIdentifier
208
                )
209
            );
210
211
            return;
212
        }
213
214
        $helper = $this->getHelper('question');
215
        $question = new ConfirmationQuestion('<question>The changes you are going to perform cannot be undone. Please remember to do a proper backup before. Would you like to continue?</question> ', false);
216
        if (!$helper->ask($input, $output, $question)) {
217
            return;
218
        }
219
220
        $progressBar = new ProgressBar($output, $totalCount);
221
        $progressBar->start();
222
223
        while ($query->offset <= $totalCount) {
224
            $results = $this->searchService->findContent($query);
225
226
            /** @var \eZ\Publish\API\Repository\Values\Content\Search\SearchHit $hit */
227
            foreach ($results->searchHits as $hit) {
228
                $this->resize($output, $hit, $imageFieldIdentifier, $filter);
229
                $progressBar->advance();
230
            }
231
232
            $query->offset += $iterationCount;
233
        }
234
235
        $progressBar->finish();
236
        $output->writeln('');
237
        $output->writeln(
238
            sprintf(
239
                "<info>All images have been successfully resized using '%s' filter.</info>",
240
                $filter
241
            )
242
        );
243
    }
244
245
    /**
246
     * @param \Symfony\Component\Console\Output\OutputInterface $output
247
     * @param \eZ\Publish\API\Repository\Values\Content\Search\SearchHit $hit
248
     * @param string $imageFieldIdentifier
249
     * @param string $filter
250
     */
251
    private function resize(OutputInterface $output, SearchHit $hit, string $imageFieldIdentifier, string $filter): void
252
    {
253
        try {
254
            /** @var \eZ\Publish\Core\FieldType\Image\Value $field */
255
            foreach ($hit->valueObject->fields[$imageFieldIdentifier] as $language => $field) {
0 ignored issues
show
Documentation introduced by
The property fields does not exist on object<eZ\Publish\API\Re...ory\Values\ValueObject>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
256
                if (null === $field->id) {
257
                    continue;
258
                }
259
                $binaryFile = $this->ioService->loadBinaryFile($field->id);
260
                $mimeType = $this->ioService->getMimeType($field->id);
261
                $binary = new Binary(
262
                    $this->ioService->getFileContents($binaryFile),
263
                    $mimeType,
264
                    $this->extensionGuesser->guess($mimeType)
265
                );
266
267
                $resizedImageBinary = $this->filterManager->applyFilter($binary, $filter);
268
                $newBinaryFile = $this->store($resizedImageBinary, $field);
269
                $image = $this->imagine->load($this->ioService->getFileContents($newBinaryFile));
270
                $dimensions = $image->getSize();
271
272
                $contentDraft = $this->contentService->createContentDraft($hit->valueObject->getVersionInfo()->getContentInfo(), $hit->valueObject->getVersionInfo());
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class eZ\Publish\API\Repository\Values\ValueObject as the method getVersionInfo() does only exist in the following sub-classes of eZ\Publish\API\Repository\Values\ValueObject: eZ\Publish\API\Repository\Values\Content\Content, eZ\Publish\API\Repository\Values\User\User, eZ\Publish\API\Repository\Values\User\UserGroup, eZ\Publish\Core\REST\Client\Values\Content\Content, eZ\Publish\Core\REST\Client\Values\User\User, eZ\Publish\Core\REST\Client\Values\User\UserGroup, eZ\Publish\Core\Repository\Values\Content\Content, eZ\Publish\Core\Reposito...es\Content\ContentProxy, eZ\Publish\Core\Repository\Values\User\User, eZ\Publish\Core\Repository\Values\User\UserGroup. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

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

class MyUser extends 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 sub-classes 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 parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
273
                $contentUpdateStruct = $this->contentService->newContentUpdateStruct();
274
                $contentUpdateStruct->setField($imageFieldIdentifier, [
275
                    'id' => $field->id,
276
                    'alternativeText' => $field->alternativeText,
277
                    'fileName' => $field->fileName,
278
                    'fileSize' => $newBinaryFile->size,
279
                    'imageId' => $field->imageId,
280
                    'width' => $dimensions->getWidth(),
281
                    'height' => $dimensions->getHeight(),
282
                ]);
283
                $contentDraft = $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
284
                $this->contentService->updateContent($contentDraft->versionInfo, $contentUpdateStruct);
285
                $this->contentService->publishVersion($contentDraft->versionInfo);
286
            }
287
        } catch (Exception $e) {
288
            $output->writeln(
289
                sprintf(
290
                    '<error>Can not resize image ID: %s, error message: %s.</error>',
291
                    $field->imageId,
0 ignored issues
show
Bug introduced by
The variable $field seems to be defined by a foreach iteration on line 255. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
292
                    $e->getMessage()
293
                )
294
            );
295
        }
296
    }
297
298
    /**
299
     * Copy of eZ\Bundle\EzPublishCoreBundle\Imagine\IORepositoryResolver::store()
300
     * Original one cannot be used since original method uses eZ\Bundle\EzPublishCoreBundle\Imagine\IORepositoryResolver::getFilePath()
301
     * so ends-up with image stored in _aliases instead of overwritten original image.
302
     *
303
     * @param \Liip\ImagineBundle\Binary\BinaryInterface $binary
304
     * @param \eZ\Publish\Core\FieldType\Image\Value $image
305
     *
306
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentException
307
     * @throws \eZ\Publish\Core\Base\Exceptions\InvalidArgumentValue
308
     *
309
     * @return \eZ\Publish\Core\IO\Values\BinaryFile
310
     */
311
    private function store(BinaryInterface $binary, Value $image): BinaryFile
312
    {
313
        $tmpFile = tmpfile();
314
        fwrite($tmpFile, $binary->getContent());
315
        $tmpMetadata = stream_get_meta_data($tmpFile);
316
        $binaryCreateStruct = $this->ioService->newBinaryCreateStructFromLocalFile($tmpMetadata['uri']);
317
        $binaryCreateStruct->id = $image->id;
318
        $newBinaryFile = $this->ioService->createBinaryFile($binaryCreateStruct);
319
        fclose($tmpFile);
320
321
        return $newBinaryFile;
322
    }
323
}
324