Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
7 / 7
CRAP
100.00% covered (success)
100.00%
1 / 1
CleanTempDirectoryCommand
100.00% covered (success)
100.00%
38 / 38
100.00% covered (success)
100.00%
7 / 7
24
100.00% covered (success)
100.00%
1 / 1
 getAllowedAppEnvs
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handle
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 recursiveCleanDirectory
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
9
 shouldEntryBeRemoved
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 opendir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 readdir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 closedir
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 filemtime
n/a
0 / 0
n/a
0 / 0
1
 filectime
n/a
0 / 0
n/a
0 / 0
1
 rmdir
n/a
0 / 0
n/a
0 / 0
1
 unlink
n/a
0 / 0
n/a
0 / 0
1
1<?php
2
3namespace Olz\Command;
4
5use Olz\Command\Common\OlzCommand;
6use Symfony\Component\Console\Attribute\AsCommand;
7use Symfony\Component\Console\Command\Command;
8use Symfony\Component\Console\Input\InputInterface;
9use Symfony\Component\Console\Output\OutputInterface;
10
11#[AsCommand(name: 'olz:clean-temp-directory')]
12class CleanTempDirectoryCommand extends OlzCommand {
13    protected string $temp_realpath;
14    protected int $clean_older_than;
15
16    /** @return array<string> */
17    protected function getAllowedAppEnvs(): array {
18        return ['dev', 'test', 'staging', 'prod'];
19    }
20
21    protected function handle(InputInterface $input, OutputInterface $output): int {
22        $data_path = $this->envUtils()->getDataPath();
23        $temp_path = "{$data_path}temp";
24        $this->temp_realpath = realpath($temp_path) ?: '';
25        $now = strtotime($this->dateUtils()->getCurrentDateInFormat('Y-m-d H:i:s')) ?: 0;
26        $cleaning_delay = 86400 * 2;
27        $this->clean_older_than = $now - $cleaning_delay;
28
29        $this->recursiveCleanDirectory($temp_path);
30
31        return Command::SUCCESS;
32    }
33
34    private function recursiveCleanDirectory(string $directory): void {
35        $handle = $this->opendir($directory);
36        if (!$handle) {
37            $this->log()->warning("Failed to open directory {$directory}");
38            return;
39        }
40        while (false !== ($entry = $this->readdir($handle))) {
41            if ($entry === '.' || $entry === '..') {
42                continue;
43            }
44            $entry_path = "{$directory}/{$entry}";
45            if (!$this->shouldEntryBeRemoved($entry_path)) {
46                continue;
47            }
48            $entry_realpath = realpath($entry_path) ?: '';
49            if (is_dir($entry_path)) {
50                $this->recursiveCleanDirectory($entry_path);
51                $this->rmdir($entry_realpath); // Remove directory if it's empty
52            } elseif (is_file($entry_path)) {
53                $this->unlink($entry_realpath);
54            }
55        }
56        $this->closedir($handle);
57    }
58
59    private function shouldEntryBeRemoved(string $entry_path): bool {
60        $last_modification_date = max([
61            $this->filemtime($entry_path),
62            $this->filectime($entry_path),
63        ]);
64        if ($last_modification_date >= $this->clean_older_than) {
65            return false;
66        }
67        $entry_realpath = realpath($entry_path) ?: '';
68        // Double check we're not doing something stupid!
69        if (substr($entry_realpath, 0, strlen($this->temp_realpath)) !== $this->temp_realpath) {
70            // @codeCoverageIgnoreStart
71            // Reason: Should never happen in reality.
72            return false;
73            // @codeCoverageIgnoreEnd
74        }
75        return true;
76    }
77
78    /** @return false|resource */
79    protected function opendir(string $path): mixed {
80        return opendir($path);
81    }
82
83    /** @param resource|null $handle */
84    protected function readdir(mixed $handle): bool|string {
85        return readdir($handle);
86    }
87
88    /** @param resource|null $handle */
89    protected function closedir(mixed $handle): void {
90        closedir($handle);
91    }
92
93    protected function filemtime(string $path): bool|int {
94        // @codeCoverageIgnoreStart
95        // Reason: Mocked in tests.
96        return filemtime($path);
97        // @codeCoverageIgnoreEnd
98    }
99
100    protected function filectime(string $path): bool|int {
101        // @codeCoverageIgnoreStart
102        // Reason: Mocked in tests.
103        return filectime($path);
104        // @codeCoverageIgnoreEnd
105    }
106
107    // @codeCoverageIgnoreStart
108    // Reason: Mocked in tests.
109
110    protected function rmdir(string $path): void {
111        rmdir($path);
112    }
113
114    protected function unlink(string $path): void {
115        unlink($path);
116    }
117
118    // @codeCoverageIgnoreEnd
119}