Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
86.67% |
26 / 30 |
|
25.00% |
1 / 4 |
CRAP | |
0.00% |
0 / 1 |
FileContentsHasher | |
86.67% |
26 / 30 |
|
25.00% |
1 / 4 |
13.40 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
6 | |||
singleton | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getFileContentsHashInternal | |
85.71% |
12 / 14 |
|
0.00% |
0 / 1 |
3.03 | |||
getFileContentsHash | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
6 |
1 | <?php |
2 | |
3 | use Wikimedia\ObjectCache\APCUBagOStuff; |
4 | use Wikimedia\ObjectCache\BagOStuff; |
5 | use Wikimedia\ObjectCache\EmptyBagOStuff; |
6 | |
7 | /** |
8 | * Generate hash digests of file contents to help with cache invalidation. |
9 | * |
10 | * This program is free software; you can redistribute it and/or modify |
11 | * it under the terms of the GNU General Public License as published by |
12 | * the Free Software Foundation; either version 2 of the License, or |
13 | * (at your option) any later version. |
14 | * |
15 | * This program is distributed in the hope that it will be useful, |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
18 | * GNU General Public License for more details. |
19 | * |
20 | * You should have received a copy of the GNU General Public License along |
21 | * with this program; if not, write to the Free Software Foundation, Inc., |
22 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
23 | * http://www.gnu.org/copyleft/gpl.html |
24 | * |
25 | * @file |
26 | */ |
27 | class FileContentsHasher { |
28 | private const ALGO = 'md4'; |
29 | |
30 | /** @var BagOStuff */ |
31 | protected $cache; |
32 | |
33 | /** @var FileContentsHasher */ |
34 | private static $instance; |
35 | |
36 | public function __construct() { |
37 | $this->cache = function_exists( 'apcu_fetch' ) ? new APCUBagOStuff() : new EmptyBagOStuff(); |
38 | } |
39 | |
40 | /** |
41 | * Get the singleton instance of this class. |
42 | * |
43 | * @return FileContentsHasher |
44 | */ |
45 | public static function singleton() { |
46 | if ( !self::$instance ) { |
47 | self::$instance = new self; |
48 | } |
49 | |
50 | return self::$instance; |
51 | } |
52 | |
53 | /** |
54 | * Get a hash of a file's contents, either by retrieving a previously- |
55 | * computed hash from the cache, or by computing a hash from the file. |
56 | * |
57 | * @param string $filePath Full path to the file. |
58 | * @return string|bool Hash of file contents, or false if the file could not be read. |
59 | */ |
60 | private function getFileContentsHashInternal( $filePath ) { |
61 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
62 | $mtime = @filemtime( $filePath ); |
63 | if ( $mtime === false ) { |
64 | return false; |
65 | } |
66 | |
67 | $cacheKey = $this->cache->makeGlobalKey( __CLASS__, $filePath, $mtime, self::ALGO ); |
68 | return $this->cache->getWithSetCallback( |
69 | $cacheKey, |
70 | $this->cache::TTL_DAY, |
71 | static function () use ( $filePath ) { |
72 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
73 | $contents = @file_get_contents( $filePath ); |
74 | if ( $contents === false ) { |
75 | // Don't cache false |
76 | return false; |
77 | } |
78 | |
79 | return hash( self::ALGO, $contents ); |
80 | } |
81 | ); |
82 | } |
83 | |
84 | /** |
85 | * Get a hash of the combined contents of one or more files, either by |
86 | * retrieving a previously-computed hash from the cache, or by computing |
87 | * a hash from the files. |
88 | * |
89 | * @param string|string[] $filePaths One or more file paths. |
90 | * @return string|bool Hash of files' contents, or false if no file could not be read. |
91 | */ |
92 | public static function getFileContentsHash( $filePaths ) { |
93 | $instance = self::singleton(); |
94 | |
95 | if ( !is_array( $filePaths ) ) { |
96 | $filePaths = (array)$filePaths; |
97 | } |
98 | |
99 | if ( count( $filePaths ) === 1 ) { |
100 | $hash = $instance->getFileContentsHashInternal( $filePaths[0] ); |
101 | return $hash; |
102 | } |
103 | |
104 | sort( $filePaths ); |
105 | $hashes = []; |
106 | foreach ( $filePaths as $filePath ) { |
107 | $hashes[] = $instance->getFileContentsHashInternal( $filePath ) ?: ''; |
108 | } |
109 | |
110 | $hashes = implode( '', $hashes ); |
111 | return $hashes ? hash( self::ALGO, $hashes ) : false; |
112 | } |
113 | } |