Passed
Push — master ( 97dd58...4109cd )
by Dev
10:22
created

StaticAppGenerator   F

Complexity

Total Complexity 64

Size/Duplication

Total Lines 449
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 164
dl 0
loc 449
rs 3.28
c 0
b 0
f 0
wmc 64

26 Methods

Rating   Name   Duplication   Size   Complexity  
A copy() 0 6 2
A generate() 0 13 1
B copyAssets() 0 23 8
A getPageRepository() 0 3 1
B copyMediaToDownload() 0 22 7
A generateErrorPages() 0 10 2
A generateFilePath() 0 6 2
A generateLivePathFor() 0 17 4
A addRedirection() 0 7 1
A generateFeedFor() 0 5 1
A generatePages() 0 9 2
A generatePage() 0 12 2
A compress() 0 3 1
A generateFromHost() 0 3 1
A generateAll() 0 11 4
A saveAsStatic() 0 18 3
A mustSymlink() 0 3 2
A generateServerManagerFile() 0 6 2
A generateCname() 0 3 1
A generateSitemaps() 0 9 3
A copyRobotsFiles() 0 3 1
A __construct() 0 33 3
A generateSitemap() 0 9 3
A generateFeed() 0 9 3
A generateHtaccess() 0 7 1
A generateErrorPage() 0 10 3

How to fix   Complexity   

Complex Class

Complex classes like StaticAppGenerator often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use StaticAppGenerator, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PiedWeb\CMSBundle\StaticGenerator;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use PiedWeb\CMSBundle\Entity\PageInterface;
7
use PiedWeb\CMSBundle\Entity\PageInterface as Page;
8
use PiedWeb\CMSBundle\Repository\PageRepository;
9
use PiedWeb\CMSBundle\Service\AppConfigHelper;
10
use PiedWeb\CMSBundle\Service\AppConfigHelper as App;
11
use PiedWeb\CMSBundle\Service\PageCanonicalService as PageCanonical;
12
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
13
use Symfony\Component\Filesystem\Filesystem;
14
use Symfony\Component\HttpFoundation\Request;
15
use Symfony\Component\HttpFoundation\RequestStack;
16
use Symfony\Component\HttpFoundation\Response;
17
use Symfony\Component\Routing\Router;
18
use Symfony\Component\Routing\RouterInterface;
19
use Symfony\Contracts\Translation\TranslatorInterface;
20
use Twig\Environment as Twig;
21
use WyriHaximus\HtmlCompress\Factory as HtmlCompressor;
22
use WyriHaximus\HtmlCompress\HtmlCompressorInterface;
23
24
/**
25
 * Generate 1 App.
26
 */
