PocketMine-MP 5.15.1 git-be6754494fdbbb9dd57c058ba0e33a4a78c4581f
ThreadSafeClassLoader.php
1<?php
2
3/*
4 *
5 * ____ _ _ __ __ _ __ __ ____
6 * | _ \ ___ ___| | _____| |_| \/ (_)_ __ ___ | \/ | _ \
7 * | |_) / _ \ / __| |/ / _ \ __| |\/| | | '_ \ / _ \_____| |\/| | |_) |
8 * | __/ (_) | (__| < __/ |_| | | | | | | | __/_____| | | | __/
9 * |_| \___/ \___|_|\_\___|\__|_| |_|_|_| |_|\___| |_| |_|_|
10 *
11 * This program is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * @author PocketMine Team
17 * @link http://www.pocketmine.net/
18 *
19 *
20 */
21
22declare(strict_types=1);
23
24namespace pocketmine\thread;
25
26use pmmp\thread\ThreadSafe;
27use pmmp\thread\ThreadSafeArray;
28use function class_exists;
29use function count;
30use function explode;
31use function file_exists;
32use function interface_exists;
33use function method_exists;
34use function spl_autoload_register;
35use function str_replace;
36use function strrpos;
37use function substr;
38use function trait_exists;
39use function trim;
40use const DIRECTORY_SEPARATOR;
41
48class ThreadSafeClassLoader extends ThreadSafe{
49
54 private ThreadSafeArray $fallbackLookup;
59 private ThreadSafeArray $psr4Lookup;
60
61 public function __construct(){
62 $this->fallbackLookup = new ThreadSafeArray();
63 $this->psr4Lookup = new ThreadSafeArray();
64 }
65
66 protected function normalizePath(string $path) : string{
67 $parts = explode("://", $path, 2);
68 if(count($parts) === 2){
69 return $parts[0] . "://" . str_replace('/', DIRECTORY_SEPARATOR, $parts[1]);
70 }
71 return str_replace('/', DIRECTORY_SEPARATOR, $parts[0]);
72 }
73
74 public function addPath(string $namespacePrefix, string $path, bool $prepend = false) : void{
75 $path = $this->normalizePath($path);
76 if($namespacePrefix === '' || $namespacePrefix === '\\'){
77 $this->fallbackLookup->synchronized(function() use ($path, $prepend) : void{
78 $this->appendOrPrependLookupEntry($this->fallbackLookup, $path, $prepend);
79 });
80 }else{
81 $namespacePrefix = trim($namespacePrefix, '\\') . '\\';
82 $this->psr4Lookup->synchronized(function() use ($namespacePrefix, $path, $prepend) : void{
83 $list = $this->psr4Lookup[$namespacePrefix] ?? null;
84 if($list === null){
85 $list = $this->psr4Lookup[$namespacePrefix] = new ThreadSafeArray();
86 }
87 $this->appendOrPrependLookupEntry($list, $path, $prepend);
88 });
89 }
90 }
91
95 protected function appendOrPrependLookupEntry(ThreadSafeArray $list, string $entry, bool $prepend) : void{
96 if($prepend){
97 $entries = $this->getAndRemoveLookupEntries($list);
98 $list[] = $entry;
99 foreach($entries as $removedEntry){
100 $list[] = $removedEntry;
101 }
102 }else{
103 $list[] = $entry;
104 }
105 }
106
113 protected function getAndRemoveLookupEntries(ThreadSafeArray $list) : array{
114 $entries = [];
115 while(($entry = $list->shift()) !== null){
116 $entries[] = $entry;
117 }
118 return $entries;
119 }
120
121 public function register(bool $prepend = false) : bool{
122 return spl_autoload_register(function(string $name) : void{
123 $this->loadClass($name);
124 }, true, $prepend);
125 }
126
130 public function loadClass(string $name) : bool{
131 $path = $this->findClass($name);
132 if($path !== null){
133 include($path);
134 if(!class_exists($name, false) && !interface_exists($name, false) && !trait_exists($name, false)){
135 return false;
136 }
137
138 if(method_exists($name, "onClassLoaded") && (new \ReflectionClass($name))->getMethod("onClassLoaded")->isStatic()){
139 $name::onClassLoaded();
140 }
141
142 return true;
143 }
144
145 return false;
146 }
147
151 public function findClass(string $name) : ?string{
152 $baseName = str_replace("\\", DIRECTORY_SEPARATOR, $name);
153
154 foreach($this->fallbackLookup as $path){
155 $filename = $path . DIRECTORY_SEPARATOR . $baseName . ".php";
156 if(file_exists($filename)){
157 return $filename;
158 }
159 }
160
161 // PSR-4 lookup
162 $logicalPathPsr4 = $baseName . ".php";
163
164 return $this->psr4Lookup->synchronized(function() use ($name, $logicalPathPsr4) : ?string{
165 $subPath = $name;
166 while(false !== $lastPos = strrpos($subPath, '\\')){
167 $subPath = substr($subPath, 0, $lastPos);
168 $search = $subPath . '\\';
169 $lookup = $this->psr4Lookup[$search] ?? null;
170 if($lookup !== null){
171 $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
172 foreach($lookup as $dir){
173 if(file_exists($file = $dir . $pathEnd)){
174 return $file;
175 }
176 }
177 }
178 }
179 return null;
180 });
181 }
182}
appendOrPrependLookupEntry(ThreadSafeArray $list, string $entry, bool $prepend)