• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JBZoo / Less / 30191071487

20 Jul 2026 08:59PM UTC coverage: 93.75%. Remained the same
30191071487

push

github

web-flow
Merge pull request #12 from JBZoo/release/8.0

8.0.0 — PHP 8.3+ floor & lock-step major

135 of 144 relevant lines covered (93.75%)

61.42 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.3
/src/Cache.php
1
<?php
2

3
/**
4
 * JBZoo Toolbox - Less.
5
 *
6
 * This file is part of the JBZoo Toolbox project.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 *
10
 * @license    MIT
11
 * @copyright  Copyright (C) JBZoo.com, All rights reserved.
12
 * @see        https://github.com/JBZoo/Less
13
 */
14

15
declare(strict_types=1);
16

17
namespace JBZoo\Less;
18

19
use JBZoo\Data\Data;
20
use JBZoo\Utils\Dates;
21
use JBZoo\Utils\Filter;
22
use JBZoo\Utils\FS;
23
use JBZoo\Utils\Slug;
24
use JBZoo\Utils\Str;
25
use JBZoo\Utils\Vars;
26

27
final class Cache
28
{
29
    private int    $cacheTtl     = Dates::MONTH;
30
    private string $hash         = '';
31
    private string $basePath     = '';
32
    private string $resultFile   = '';
33
    private string $lessFilepath = '';
34
    private Data   $options;
35

36
    public function __construct(Data $options)
37
    {
38
        $this->options = $options;
78✔
39
        $this->setCacheTTL($this->options->getInt('cache_ttl'));
78✔
40
    }
41

42
    public function setFile(string $lessFile, string $basePath): void
43
    {
44
        $lessFilepath = FS::real($lessFile);
78✔
45
        if ($lessFilepath === '' || $lessFilepath === null) {
78✔
46
            throw new Exception("File '{$lessFile}' not found");
6✔
47
        }
48

49
        $this->lessFilepath = $lessFilepath;
72✔
50
        $this->basePath     = FS::clean($basePath);
72✔
51

52
        $this->hash       = $this->getHash();
72✔
53
        $this->resultFile = $this->getResultFile();
72✔
54
    }
55

56
    /**
57
     * Check is current cache is expired.
58
     */
59
    public function isExpired(): bool
60
    {
61
        if (!FS::isFile($this->resultFile)) {
66✔
62
            return true;
66✔
63
        }
64

65
        $fileAge = \time() - (int)\filemtime($this->resultFile);
18✔
66
        $fileAge = \abs($fileAge);
18✔
67

68
        if ($fileAge >= $this->cacheTtl) {
18✔
69
            return true;
6✔
70
        }
71

72
        $firstLine = \trim((string)FS::firstLine($this->resultFile));
12✔
73
        $expected  = \trim($this->getHeader());
12✔
74

75
        return $expected !== $firstLine;
12✔
76
    }
77

78
    /**
79
     * Save result to cache.
80
     */
81
    public function save(string $content): void
82
    {
83
        $content = $this->getHeader() . $content;
66✔
84
        $result  = \file_put_contents($this->resultFile, $content);
66✔
85

86
        if ($result === false || $result === 0) {
66✔
87
            throw new Exception('JBZoo/Less: File not save - ' . $this->resultFile);
×
88
        }
89
    }
90

91
    public function getFile(): string
92
    {
93
        return $this->resultFile;
66✔
94
    }
95

96
    /**
97
     * @param int $newTTL In seconds (1 to 365 days)
98
     */
99
    public function setCacheTTL(int $newTTL): void
100
    {
101
        $newTTL = Filter::int($newTTL);
78✔
102
        $newTTL = Vars::limit($newTTL, 1, Dates::YEAR);
78✔
103

104
        $this->cacheTtl = $newTTL;
78✔
105
    }
106

107
    private function getResultFile(): string
108
    {
109
        $relPath = FS::getRelative($this->lessFilepath, $this->options->getString('root_path'));
72✔
110

111
        // Normalize relative path
112
        $relPath = Slug::filter($relPath, '_');
72✔
113
        $relPath = Str::low($relPath);
72✔
114

115
        // Get full clean path
116
        $configCachePath = $this->options->getString('cache_path');
72✔
117
        $cacheBasePath   = FS::real($configCachePath);
72✔
118
        if ($cacheBasePath !== '' && $cacheBasePath !== null) {
72✔
119
            $fullPath = "{$cacheBasePath}/{$relPath}.css";
72✔
120
            $fullPath = FS::clean($fullPath);
72✔
121
        } else {
122
            throw new Exception('Cache directory is not found: ' . $configCachePath);
×
123
        }
124

125
        return $fullPath;
72✔
126
    }
127

128
    private function getHash(): string
129
    {
130
        // Check depends
131
        $mixins = $this->options->getArray('autoload');
72✔
132
        $hashes = [];
72✔
133

134
        foreach ($mixins as $mixin) {
72✔
135
            $hashes[$mixin] = \md5_file($mixin);
6✔
136
        }
137
        \ksort($hashes);
72✔
138

139
        $options              = $this->options->getArrayCopy();
72✔
140
        $options['functions'] = \array_keys($options['functions']);
72✔
141
        \ksort($options);
72✔
142

143
        $hashed = [
72✔
144
            'less'     => $this->lessFilepath,
72✔
145
            'less_md5' => \md5_file($this->lessFilepath),
72✔
146
            'base'     => $this->basePath,
72✔
147
            'mixins'   => $hashes,
72✔
148
            'options'  => $options,
72✔
149
        ];
72✔
150

151
        $hashed = \serialize($hashed);
72✔
152

153
        return \md5($hashed); // md5 is faster than sha1!
72✔
154
    }
155

156
    private function getHeader(): string
157
    {
158
        return "/* cache-id:{$this->hash} */\n";
66✔
159
    }
160
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc