Completed
Push — master ( 0b3034...a98e2b )
by
unknown
36:06 queued 14:31
created

RegenerateUrlAliasesCommand::processLocations()   A

Complexity

Conditions 4
Paths 24

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 24
nop 2
dl 0
loc 52
rs 9.0472
c 0
b 0
f 0

How to fix   Long Method   

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
/**
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
namespace eZ\Bundle\EzPublishCoreBundle\Command;
8
9
use Exception;
10
use eZ\Publish\API\Repository\Repository;
11
use eZ\Publish\API\Repository\Values\Content\Language;
12
use eZ\Publish\API\Repository\Values\Content\Location;
13
use Psr\Log\LoggerInterface;
14
use Psr\Log\NullLogger;
15
use Symfony\Component\Console\Command\Command;
16
use Symfony\Component\Console\Helper\ProgressBar;
17
use Symfony\Component\Console\Input\InputInterface;
18
use Symfony\Component\Console\Input\InputOption;
19
use Symfony\Component\Console\Output\OutputInterface;
20
use Symfony\Component\Console\Question\ConfirmationQuestion;
21
22
/**
23
 * The ezplatform:urls:regenerate-aliases Symfony command implementation.
24
 * Recreates system URL aliases for all existing Locations and cleanups corrupted URL alias nodes.
25
 */