27
class StaticAppGenerator
28
{
29
    /**
30
     * Contain files relative to SEO wich will be hard copied.
31
     *
32
     * @var array
33
     */
34
    protected $robotsFiles = ['robots.txt'];
35
36
    /**
37
     * @var array
38
     */
39
    protected $dontCopy = ['index.php', '.htaccess'];
40
41
    /**
42
     * @var EntityManagerInterface
43
     */
44
    protected $em;
45
46
    /**
47
     * @var Filesystem
48
     */
49
    protected $filesystem;
50
51
    /**
52
     * @var Twig
53
     */
54
    protected $twig;
55
56
    /**
57
     * @var string
58
     */
59
    protected $webDir;
60
61
    /**
62
     * @var array
63
     */
64
    protected $apps;
65
    protected $app;
66
    protected $staticDomain;
67
    protected $mustGetPagesWithoutHost;
68
69
    /** var @string */
70
    protected $staticDir;
71
72
    /**
73
     * @var RequestStack
74
     */
75
    protected $requesStack;
76
77
    /**
78
     * @var \PiedWeb\CMSBundle\Service\PageCanonicalService
79
     */
80
    protected $pageCanonical;
81
82
    /**
83
     * @var TranslatorInterface
84
     */
85
    protected $translator;
86
87
    /**
88
     * @var HtmlCompressorInterface
89
     */
90
    protected $parser;
91
92
    /**
93
     * @var ParameterBagInterface
94
     */
95
    protected $params;
96
    public static $appKernel;
97
98
    /**
99
     * @var Router
100
     */
101
    protected $router;
102
103
    /**
104
     * Used in .htaccess generation.
105
     *
106
     * @var string
107
     */
108
    protected $redirections = '';
109
110
    public function __construct(
111
        EntityManagerInterface $em,
112
        Twig $twig,
113
        ParameterBagInterface $params,
114
        RequestStack $requesStack,
115
        PageCanonical $pageCanonical,
116
        TranslatorInterface $translator,
117
        RouterInterface $router,
118
        string $webDir,
119
        $kernel
120
    ) {
121
        $this->em = $em;
122
        $this->filesystem = new Filesystem();
123
        $this->twig = $twig;
124
        $this->params = $params;
125
        $this->requesStack = $requesStack;
126
        $this->webDir = $webDir;
127
        $this->pageCanonical = $pageCanonical;
128
        $this->translator = $translator;
129
        $this->router = $router;
0 ignored issues
show
Documentation Bug introduced by
$router is of type Symfony\Component\Routing\RouterInterface, but the property $router was declared to be of type Symfony\Component\Routing\Router. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof 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 given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
130
        $this->apps = $this->params->get('pwc.apps');
131
        $this->parser = HtmlCompressor::construct();
132
133
        if (!method_exists($this->filesystem, 'dumpFile')) {
134
            throw new \RuntimeException('Method dumpFile() is not available. Upgrade your Filesystem.');
135
        }
136
137
        if (null === static::$appKernel) {
138
            $kernelClass = \get_class($kernel);
139
            static::$appKernel = new $kernelClass('prod', false);
140
            //static::$appKernel = clone $kernel;
141
            // NOTE: If we clone, it's take too much time in dev mod
142
            static::$appKernel->boot();
143
        }
144
    }
145
146
    public function generateAll($filter = null)
147
    {
148
149
        foreach ($this->apps as $app) {
150
            if ($filter && !in_array($filter, $app->getHost())) {
151
                    continue;
152
            }
153
            $this->generate($app, $this->mustGetPagesWithoutHost);
154
            //$this->generateStaticApp($app);
155
156
            $this->mustGetPagesWithoutHost = false;
157
        }
158
    }
159
160
    public function generateFromHost($host)
161
    {
162
        return $this->generateAll($host);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->generateAll($host) targeting PiedWeb\CMSBundle\Static...enerator::generateAll() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
163
    }
164
165
    /**
166
     * Main Logic is here.
167
     *
168
     * @throws \RuntimeException
169
     * @throws \LogicException
170
     */
171
    public function generate($app, $mustGetPagesWithoutHost = false)
172
    {
173
        $this->app = new App($app['hosts'][0], [$app]);
174
        $this->mustGetPagesWithoutHost = $mustGetPagesWithoutHost;
175
176
        $this->filesystem->remove($this->app->getStaticDir());
177
        $this->generatePages();
178
        $this->generateSitemaps();
179
        $this->generateErrorPages();
180
        $this->copyRobotsFiles();
181
        $this->generateServerManagerFile();
182
        $this->copyAssets();
183
        $this->copyMediaToDownload();
184
    }
185
186
    protected function generateLivePathFor($host, $route = 'piedweb_cms_page', $params = [])
187
    {
188
        if ($host instanceof PageInterface) {
189
            $page = $host;
190
            $host = $page->getHost();
191
        }
192
193
        if (isset($page)) {
194
            $params['slug'] = $page->getRealSlug();
195
        }
196
197
        if ($host) {
198
            $params['host'] = $host;
199
            $route = 'custom_host_'.$route;
200
        }
201
202
        return $this->router->generate($route, $params);
203
    }
204
205
    /**
206
     * Symlink doesn't work on github page, symlink only for apache if conf say OK to symlink.
207
     */
208
    protected function mustSymlink()
209
    {
210
        return $this->app->get('static_generateForApache') ? $this->app->get('static_symlinkMedia') : false;
211
    }
212
213
    /**
214
     * Generate .htaccess for Apache or CNAME for github
215
     * Must be run after generatePages() !!
216
     */
217
    protected function generateServerManagerFile()
218
    {
219
        if ($this->app->get('static_generateForApache')) {
220
            $this->generateHtaccess();
221
        } else { //if ($this->app['static_generateForGithubPages'])) {
222
            $this->generateCname();
223
        }
224
    }
225
226
    /**
227
     * Copy files relative to SEO (robots, sitemaps, etc.).
228
     */
229
    protected function copyRobotsFiles(): void
230
    {
231
        array_map([$this, 'copy'], $this->robotsFiles);
232
    }
233
234
    // todo
235
    // docs
236
    // https://help.github.com/en/github/working-with-github-pages/managing-a-custom-domain-for-your-github-pages-site
237
    protected function generateCname()
238
    {
239
        $this->filesystem->dumpFile($this->app->getStaticDir().'/CNAME', $this->app->getMainHost());
240
    }
241
242
    protected function generateHtaccess()
243
    {
244
        $htaccess = $this->twig->render('@PiedWebCMS/static/htaccess.twig', [
245
            'domain' => $this->app->getMainHost(),
246
            'redirections' => $this->redirections,
247
        ]);
248
        $this->filesystem->dumpFile($this->app->getStaticDir().'/.htaccess', $htaccess);
249
    }
250
251
    protected function copy(string $file): void
252
    {
253
        if (file_exists($file)) {
254
            copy(
255
                str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$file),
256
                $this->app->getStaticDir().'/'.$file
257
            );
258
        }
259
    }
260
261
    /**
262
     * Copy (or symlink) for all assets in public
263
     * (and media previously generated by liip in public).
264
     */
265
    protected function copyAssets(): void
266
    {
267
        $symlink = $this->mustSymlink();
268
269
        $dir = dir($this->webDir);
270
        while (false !== $entry = $dir->read()) {
271
            if ('.' == $entry || '..' == $entry) {
272
                continue;
273
            }
274
            if (!\in_array($entry, $this->robotsFiles) && !\in_array($entry, $this->dontCopy)) {
275
                //$this->symlink(
276
                if (true === $symlink) {
277
                    $this->filesystem->symlink(
278
                        str_replace($this->params->get('kernel.project_dir').'/', '../', $this->webDir.'/'.$entry),
279
                        $this->app->getStaticDir().'/'.$entry
280
                    );
281
                } else {
282
                    $action = is_file($this->webDir.'/'.$entry) ? 'copy' : 'mirror';
283
                    $this->filesystem->$action($this->webDir.'/'.$entry, $this->app->getStaticDir().'/'.$entry);
284
                }
285
            }
286
        }
287
        $dir->close();
288
    }
289
290
    /**
291
     * Copy or Symlink "not image" media to download folder.
292
     *
293
     * @return void
294
     */
295
    protected function copyMediaToDownload()
296
    {
297
        $symlink = $this->mustSymlink();
298
299
        if (!file_exists($this->app->getStaticDir().'/download')) {
300
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/');
301
            $this->filesystem->mkdir($this->app->getStaticDir().'/download/media');
302
        }
303
304
        $dir = dir($this->webDir.'/../media');
305
        while (false !== $entry = $dir->read()) {
306
            if ('.' == $entry || '..' == $entry) {
307
                continue;
308
            }
309
            // if the file is an image, it's ever exist (maybe it's slow to check every files)
310
            if (!file_exists($this->webDir.'/media/default/'.$entry)) {
311
                if (true === $symlink) {
312
                    $this->filesystem->symlink('../../../media/'.$entry, $this->app->getStaticDir().'/download/media/'.$entry);
313
                } else {
314
                    $this->filesystem->copy(
315
                        $this->webDir.'/../media/'.$entry,
316
                        $this->app->getStaticDir().'/download/media/'.$entry
317
                    );
318
                }
319
            }
320
        }
321
322
        //$this->filesystem->$action($this->webDir.'/../media', $this->app->getStaticDir().'/download/media');
323
    }
324
325
    protected function generatePages(): void
326
    {
327
        $qb = $this->getPageRepository()->getQueryToFindPublished('p');
328
        $qb = $this->getPageRepository()->andHost($qb, $this->app->getMainHost(), $this->mustGetPagesWithoutHost);
329
        $pages = $qb->getQuery()->getResult();
330
331
        foreach ($pages as $page) {
332
            $this->generatePage($page);
333
            $this->generateFeedFor($page);
334
        }
335
    }
336
337
    protected function generateSitemaps(): void
338
    {
339
        foreach (explode('|', $this->params->get('pwc.locales')) as $locale) {
340
341
            foreach (['txt', 'xml'] as $format) {
342
                $this->generateSitemap($locale, $format);
343
            }
344
345
            $this->generateFeed($locale);
346
        }
347
    }
348
349
    protected function generateSitemap($locale, $format)
350
    {
351
        $liveUri = $this->generateLivePathFor($this->app->getMainHost(), 'piedweb_cms_page_sitemap', ['locale'=>$locale, '_format'=>$format]);
352
        $staticFile = $this->app->getStaticDir().'/sitemap'.$locale.'.'.$format; // todo get it from URI removing host
353
        $this->saveAsStatic($liveUri, $staticFile);
354
355
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
356
            $staticFile = $this->app->getStaticDir().'/sitemap.'.$format;
357
            $this->saveAsStatic($liveUri, $staticFile);
358
        }
359
    }
360
361
    protected function generateFeed($locale)
362
    {
363
        $liveUri = $this->generateLivePathFor($this->app->getMainHost(), 'piedweb_cms_page_main_feed', ['locale'=>$locale]);
364
        $staticFile = $this->app->getStaticDir().'/feed'.$locale.'.xml';
365
        $this->saveAsStatic($liveUri, $staticFile);
366
367
        if ($this->params->get('locale') == $locale ? '' : '.'.$locale) {
368
            $staticFile = $this->app->getStaticDir().'/feed.xml';
369
            $this->saveAsStatic($liveUri, $staticFile);
370
        }
371
    }
372
373
    /**
374
     * The function cache redirection found during generatePages and
375
     * format in self::$redirection the content for the .htaccess.
376
     *
377
     * @return void
378
     */
379
    protected function addRedirection(Page $page)
380
    {
381
        $this->redirections .= 'Redirect ';
382
        $this->redirections .= $page->getRedirectionCode().' ';
383
        $this->redirections .= $this->pageCanonical->generatePathForPage($page->getRealSlug());
384
        $this->redirections .= ' '.$page->getRedirection();
385
        $this->redirections .= PHP_EOL;
386
    }
387
388
    protected function generatePage(Page $page)
389
    {
390
        /**/
391
        // check if it's a redirection
392
        if (false !== $page->getRedirection()) {
393
            $this->addRedirection($page);
394
395
            return;
396
        }
397
        /**/
398
399
        $this->saveAsStatic($this->generateLivePathFor($page), $this->generateFilePath($page));
400
    }
401
402
    protected function saveAsStatic($liveUri, $destination)
403
    {
404
        $request = Request::create($liveUri);
405
406
        $response = static::$appKernel->handle($request);
407
408
        if ($response->isRedirect()) {
409
            // todo
410
            //$this->addRedirection($liveUri, getRedirectUri)
411
            return;
412
        }
413
        elseif (200 != $response->getStatusCode()) {
414
            // todo log context
415
            return;
416
        }
417
418
        $content = $this->compress($response->getContent());
419
        $this->filesystem->dumpFile($destination, $content);
420
    }
421
422
    protected function compress($html)
423
    {
424
        return $this->parser->compress($html);
425
    }
426
427
    protected function generateFilePath(Page $page)
428
    {
429
        $slug = '' == $page->getRealSlug() ? 'index' : $page->getRealSlug();
430
        $route = $this->pageCanonical->generatePathForPage($slug);
431
432
        return $this->app->getStaticDir().$route.'.html';
433
    }
434
435
    /**
436
     * Generate static file for feed indexing children pages
437
     * (only if children pages exists).
438
     *
439
     * @return void
440
     */
441
    protected function generateFeedFor(Page $page)
442
    {
443
        $liveUri = $this->generateLivePathFor($page, 'piedweb_cms_page_feed');
444
        $staticFile = preg_replace('/.html$/', '.xml', $this->generateFilePath($page));
445
        $this->saveAsStatic($liveUri, $staticFile);
446
    }
447
448
    protected function generateErrorPages(): void
449
    {
450
        $this->generateErrorPage();
451
452
        // todo i18n error in .htaccess
453
        $locales = explode('|', $this->params->get('pwc.locales'));
454
455
        foreach ($locales as $locale) {
456
            $this->filesystem->mkdir($this->app->getStaticDir().'/'.$locale);
457
            $this->generateErrorPage($locale);
458
        }
459
    }
460
461
    protected function generateErrorPage($locale = null, $uri = '404.html')
462
    {
463
        if (null !== $locale) {
464
            $request = new Request();
465
            $request->setLocale($locale);
466
            $this->requesStack->push($request);
467
        }
468
469
        $dump = $this->parser->compress($this->twig->render('@Twig/Exception/error.html.twig'));
470
        $this->filesystem->dumpFile($this->app->getStaticDir().(null !== $locale ? '/'.$locale : '').'/'.$uri, $dump);
471
    }
472
473
    protected function getPageRepository(): PageRepository
474
    {
475
        return $this->em->getRepository($this->params->get('pwc.entity_page'));
476
    }
477
}
478