Passed
Push — main ( c1e5b8...0797d2 )
by Axel
04:05
created

CacheClearer   A

Complexity

Total Complexity 17

Size/Duplication

Total Lines 131
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 55
dl 0
loc 131
rs 10
c 0
b 0
f 0
wmc 17

4 Methods

Rating   Name   Duplication   Size   Complexity  
B doClear() 0 36 10
A clear() 0 9 4
A __construct() 0 18 1
A initialiseCacheTypeMap() 0 38 2
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the Zikula package.
7
 *
8
 * Copyright Zikula - https://ziku.la/
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 Zikula\CoreBundle;
15
16
use FilesystemIterator;
17
use FOS\JsRoutingBundle\Extractor\ExposedRoutesExtractorInterface;
18
use Psr\Log\LoggerInterface;
19
use Symfony\Component\DependencyInjection\Attribute\Autowire;
20
use Symfony\Component\Filesystem\Filesystem;
21
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
22
23
class CacheClearer
24
{
25
    private LoggerInterface $logger;
26
27
    private bool $installed;
28
29
    private Filesystem $fileSystem;
30
31
    private array $cacheTypes = [];
32
33
    private array $cachesToClear;
34
35
    public function __construct(
36
        LoggerInterface $zikulaLogger,
37
        #[Autowire(service: 'cache_warmer')]
38
        private readonly CacheWarmerInterface $warmer,
39
        #[Autowire(service: 'fos_js_routing.extractor')]
40
        private readonly ExposedRoutesExtractorInterface $fosJsRoutesExtractor,
41
        #[Autowire('%kernel.cache_dir%')]
42
        private readonly string $cacheDir,
43
        #[Autowire('%kernel.container_class%')]
44
        private readonly string $kernelContainerClass,
45
        #[Autowire('%env(ZIKULA_INSTALLED)%')]
46
        string $installed
47
    ) {
48
        $this->logger = $zikulaLogger;
49
        $this->installed = '0.0.0' !== $installed;
50
        $this->fileSystem = new Filesystem();
51
        $this->cacheTypes = [];
52
        $this->cachesToClear = [];
53
    }
54
55
    /**
56
     * The cache is not cleared on demand.
57
     * Calling 'clear' will store caches to clear
58
     * This ensures no duplication and defers actual clearing
59
     * until kernel.terminate event
60
     * @see \Zikula\CoreBundle\EventListener\CacheClearListener::doClearCache
61
     */
62
    public function clear(string $type): void
63
    {
64
        if (!isset($this->cachesToClear[$type])) {
65
            foreach ($this->cachesToClear as $value) {
66
                if (0 === mb_strpos($type, $value)) {
67
                    return;
68
                }
69
            }
70
            $this->cachesToClear[$type] = $type;
71
        }
72
    }
73
74
    /**
75
     * @internal
76
     * This is not a public api
77
     */
78
    public function doClear(): void
79
    {
80
        if (empty($this->cachesToClear)) {
81
            return;
82
        }
83
84
        if (!count($this->cacheTypes)) {
85
            $this->initialiseCacheTypeMap();
86
        }
87
        foreach ($this->cachesToClear as $type) {
88
            foreach ($this->cacheTypes as $cacheType => $files) {
89
                if (0 !== mb_strpos($cacheType, $type)) {
90
                    continue;
91
                }
92
                foreach ($files as $file) {
93
                    if (is_dir($file)) {
94
                        // Do not delete the folder itself, but all files in it.
95
                        // Otherwise Symfony somehow can't create the folder anymore.
96
                        $file = new FilesystemIterator($file);
97
                    }
98
                    // This silently ignores non existing files.
99
                    $this->fileSystem->remove($file);
100
                }
101
                $this->logger->notice(sprintf('Cache cleared: %s', $cacheType));
102
            }
103
        }
104
        if (function_exists('opcache_reset') && filter_var(ini_get('opcache.enable'), FILTER_VALIDATE_BOOLEAN)) {
105
            // This is a brute force clear of _all_ the cached files
106
            // because simply clearing the files in $this->cachesToClear isn't enough.
107
            // Perhaps if we could discern exactly which files to invalidate, we could
108
            // take a more precise approach with @opcache_invalidate($file, true).
109
            @opcache_reset();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for opcache_reset(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

109
            /** @scrutinizer ignore-unhandled */ @opcache_reset();

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...
110
            $this->logger->notice('OPCache cleared!');
111
        }
112
        // the cache must be warmed after deleting files
113
        $this->warmer->warmUp($this->cacheDir);
114
    }
115
116
    private function initialiseCacheTypeMap()
117
    {
118
        $fosJsRoutingFiles = [];
119
        if ($this->installed) {
120
            // avoid accessing FOS extractor before/during installation
121
            // because this requires request context
122
            $fosJsRoutingFiles[] = $this->fosJsRoutesExtractor->getCachePath();
0 ignored issues
show
Bug introduced by
The call to FOS\JsRoutingBundle\Extr...terface::getCachePath() has too few arguments starting with locale. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

122
            /** @scrutinizer ignore-call */ 
123
            $fosJsRoutingFiles[] = $this->fosJsRoutesExtractor->getCachePath();

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
123
        }
124
125
        $cacheFolder = $this->cacheDir . DIRECTORY_SEPARATOR;
126
127
        $this->cacheTypes = [
128
            'symfony.routing.generator' => [
129
                $cacheFolder . 'url_generating_routes.php',
130
                $cacheFolder . 'url_generating_routes.php.meta'
131
            ],
132
            'symfony.routing.matcher' => [
133
                $cacheFolder . 'url_matching_routes.php',
134
                $cacheFolder . 'url_matching_routes.php.meta'
135
            ],
136
            'symfony.routing.fosjs' => $fosJsRoutingFiles,
137
            'symfony.config' => [
138
                // clearing the container class will force all other container files
139
                // to be rebuilt so there is no need to delete all of them
140
                // nor a need to delete the container directory
141
                $cacheFolder . $this->kernelContainerClass . '.php',
142
            ],
143
            'symfony.translations' => [
144
                $cacheFolder . '/translations'
145
            ],
146
            'twig' => [
147
                $cacheFolder . 'twig'
148
            ],
149
            'purifier' => [
150
                $cacheFolder . 'purifier'
151
            ],
152
            'assets' => [
153
                $cacheFolder . 'assets'
154
            ]
155
        ];
156
    }
157
}
158