26
class RegenerateUrlAliasesCommand extends Command
27
{
28
    const DEFAULT_ITERATION_COUNT = 1000;
29
30
    const BEFORE_RUNNING_HINTS = <<<EOT
31
<error>Before you continue:</error>
32
- Make sure to back up your database.
33
- Take installation offline, during the script execution the database should not be modified.
34
- Run this command without memory limit, i.e. processing of 300k Locations can take up to 1 GB of RAM.
35
- Run this command in production environment using <info>--env=prod</info>
36
EOT;
37
38
    /**
39
     * @var \eZ\Publish\API\Repository\Repository
40
     */
41
    private $repository;
42
43
    /**
44
     * @var \Psr\Log\LoggerInterface
45
     */
46
    private $logger;
47
48
    /**
49
     * @param \eZ\Publish\API\Repository\Repository $repository
50
     * @param \Psr\Log\LoggerInterface $logger
51
     */
52
    public function __construct(Repository $repository, LoggerInterface $logger = null)
53
    {
54
        parent::__construct();
55
56
        $this->repository = $repository;
57
        $this->logger = null !== $logger ? $logger : new NullLogger();
58
    }
59
60
    /**
61
     * {@inheritdoc}
62
     */
63
    protected function configure()
64
    {
65
        $beforeRunningHints = self::BEFORE_RUNNING_HINTS;
66
        $this
67
            ->setName('ezplatform:urls:regenerate-aliases')
68
            ->setDescription(
69
                'Regenerates Location URL aliases (autogenerated) and cleans up custom Location ' .
70
                'and global URL aliases stored in the Legacy Storage Engine'
71
            )
72
            ->addOption(
73
                'iteration-count',
74
                'c',
75
                InputOption::VALUE_OPTIONAL,
76
                'Number of Locations fetched into memory and processed at once',
77
                self::DEFAULT_ITERATION_COUNT
78
            )
79
            ->setHelp(
80
                <<<EOT
81
{$beforeRunningHints}
82
83
The command <info>%command.name%</info> regenerates URL aliases for Locations and cleans up
84
corrupted URL aliases (pointing to non-existent Locations).
85
Existing aliases are archived (will redirect to the new ones).
86
87
Note: This script can potentially run for a very long time.
88
89
Due to performance issues the command does not send any Signals.
90
91
<comment>HTTP cache needs to be cleared manually after executing this command.</comment>
92
93
EOT
94
            );
95
    }
96
97
    /**
98
     * Regenerate URL aliases.
99
     *
100
     * {@inheritdoc}
101
     */
102
    protected function execute(InputInterface $input, OutputInterface $output)
103
    {
104
        $iterationCount = (int)$input->getOption('iteration-count');
105
106
        $locationsCount = $this->repository->sudo(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\API\Repository\Repository as the method sudo() does only exist in the following implementations of said interface: eZ\Publish\Core\Repository\Repository, eZ\Publish\Core\Reposito...eAccessAware\Repository, eZ\Publish\Core\SignalSlot\Repository.

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...
107
            function (Repository $repository) {
108
                return $repository->getLocationService()->getAllLocationsCount();
109
            }
110
        );
111
112
        $helper = $this->getHelper('question');
113
        $question = new ConfirmationQuestion(
114
            sprintf(
115
                "<info>Found %d Locations.</info>\n%s\n<info>Do you want to proceed? [y/N] </info>",
116
                $locationsCount,
117
                self::BEFORE_RUNNING_HINTS
118
            ),
119
            false
120
        );
121
        if (!$helper->ask($input, $output, $question)) {
122
            return;
123
        }
124
125
        $output->writeln('<info>Cleaning up corrupted URL aliases...</info>');
126
        $corruptedAliasesCount = $this->repository->sudo(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\API\Repository\Repository as the method sudo() does only exist in the following implementations of said interface: eZ\Publish\Core\Repository\Repository, eZ\Publish\Core\Reposito...eAccessAware\Repository, eZ\Publish\Core\SignalSlot\Repository.

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...
127
            function (Repository $repository) {
128
                return $repository->getURLAliasService()->deleteCorruptedUrlAliases();
129
            }
130
        );
131
        $output->writeln("<info>Done. Deleted {$corruptedAliasesCount} entries.</info>");
132
133
        $output->writeln('Regenerating System URL aliases...');
134
135
        $progressBar = $this->getProgressBar($locationsCount, $output);
136
        $progressBar->start();
137
138
        for ($offset = 0; $offset <= $locationsCount; $offset += $iterationCount) {
139
            gc_disable();
140
            $locations = $this->repository->sudo(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\API\Repository\Repository as the method sudo() does only exist in the following implementations of said interface: eZ\Publish\Core\Repository\Repository, eZ\Publish\Core\Reposito...eAccessAware\Repository, eZ\Publish\Core\SignalSlot\Repository.

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...
141
                function (Repository $repository) use ($offset, $iterationCount) {
142
                    return $repository->getLocationService()->loadAllLocations($offset, $iterationCount);
143
                }
144
            );
145
            $this->processLocations($locations, $progressBar);
146
            gc_enable();
147
        }
148
        $progressBar->finish();
149
        $output->writeln('');
150
        $output->writeln('<info>Done.</info>');
151
    }
152
153
    /**
154
     * Return configured progress bar helper.
155
     *
156
     * @param int $maxSteps
157
     * @param \Symfony\Component\Console\Output\OutputInterface $output
158
     *
159
     * @return \Symfony\Component\Console\Helper\ProgressBar
160
     */
161
    protected function getProgressBar($maxSteps, OutputInterface $output)
162
    {
163
        $progressBar = new ProgressBar($output, $maxSteps);
164
        $progressBar->setFormat(
165
            ' %current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s% %memory:6s%'
166
        );
167
168
        return $progressBar;
169
    }
170
171
    /**
172
     * Process single results page of fetched Locations.
173
     *
174
     * @param \eZ\Publish\API\Repository\Values\Content\Location[] $locations
175
     * @param \Symfony\Component\Console\Helper\ProgressBar $progressBar
176
     */
177
    private function processLocations(array $locations, ProgressBar $progressBar)
178
    {
179
        $contentList = $this->repository->sudo(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\API\Repository\Repository as the method sudo() does only exist in the following implementations of said interface: eZ\Publish\Core\Repository\Repository, eZ\Publish\Core\Reposito...eAccessAware\Repository, eZ\Publish\Core\SignalSlot\Repository.

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...
180
            function (Repository $repository) use ($locations) {
181
                $contentInfoList = array_map(
182
                    function (Location $location) {
183
                        return $location->contentInfo;
184
                    },
185
                    $locations
186
                );
187
188
                // load Content list in all languages
189
                return $repository->getContentService()->loadContentListByContentInfo(
190
                    $contentInfoList,
191
                    Language::ALL,
192
                    false
193
                );
194
            }
195
        );
196
        foreach ($locations as $location) {
197
            try {
198
                // ignore missing Content items
199
                if (!isset($contentList[$location->contentId])) {
200
                    continue;
201
                }
202
203
                $content = $contentList[$location->contentId];
204
                $this->repository->sudo(
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface eZ\Publish\API\Repository\Repository as the method sudo() does only exist in the following implementations of said interface: eZ\Publish\Core\Repository\Repository, eZ\Publish\Core\Reposito...eAccessAware\Repository, eZ\Publish\Core\SignalSlot\Repository.

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...
205
                    function (Repository $repository) use ($location, $content) {
206
                        $repository->getURLAliasService()->refreshSystemUrlAliasesForLocation(
207
                            $location
208
                        );
209
                    }
210
                );
211
            } catch (Exception $e) {
212
                $contentInfo = $location->getContentInfo();
213
                $msg = sprintf(
214
                    'Failed processing location %d - [%d] %s (%s: %s)',
215
                    $location->id,
216
                    $contentInfo->id,
217
                    $contentInfo->name,
218
                    get_class($e),
219
                    $e->getMessage()
220
                );
221
                $this->logger->warning($msg);
222
                // in debug mode log full exception with a trace
223
                $this->logger->debug($e);
224
            } finally {
225
                $progressBar->advance(1);
226
            }
227
        }
228
    }
229
}
230