Passed
Push — master ( 96803c...b18ae0 )
by Divine Niiquaye
01:37
created

Loader::getClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 3
rs 10
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\DependencyInjection;
19
20
use Nette;
21
use Nette\DI\DependencyChecker;
22
23
/**
24
 * DI container loader.
25
 *
26
 * @author Divine Niiquaye Ibok <[email protected]>
27
 */
28
class Loader
29
{
30
    /** @var bool */
31
    private $autoRebuild = false;
32
33
    /** @var string */
34
    private $tempDirectory;
35
36
    public function __construct(string $tempDirectory, bool $autoRebuild = false)
37
    {
38
        $this->tempDirectory = $tempDirectory;
39
        $this->autoRebuild   = $autoRebuild;
40
    }
41
42
    /**
43
     * @param callable $generator function (Nette\DI\Compiler $compiler): string|null
44
     * @param mixed    $key
45
     */
46
    public function load(callable $generator, $key = null): string
47
    {
48
        $class = $this->getClassName($key);
49
50
        if (!\class_exists($class, false)) {
51
            $this->loadFile($class, $generator);
52
        }
53
54
        return $class;
55
    }
56
57
    /**
58
     * @param mixed $key
59
     */
60
    public function getClassName($key): string
61
    {
62
        return 'Container_' . \substr(\md5(\serialize($key)), 0, 10);
63
    }
64
65
    /**
66
     * @param string   $class
67
     * @param callable $generator
68
     *
69
     * @return array of (code, file[])
70
     */
71
    protected function generate(string $class, callable $generator): array
72
    {
73
        $compiler = new Compiler(new Builder());
74
        $compiler->setClassName($class);
75
76
        $code = $generator(...[&$compiler]) ?: $compiler->compile();
77
78
        return [
79
            "<?php\n$code",
80
            \serialize($compiler->exportDependencies()),
81
            (new XmlAdapter())->dump($compiler->getConfig()),
0 ignored issues
show
Deprecated Code introduced by
The function Nette\DI\Compiler::getConfig() has been deprecated. ( Ignorable by Annotation )

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

81
            (new XmlAdapter())->dump(/** @scrutinizer ignore-deprecated */ $compiler->getConfig()),
Loading history...
82
        ];
83
    }
84
85
    private function loadFile(string $class, callable $generator): void
86
    {
87
        $file = "$this->tempDirectory/$class.php";
88
89
        if (!$this->isExpired($file) && (@include $file) !== false) { // @ file may not exist
90
            return;
91
        }
92
93
        Nette\Utils\FileSystem::createDir($this->tempDirectory);
94
95
        $handle = @\fopen("$file.lock", 'c+'); // @ is escalated to exception
96
97
        if (!$handle) {
0 ignored issues
show
introduced by
$handle is of type false|resource, thus it always evaluated to false.
Loading history...
98
            throw new Nette\IOException("Unable to create file '$file.lock'. " . Nette\Utils\Helpers::getLastError());
99
        }
100
101
        if (!@\flock($handle, \LOCK_EX)) { // @ is escalated to exception
102
            throw new Nette\IOException("Unable to acquire exclusive lock on '$file.lock'. " . Nette\Utils\Helpers::getLastError());
103
        }
104
105
        if (!\is_file($file) || $this->isExpired($file, $updatedMeta)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $updatedMeta seems to be never defined.
Loading history...
106
            if (isset($updatedMeta)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $updatedMeta seems to never exist and therefore isset should always be false.
Loading history...
107
                $toWrite["$file.meta"] = $updatedMeta;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$toWrite was never initialized. Although not strictly required by PHP, it is generally a good practice to add $toWrite = array(); before regardless.
Loading history...
108
            } else {
109
                [$toWrite[$file], $toWrite["$file.meta"], $toWrite["$file.xml"]] = $this->generate($class, $generator);
110
            }
111
112
            foreach ($toWrite as $name => $content) {
113
                if (\file_put_contents("$name.tmp", $content) !== \strlen($content) || !\rename("$name.tmp", $name)) {
114
                    @\unlink("$name.tmp"); // @ - file may not exist
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). 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

114
                    /** @scrutinizer ignore-unhandled */ @\unlink("$name.tmp"); // @ - file may not exist

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...
115
116
                    throw new Nette\IOException("Unable to create file '$name'.");
117
                }
118
119
                if (\function_exists('opcache_invalidate')) {
120
                    @\opcache_invalidate($name, true); // @ can be restricted
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

120
                    /** @scrutinizer ignore-unhandled */ @\opcache_invalidate($name, true); // @ can be restricted

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...
121
                }
122
            }
123
        }
124
125
        if ((@include $file) === false) { // @ - error escalated to exception
126
            throw new Nette\IOException("Unable to include '$file'.");
127
        }
128
        \flock($handle, \LOCK_UN);
129
    }
130
131
    private function isExpired(string $file, string &$updatedMeta = null): bool
132
    {
133
        if ($this->autoRebuild) {
134
            $meta = @\unserialize((string) \file_get_contents("$file.meta")); // @ - file may not exist
135
            $orig = $meta[2] ?? null;
136
137
            return empty($meta[0])
138
                || DependencyChecker::isExpired(...$meta)
0 ignored issues
show
Bug introduced by
The call to Nette\DI\DependencyChecker::isExpired() has too few arguments starting with files. ( Ignorable by Annotation )

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

138
                || DependencyChecker::/** @scrutinizer ignore-call */ isExpired(...$meta)

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...
139
                || ($orig !== $meta[2] && $updatedMeta = \serialize($meta));
140
        }
141
142
        return false;
143
    }
144
}
145