Passed
Push — master ( 590757...67ec3e )
by Divine Niiquaye
02:17
created

CacheStorage::write()   B

Complexity

Conditions 10
Paths 14

Size

Total Lines 32
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 110

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 10
eloc 17
c 1
b 0
f 0
nc 14
nop 2
dl 0
loc 32
ccs 0
cts 18
cp 0
crap 110
rs 7.6666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Biurad opensource projects.
7
 *
8
 * PHP version 7.2 and above required
9
 *
10
 * @author    Divine Niiquaye Ibok <[email protected]>
11
 * @copyright 2019 Biurad Group (https://biurad.com/)
12
 * @license   https://opensource.org/licenses/BSD-3-Clause License
13
 *
14
 * For the full copyright and license information, please view the LICENSE
15
 * file that was distributed with this source code.
16
 */
17
18
namespace Biurad\UI\Storage;
19
20
use Biurad\UI\Interfaces\StorageInterface;
21
use Psr\Log\LoggerInterface;
22
use RuntimeException;
23
24
/**
25
 * CacheLoader is a loader that caches other loaders responses
26
 * on the filesystem.
27
 *
28
 * This cache only caches on disk to allow PHP accelerators to cache the opcodes.
29
 * All other mechanism would imply the use of `eval()`.
30
 *
31
 * @author Divine Niiquaye Ibok <[email protected]>
32
 */
33
class CacheStorage implements StorageInterface
34
{
35
    /** @var StorageInterface */
36
    protected $storage;
37
38
    /** @var null|LoggerInterface */
39
    protected $logger;
40
41
    /** @var string */
42
    protected $directory;
43
44
    /**
45
     * @param StorageInterface     $storage
46
     * @param string               $directory The directory where to store the cache files
47
     * @param null|LoggerInterface $logger
48
     */
49
    public function __construct(StorageInterface $storage, string $directory, ?LoggerInterface $logger = null)
50
    {
51
        $this->logger    = $logger;
52
        $this->storage   = $storage;
53
        $this->directory = \rtrim($directory, '\/') . '/';
54
    }
55
56
    /**
57
     * {@inheritdoc}
58
     */
59
    public function addLocation(string $location): void
60
    {
61
        $this->storage->addLocation($location);
62
    }
63
64
    /**
65
     * {@inheritdoc}
66
     */
67
    public function load(string $template): ?string
68
    {
69
        $key  = \hash('sha256', $template);
70
        $dir  = $this->directory . \substr($key, 0, 2);
71
        $file = \substr($key, 2) . '.ui.phtml';
72
        $path = $dir . \DIRECTORY_SEPARATOR . $file;
73
74
        if (\is_file($path) && !$this->isFresh($template, @filemtime($path))) {
75
            if (null !== $this->logger) {
76
                $this->logger->debug('Fetching template from cache.', ['name' => $template]);
77
            }
78
79
            return \file_get_contents($path);
80
        }
81
82
        if (null === $storage = $this->storage->load($template)) {
83
            return null;
84
        }
85
86
        $this->write($path, \file_exists($storage) ? \file_get_contents($storage) : $storage);
87
88
        if (null !== $this->logger) {
89
            $this->logger->debug('Storing template in cache.', ['name' => $template]);
90
        }
91
92
        return \file_get_contents($path);
93
    }
94
95
    /**
96
     * @param string $key
97
     * @param string $content
98
     */
99
    private function write(string $key, string $content): void
100
    {
101
        $dir = \dirname($key);
102
103
        if (!\is_dir($dir)) {
104
            if (false === @\mkdir($dir, 0777, true)) {
105
                \clearstatcache(true, $dir);
106
107
                if (!\is_dir($dir)) {
108
                    throw new RuntimeException(\sprintf('Unable to create the cache directory (%s).', $dir));
109
                }
110
            }
111
        } elseif (!\is_writable($dir)) {
112
            throw new RuntimeException(\sprintf('Unable to write in the cache directory (%s).', $dir));
113
        }
114
115
        $tmpFile = \tempnam($dir, \basename($key));
116
117
        if (false !== @\file_put_contents($tmpFile, $content) && @\rename($tmpFile, $key)) {
118
            @\chmod($key, 0666 & ~\umask());
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). 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

118
            /** @scrutinizer ignore-unhandled */ @\chmod($key, 0666 & ~\umask());

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...
119
120
            // Compile cached file into bytecode cache
121
            if (\function_exists('opcache_invalidate') && \filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOLEAN)) {
122
                @\opcache_invalidate($key, true);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for opcache_invalidate(). 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

122
                /** @scrutinizer ignore-unhandled */ @\opcache_invalidate($key, true);

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...
123
            } elseif (\function_exists('apc_compile_file')) {
124
                apc_compile_file($key);
125
            }
126
127
            return;
128
        }
129
130
        throw new RuntimeException(\sprintf('Failed to write cache file "%s".', $key));
131
    }
132
133
    /**
134
     * Returns true if the template is still fresh.
135
     *
136
     * Besides checking the loader for freshness information,
137
     * this method also checks if the enabled extensions have
138
     * not changed.
139
     *
140
     * @param string $name
141
     * @param int $time The last modification time of the cached template
142
     */
143
    private function isFresh(string $name, int $time): bool
144
    {
145
        if (null === $path = $this->storage->load($name)) {
146
            return false;
147
        }
148
149
        return \filemtime($path) < $time;
150
    }
151
}
152