Completed
Branch 09branch (740a7d)
by Anton
02:52
created

Loader::loadClass()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 8
nc 3
nop 1
dl 0
loc 16
rs 9.4285
c 0
b 0
f 0
1
<?php
2
/**
3
 * Spiral Framework.
4
 *
5
 * @license   MIT
6
 * @author    Anton Titov (Wolfy-J)
7
 */
8
9
namespace Spiral\Core;
10
11
use Spiral\Core\Container\SingletonInterface;
12
13
/**
14
 * Can speed up class loading in some conditions. Used by profiler to show all loadead classes.
15
 *
16
 * Implementation work
17
 */
18
class Loader extends Component implements SingletonInterface
19
{
20
    /**
21
     * Default memory segment.
22
     */
23
    const MEMORY = 'loadmap';
24
25
    /**
26
     * Loader memory segment.
27
     *
28
     * @var string
29
     */
30
    private $name = '';
31
32
    /**
33
     * List of classes loaded while this working session.
34
     *
35
     * @var array
36
     */
37
    private $classes = [];
38
39
    /**
40
     * Association between class and it's location.
41
     *
42
     * @var array
43
     */
44
    private $loadmap = [];
45
46
    /**
47
     * Is SPL methods handled.
48
     *
49
     * @var bool
50
     */
51
    private $enabled = false;
52
53
    /**
54
     * @invisible
55
     * @var MemoryInterface
56
     */
57
    protected $memory = null;
58
59
    /**
60
     * Loader will automatically handle SPL autoload functions to start caching loadmap.
61
     *
62
     * @param MemoryInterface $memory
63
     * @param bool            $enable Automatically enable.
64
     * @param string          $name
65
     */
66
    public function __construct(
67
        MemoryInterface $memory,
68
        bool $enable = true,
69
        string $name = self::MEMORY
70
    ) {
71
        $this->memory = $memory;
72
        $this->name = $name;
73
74
        if ($enable) {
75
            $this->enable();
76
        }
77
    }
78
79
    /**
80
     * Check if loader is enabled.
81
     *
82
     * @return bool
83
     */
84
    public function isEnabled(): bool
85
    {
86
        return $this->enabled;
87
    }
88
89
    /**
90
     * Handle SPL auto location, will go to top of spl chain.
91
     *
92
     * @return $this|self
93
     */
94
    public function enable(): Loader
95
    {
96
        if ($this->enabled) {
97
            return $this;
98
        }
99
100
        spl_autoload_register([$this, 'loadClass'], true, true);
101
102
        $this->enabled = true;
103
        $this->loadmap = (array)$this->memory->loadData(static::MEMORY);
104
105
        return $this;
106
    }
107
108
    /**
109
     * Stop handling SPL calls.
110
     *
111
     * @return $this|self
112
     */
113
    public function disable(): Loader
114
    {
115
        if (!$this->enabled) {
116
            return $this;
117
        }
118
119
        spl_autoload_unregister([$this, 'loadClass']);
120
121
        $this->memory->saveData($this->name, $this->loadmap);
122
        $this->enabled = false;
123
124
        return $this;
125
    }
126
127
    /**
128
     * Re-enable autoload to push it up into food chain.
129
     */
130
    public function reset()
131
    {
132
        return $this->disable()->enable();
133
    }
134
135
    /**
136
     * Try to load class declaration from memory or delegate it to other auto-loaders.
137
     *
138
     * @param string $class Class name with namespace included.
139
     */
140
    public function loadClass(string $class)
141
    {
142
        if (isset($this->loadmap[$class])) {
143
            try {
144
                //We already know route to class declaration
145
                include_once($this->classes[$class] = $this->loadmap[$class]);
146
147
                return;
148
            } catch (\Throwable $e) {
149
                //Delegating to external loaders
150
            }
151
        }
152
153
        $this->classes[$class] = null;
154
        $this->loadExternal($class);
155
    }
156
157
    /**
158
     * All loaded classes.
159
     *
160
     * @return array
161
     */
162
    public function getClasses(): array
163
    {
164
        return $this->classes;
165
    }
166
167
    /**
168
     * Check if desired class exists in loadmap.
169
     *
170
     * @param string $class
171
     *
172
     * @return bool
173
     */
174
    public function isKnown(string $class): bool
175
    {
176
        return array_key_exists($class, $this->classes);
177
    }
178
179
    /**
180
     * Destroy loader.
181
     */
182
    public function __destruct()
183
    {
184
        $this->disable();
185
    }
186
187
    /**
188
     * Try to load class using external auto-loaders.
189
     *
190
     * @param string $class
191
     */
192
    protected function loadExternal(string $class)
193
    {
194
        foreach (spl_autoload_functions() as $function) {
195
            if (is_array($function) && get_class($function[0]) == self::class) {
196
                //Found ourselves
197
                continue;
198
            }
199
200
            //Call inner loader
201
            call_user_func($function, $class);
202
203
            if (
204
                class_exists($class, false)
205
                || interface_exists($class, false)
206
                || trait_exists($class, false)
207
            ) {
208
                //We need reflection to find class location
209
                $reflector = new \ReflectionClass($class);
210
211
                try {
212
                    $filename = $reflector->getFileName();
213
                    $filename = rtrim(
214
                        str_replace(['\\', '//'], '/', $filename),
215
                        '/'
216
                    );
217
218
                    if (file_exists($filename)) {
219
                        $this->loadmap[$class] = $this->classes[$class] = $filename;
220
                    }
221
                } catch (\Throwable $e) {
222
                    //Get filename for classes located in PHARs might break reflection
223
                }
224
225
                break;
226
            }
227
        }
228
    }
229
}
230