Completed
Pull Request — 2.x (#345)
by
unknown
01:48
created

SitemapGeneratorCommand::setContainer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
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\SeoBundle\Command;
15
16
use Sonata\Exporter\Handler;
17
use Sonata\Exporter\Writer\SitemapWriter;
18
use Sonata\SeoBundle\Sitemap\SourceManager;
19
use Symfony\Component\Console\Command\Command;
20
use Symfony\Component\Console\Input\InputArgument;
21
use Symfony\Component\Console\Input\InputInterface;
22
use Symfony\Component\Console\Input\InputOption;
23
use Symfony\Component\Console\Output\OutputInterface;
24
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
25
use Symfony\Component\DependencyInjection\ContainerInterface;
26
use Symfony\Component\Filesystem\Filesystem;
27
use Symfony\Component\Finder\Finder;
28
use Symfony\Component\Routing\RequestContext;
29
use Symfony\Component\Routing\RouterInterface;
30
31
/**
32
 * Create a sitemap.
33
 *
34
 * @author Thomas Rabaix <[email protected]>
35
 */
36
class SitemapGeneratorCommand extends Command implements ContainerAwareInterface
37
{
38
    /**
39
     * @var RouterInterface
40
     */
41
    private $router;
42
43
    /**
44
     * @var SourceManager
45
     */
46
    private $sitemapManager;
47
48
    /**
49
     * @var Filesystem
50
     */
51
    private $fs;
52
53
    /**
54
     * @deprecated
55
     * @var ContainerInterface|null
56
     */
57
    private $container;
58
59
    public function __construct(?RouterInterface $router = null, ?SourceManager $sitemapManager = null, ?Filesystem $fs = null)
60
    {
61
        $this->router = $router;
62
        $this->sitemapManager = $sitemapManager;
63
        $this->fs = $fs;
64
65
        parent::__construct();
66
    }
67
68
    /**
69
     * @deprecated
70
     * @todo Remove deprecated methods, remove interface implementation, cleanup 'use' block.
71
     * @todo Make arguments of __construct required instead of optional.
72
     */
73
    public function setContainer(ContainerInterface $container = null)
74
    {
75
        @trigger_error('Please, use the new method of console command declaration.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
76
77
        $this->router = $container->get('router');
0 ignored issues
show
Bug introduced by
It seems like $container is not always an object, but can also be of type null. Maybe add an additional type check?

If a variable is not always an object, we recommend to add an additional type check to ensure your method call is safe:

function someFunction(A $objectMaybe = null)
{
    if ($objectMaybe instanceof A) {
        $objectMaybe->doSomething();
    }
}
Loading history...
78
        $this->sitemapManager = $container->get('sonata.seo.sitemap.manager');
79
        $this->fs = $container->get('filesystem');
80
        $this->container = $container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
81
    }
82
83
    /**
84
     * @deprecated
85
     */
86
    public function getContainer(): ?ContainerInterface
87
    {
88
        @trigger_error('Please, avoid usage of container in your services.', E_USER_DEPRECATED);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
89
90
        return $this->container;
0 ignored issues
show
Deprecated Code introduced by
The property Sonata\SeoBundle\Command...atorCommand::$container has been deprecated.

This property has been deprecated. The supplier of the class has supplied an explanatory message.

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

Loading history...
91
    }
92
93
    /**
94
     * {@inheritdoc}
95
     */
96
    public function configure()
97
    {
98
        $this->setName('sonata:seo:sitemap');
99
100
        $this->addArgument('folder', InputArgument::REQUIRED, 'The folder to store the sitemap.xml file');
101
        $this->addArgument('host', InputArgument::REQUIRED, 'Set the host');
102
        $this->addOption('scheme', null, InputOption::VALUE_OPTIONAL, 'Set the scheme', 'http');
103
        $this->addOption('baseurl', null, InputOption::VALUE_OPTIONAL, 'Set the base url', '');
104
        $this->addOption('sitemap_path', null, InputOption::VALUE_OPTIONAL, 'Set the sitemap relative path (if in a specific folder)', '');
105
106
        $this->setDescription('Create a sitemap');
107
        $this->setHelp(<<<'EOT'
108
The <info>sonata:seo:sitemap</info> command create new sitemap files (index + sitemap).
109
110
EOT
111
        );
112
    }
113
114
    /**
115
     * {@inheritdoc}
116
     */
117
    public function execute(InputInterface $input, OutputInterface $output)
118
    {
119
        $host = $input->getArgument('host');
120
        $scheme = $input->getOption('scheme');
121
        $baseUrl = $input->getOption('baseurl');
122
        $permanentFolder = $input->getArgument('folder');
123
        $appendPath = $input->hasOption('sitemap_path') ? $input->getOption('sitemap_path') : $baseUrl;
124
125
        $this->getContext()->setHost($host);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 119 can also be of type array<integer,string> or null; however, Symfony\Component\Routin...questContext::setHost() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
126
        $this->getContext()->setScheme($scheme);
127
        $this->getContext()->setBaseUrl($baseUrl);
128
129
        $tempFolder = $this->createTempFolder($output);
130
        if (null === $tempFolder) {
131
            $output->writeln('<error>The temporary folder already exists</error>');
132
            $output->writeln('<error>If the task is not running please delete this folder</error>');
133
            return 1;
134
        }
135
136
        $output->writeln(sprintf('Generating sitemap - this can take a while'));
137
        $this->generateSitemap($tempFolder, $scheme, $host, $appendPath);
0 ignored issues
show
Bug introduced by
It seems like $host defined by $input->getArgument('host') on line 119 can also be of type array<integer,string> or null; however, Sonata\SeoBundle\Command...mand::generateSitemap() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
138
139
        $output->writeln(sprintf('Moving temporary file to %s ...', $permanentFolder));
140
        $this->moveTemporaryFile($tempFolder, $permanentFolder);
0 ignored issues
show
Bug introduced by
It seems like $permanentFolder defined by $input->getArgument('folder') on line 122 can also be of type array<integer,string> or null; however, Sonata\SeoBundle\Command...nd::moveTemporaryFile() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
141
142
        $output->writeln('Cleanup ...');
143
        $this->fs->remove($tempFolder);
144
145
        $output->writeln('<info>done!</info>');
146
    }
147
148
    private function getContext(): RequestContext
149
    {
150
        return $this->router->getContext();
151
    }
152
153
    /**
154
     * Creates temporary folder if one does not exist.
155
     * @return string|null Folder name or null if folder is already exist
156
     */
157
    private function createTempFolder(OutputInterface $output)
158
    {
159
        $tempFolder = sys_get_temp_dir().'/sonata_sitemap_'.md5(__DIR__);
160
161
        $output->writeln(sprintf('Creating temporary folder: %s', $tempFolder));
162
163
        if ($this->fs->exists($tempFolder)) {
164
            return null;
165
        }
166
167
        $this->fs->mkdir($tempFolder);
168
169
        return $tempFolder;
170
    }
171
172
    /**
173
     * @throws \Exception
174
     */
175
    private function generateSitemap(string $folder, string $scheme, string $host, string $appendPath): void
176
    {
177
        foreach ($this->sitemapManager as $group => $sitemap) {
178
            $write = new SitemapWriter($folder, $group, $sitemap->types, false);
179
180
            try {
181
                Handler::create($sitemap->sources, $write)->export();
182
            } catch (\Exception $e) {
183
                $this->fs->remove($folder);
184
185
                throw $e;
186
            }
187
        }
188
189
        // generate global sitemap index
190
        SitemapWriter::generateSitemapIndex(
191
            $folder,
192
            sprintf('%s://%s%s', $scheme, $host, $appendPath),
193
            'sitemap*.xml',
194
            'sitemap.xml'
195
        );
196
    }
197
198
    private function moveTemporaryFile(string $tempFolder, string $permanentFolder): void
199
    {
200
        $oldFiles = Finder::create()->files()->name('sitemap*.xml')->in($permanentFolder);
201
        foreach ($oldFiles as $file) {
202
            $this->fs->remove($file->getRealPath());
203
        }
204
205
        $newFiles = Finder::create()->files()->name('sitemap*.xml')->in($tempFolder);
206
        foreach ($newFiles as $file) {
207
            $this->fs->rename($file->getRealPath(), sprintf('%s/%s', $permanentFolder, $file->getFilename()));
208
        }
209
    }
210
}
211