Completed
Push — master ( 30445b...86287f )
by Anton
11s
created

Loader::enable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 0
dl 0
loc 13
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
     * Indication that loadmap changed.
41
     *
42
     * @var bool
43
     */
44
    private $changed = false;
45
46
    /**
47
     * Association between class and it's location.
48
     *
49
     * @var array
50
     */
51
    private $loadmap = [];
52
53
    /**
54
     * Is SPL methods handled.
55
     *
56
     * @var bool
57
     */
58
    private $enabled = false;
59
60
    /**
61
     * @invisible
62
     * @var MemoryInterface
63
     */
64
    protected $memory = null;
65
66
    /**
67
     * Loader will automatically handle SPL autoload functions to start caching loadmap.
68
     *
69
     * @param MemoryInterface $memory
70
     * @param bool            $enable Automatically enable.
71
     * @param string          $name
72
     */
73
    public function __construct(
74
        MemoryInterface $memory,
75
        bool $enable = true,
76
        string $name = self::MEMORY
77
    ) {
78
        $this->memory = $memory;
79
        $this->name = $name;
80
81
        if ($enable) {
82
            $this->enable();
83
        }
84
    }
85
86
    /**
87
     * Check if loader is enabled.
88
     *
89
     * @return bool
90
     */
91
    public function isEnabled(): bool
92
    {
93
        return $this->enabled;
94
    }
95
96
    /**
97
     * Handle SPL auto location, will go to top of spl chain.
98
     *
99
     * @return $this|self
100
     */
101
    public function enable(): Loader
102
    {
103
        if ($this->enabled) {
104
            return $this;
105
        }
106
107
        spl_autoload_register([$this, 'loadClass'], true, true);
108
109
        $this->enabled = true;
110
        $this->loadmap = (array)$this->memory->loadData(static::MEMORY);
111
112
        return $this;
113
    }
114
115
    /**
116
     * Stop handling SPL calls.
117
     *
118
     * @return $this|self
119
     */
120
    public function disable(): Loader
121
    {
122
        if (!$this->enabled) {
123
            return $this;
124
        }
125
126
        spl_autoload_unregister([$this, 'loadClass']);
127
128
        if ($this->changed) {
129
            $this->memory->saveData($this->name, $this->loadmap);
130
        }
131
132
        $this->enabled = false;
133
134
        return $this;
135
    }
136
137
    /**
138
     * Re-enable autoload to push it up into food chain.
139
     */
140
    public function reset()
141
    {
142
        return $this->disable()->enable();
143
    }
144
145
    /**
146
     * Try to load class declaration from memory or delegate it to other auto-loaders.
147
     *
148
     * @param string $class Class name with namespace included.
149
     */
150
    public function loadClass(string $class)
151
    {
152
        if (isset($this->loadmap[$class])) {
153
            try {
154
                //We already know route to class declaration
155
                include_once($this->classes[$class] = $this->loadmap[$class]);
156
157
                return;
158
            } catch (\Throwable $e) {
159
                //Delegating to external loaders
160
            }
161
        }
162
163
        $this->classes[$class] = null;
164
        $this->loadExternal($class);
165
    }
166
167
    /**
168
     * All loaded classes.
169
     *
170
     * @return array
171
     */
172
    public function getClasses(): array
173
    {
174
        return $this->classes;
175
    }
176
177
    /**
178
     * Check if desired class exists in loadmap.
179
     *
180
     * @param string $class
181
     *
182
     * @return bool
183
     */
184
    public function isKnown(string $class): bool
185
    {
186
        return array_key_exists($class, $this->classes);
187
    }
188
189
    /**
190
     * Destroy loader.
191
     */
192
    public function __destruct()
193
    {
194
        $this->disable();
195
    }
196
197
    /**
198
     * Try to load class using external auto-loaders.
199
     *
200
     * @param string $class
201
     */
202
    protected function loadExternal(string $class)
203
    {
204
        foreach (spl_autoload_functions() as $function) {
205
            if (
206
                is_array($function)
207
                && is_object($function[0]) 
208
                && get_class($function[0]) == self::class) {
209
                //Self
210
                continue;
211
            }
212
213
            //Call inner loader
214
            call_user_func($function, $class);
215
216
            if (
217
                class_exists($class, false)
218
                || interface_exists($class, false)
219
                || trait_exists($class, false)
220
            ) {
221
                $this->rememberFilename($class);
222
                break;
223
            }
224
        }
225
    }
226
227
    /**
228
     * @param string $class
229
     */
230
    private function rememberFilename(string $class)
231
    {
232
        //We need reflection to find class location
233
        $reflector = new \ReflectionClass($class);
234
235
        try {
236
            $filename = $reflector->getFileName();
237
            $filename = rtrim(
238
                str_replace(['\\', '//'], '/', $filename),
239
                '/'
240
            );
241
242
            if (file_exists($filename)) {
243
                $this->loadmap[$class] = $this->classes[$class] = $filename;
244
                $this->changed = true;
245
            }
246
        } catch (\Throwable $e) {
247
            //Get filename for classes located in PHARs might break reflection
248
        }
249
    }
250
}
251