SyncThumbsCommand::execute()   C
last analyzed

Complexity

Conditions 10
Paths 16

Size

Total Lines 96

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 96
rs 6.2206
c 0
b 0
f 0
cc 10
nc 16
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Sonata Project package.
7
 *
8
 * (c) Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Sonata\MediaBundle\Command;
15
16
use Sonata\MediaBundle\Model\MediaInterface;
17
use Sonata\MediaBundle\Provider\MediaProviderInterface;
18
use Symfony\Component\Console\Input\InputArgument;
19
use Symfony\Component\Console\Input\InputInterface;
20
use Symfony\Component\Console\Input\InputOption;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Console\Question\ChoiceQuestion;
23
24
/**
25
 * This command can be used to re-generate the thumbnails for all uploaded medias.
26
 *
27
 * Useful if you have existing media content and added new formats.
28
 *
29
 * @final since sonata-project/media-bundle 3.21.0
30
 */
31
class SyncThumbsCommand extends BaseCommand
0 ignored issues
show
Deprecated Code introduced by
The class Sonata\MediaBundle\Command\BaseCommand has been deprecated with message: since sonata-project/media-bundle 3.26, to be removed in 4.0.

This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.

Loading history...
32
{
33
    /**
34
     * @var bool
35
     */
36
    protected $quiet = false;
37
38
    /**
39
     * @var OutputInterface
40
     */
41
    protected $output;
42
43
    public function configure(): void
44
    {
45
        $this->setName('sonata:media:sync-thumbnails')
46
            ->setDescription('Sync uploaded image thumbs with new media formats')
47
            ->setDefinition(
48
                [
49
                new InputArgument('providerName', InputArgument::OPTIONAL, 'The provider'),
50
                new InputArgument('context', InputArgument::OPTIONAL, 'The context'),
51
                new InputOption('batchSize', null, InputOption::VALUE_REQUIRED, 'Media batch size (100 by default)', 100),
52
                new InputOption('batchesLimit', null, InputOption::VALUE_REQUIRED, 'Media batches limit (0 by default)', 0),
53
                new InputOption('startOffset', null, InputOption::VALUE_REQUIRED, 'Medias start offset (0 by default)', 0),
54
            ]
55
            );
56
    }
57
58
    public function execute(InputInterface $input, OutputInterface $output): int
59
    {
60
        $helper = $this->getHelper('question');
61
62
        $providerName = $input->getArgument('providerName');
63
        if (null === $providerName) {
64
            $providers = array_keys($this->getMediaPool()->getProviders());
65
            $question = new ChoiceQuestion('Please select the provider', $providers, 0);
66
            $question->setErrorMessage('Provider %s is invalid.');
67
68
            $providerName = $helper->ask($input, $output, $question);
69
        }
70
71
        $context = $input->getArgument('context');
72
        if (null === $context) {
73
            $contexts = array_keys($this->getMediaPool()->getContexts());
74
            $question = new ChoiceQuestion('Please select the context', $contexts, 0);
75
            $question->setErrorMessage('Context %s is invalid.');
76
77
            $context = $helper->ask($input, $output, $question);
78
        }
79
80
        $this->quiet = $input->getOption('quiet');
0 ignored issues
show
Documentation Bug introduced by
It seems like $input->getOption('quiet') can also be of type string or array<integer,string>. However, the property $quiet is declared as type boolean. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
81
        $this->output = $output;
82
83
        $provider = $this->getMediaPool()->getProvider($providerName);
84
85
        $filesystem = $provider->getFilesystem();
86
        $fsReflection = new \ReflectionClass($filesystem);
87
        $fsRegister = $fsReflection->getProperty('fileRegister');
88
        $fsRegister->setAccessible(true);
89
90
        $batchCounter = 0;
91
        $batchSize = (int) $input->getOption('batchSize');
92
        $batchesLimit = (int) $input->getOption('batchesLimit');
93
        $startOffset = (int) $input->getOption('startOffset');
94
        $totalMediasCount = 0;
95
        do {
96
            ++$batchCounter;
97
98
            try {
99
                $batchOffset = $startOffset + ($batchCounter - 1) * $batchSize;
100
                $medias = $this->getMediaManager()->findBy(
101
                    [
102
                        'providerName' => $providerName,
103
                        'context' => $context,
104
                    ],
105
                    [
106
                        'id' => 'ASC',
107
                    ],
108
                    $batchSize,
109
                    $batchOffset
110
                );
111
            } catch (\Exception $e) {
112
                $this->log('Error: '.$e->getMessage());
113
114
                break;
115
            }
116
117
            $batchMediasCount = \count($medias);
118
            if (0 === $batchMediasCount) {
119
                break;
120
            }
121
122
            $totalMediasCount += $batchMediasCount;
123
            $this->log(
124
                sprintf(
125
                    'Loaded %s medias (batch #%d, offset %d) for generating thumbs (provider: %s, context: %s)',
126
                    $batchMediasCount,
127
                    $batchCounter,
128
                    $batchOffset,
129
                    $providerName,
130
                    $context
131
                )
132
            );
133
134
            foreach ($medias as $media) {
135
                if (!$this->processMedia($media, $provider)) {
136
                    continue;
137
                }
138
                //clean filesystem registry for saving memory
139
                $fsRegister->setValue($filesystem, []);
140
            }
141
142
            //clear entity manager for saving memory
143
            $this->getMediaManager()->getObjectManager()->clear();
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Sonata\Doctrine\Model\ManagerInterface as the method getObjectManager() does only exist in the following implementations of said interface: Sonata\Doctrine\Document\BaseDocumentManager, Sonata\Doctrine\Document\BasePHPCRManager, Sonata\Doctrine\Entity\BaseEntityManager, Sonata\Doctrine\Model\BaseManager, Sonata\MediaBundle\Document\GalleryManager, Sonata\MediaBundle\Document\MediaManager, Sonata\MediaBundle\Entity\GalleryManager, Sonata\MediaBundle\Entity\MediaManager, Sonata\MediaBundle\PHPCR\GalleryManager, Sonata\MediaBundle\PHPCR\MediaManager, Sonata\NotificationBundle\Entity\MessageManager.

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...
144
145
            if ($batchesLimit > 0 && $batchCounter === $batchesLimit) {
146
                break;
147
            }
148
        } while (true);
149
150
        $this->log("Done (total medias processed: {$totalMediasCount}).");
151
152
        return 0;
153
    }
154
155
    /**
156
     * @param MediaInterface         $media
157
     * @param MediaProviderInterface $provider
158
     */
159
    protected function processMedia($media, $provider): bool
160
    {
161
        $this->log('Generating thumbs for '.$media->getName().' - '.$media->getId());
162
163
        try {
164
            $provider->removeThumbnails($media);
165
        } catch (\Exception $e) {
166
            $this->log(sprintf(
167
                '<error>Unable to remove old thumbnails, media: %s - %s </error>',
168
                $media->getId(),
169
                $e->getMessage()
170
            ));
171
172
            return false;
173
        }
174
175
        try {
176
            $provider->generateThumbnails($media);
177
        } catch (\Exception $e) {
178
            $this->log(sprintf(
179
                '<error>Unable to generate new thumbnails, media: %s - %s </error>',
180
                $media->getId(),
181
                $e->getMessage()
182
            ));
183
184
            return false;
185
        }
186
187
        return true;
188
    }
189
190
    /**
191
     * Write a message to the output.
192
     *
193
     * @param string $message
194
     */
195
    protected function log($message): void
196
    {
197
        if (false === $this->quiet) {
198
            $this->output->writeln($message);
199
        }
200
    }
201
}
202