Bitrix-D7  20.5.0
main/lib/loader.php
См. документацию.
1 <?php
2 namespace Bitrix\Main;
3 
5 
6 /**
7  * Class Loader loads required files, classes and modules. It is the only class which is included directly.
8  * @package Bitrix\Main
9  */
10 class Loader
11 {
12  /**
13  * Can be used to prevent loading all modules except main and fileman
14  */
15  const SAFE_MODE = false;
16 
17  const BITRIX_HOLDER = "bitrix";
18  const LOCAL_HOLDER = "local";
19 
20  protected static $safeModeModules = ["main" => true, "fileman" => true];
21  protected static $loadedModules = ["main" => true];
22  protected static $semiloadedModules = [];
23  protected static $modulesHolders = ["main" => self::BITRIX_HOLDER];
24  protected static $sharewareModules = [];
25 
26  /**
27  * Custom autoload paths.
28  * @var array [namespace => path]
29  */
30  protected static $namespaces = [];
31 
32  /**
33  * Returned by includeSharewareModule() if module is not found
34  */
35  const MODULE_NOT_FOUND = 0;
36  /**
37  * Returned by includeSharewareModule() if module is installed
38  */
39  const MODULE_INSTALLED = 1;
40  /**
41  * Returned by includeSharewareModule() if module works in demo mode
42  */
43  const MODULE_DEMO = 2;
44  /**
45  * Returned by includeSharewareModule() if the trial period is expired
46  */
48 
49  protected static $autoLoadClasses = [];
50 
51  /**
52  * @var bool Controls throwing exception by requireModule method
53  */
54  protected static $requireThrowException = true;
55 
56  /** @deprecated */
57  const ALPHA_LOWER = "qwertyuioplkjhgfdsazxcvbnm";
58  /** @deprecated */
59  const ALPHA_UPPER = "QWERTYUIOPLKJHGFDSAZXCVBNM";
60 
61  /**
62  * Includes a module by its name.
63  *
64  * @param string $moduleName Name of the included module
65  * @return bool Returns true if module was included successfully, otherwise returns false
66  * @throws LoaderException
67  */
68  public static function includeModule($moduleName)
69  {
70  if (!is_string($moduleName) || $moduleName == "")
71  {
72  throw new LoaderException("Empty module name");
73  }
74  if (preg_match("#[^a-zA-Z0-9._]#", $moduleName))
75  {
76  throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
77  }
78 
79  $moduleName = strtolower($moduleName);
80 
81  if (self::SAFE_MODE)
82  {
83  if (!isset(self::$safeModeModules[$moduleName]))
84  {
85  return false;
86  }
87  }
88 
89  if (isset(self::$loadedModules[$moduleName]))
90  {
91  return self::$loadedModules[$moduleName];
92  }
93 
94  if (isset(self::$semiloadedModules[$moduleName]))
95  {
96  trigger_error("Module '".$moduleName."' is in loading progress", E_USER_WARNING);
97  }
98 
99  $arInstalledModules = ModuleManager::getInstalledModules();
100  if (!isset($arInstalledModules[$moduleName]))
101  {
102  return (self::$loadedModules[$moduleName] = false);
103  }
104 
105  $documentRoot = self::getDocumentRoot();
106 
107  $moduleHolder = self::LOCAL_HOLDER;
108  $pathToInclude = $documentRoot."/".$moduleHolder."/modules/".$moduleName;
109  if (!file_exists($pathToInclude))
110  {
111  $moduleHolder = self::BITRIX_HOLDER;
112  $pathToInclude = $documentRoot."/".$moduleHolder."/modules/".$moduleName;
113  if (!file_exists($pathToInclude))
114  {
115  return (self::$loadedModules[$moduleName] = false);
116  }
117  }
118 
119  //register a PSR-4 base folder for the module
120  if(strpos($moduleName, ".") !== false)
121  {
122  //partner's module
123  $baseName = str_replace(".", "\\", ucwords($moduleName, "."));
124  }
125  else
126  {
127  //bitrix's module
128  $baseName = "Bitrix\\".ucfirst($moduleName);
129  }
130  self::registerNamespace($baseName, $documentRoot."/".$moduleHolder."/modules/".$moduleName."/lib");
131 
132  self::$modulesHolders[$moduleName] = $moduleHolder;
133 
134  $res = true;
135  if(file_exists($pathToInclude."/include.php"))
136  {
137  //recursion control
138  self::$semiloadedModules[$moduleName] = true;
139 
140  $res = self::includeModuleInternal($pathToInclude."/include.php");
141 
142  unset(self::$semiloadedModules[$moduleName]);
143  }
144 
145  self::$loadedModules[$moduleName] = ($res !== false);
146 
147  if(self::$loadedModules[$moduleName] == false)
148  {
149  //unregister the namespace if "include" fails
150  self::unregisterNamespace($baseName);
151  }
152  else
153  {
154  ServiceLocator::getInstance()->registerByModuleSettings($moduleName);
155  }
156 
157  return self::$loadedModules[$moduleName];
158  }
159 
160  /**
161  * Includes module by its name, throws an exception in case of failure
162  *
163  * @param $moduleName
164  *
165  * @return bool
166  * @throws LoaderException
167  */
168  public static function requireModule($moduleName)
169  {
170  $included = self::includeModule($moduleName);
171 
172  if (!$included && self::$requireThrowException)
173  {
174  throw new LoaderException("Required module `{$moduleName}` was not found");
175  }
176 
177  return $included;
178  }
179 
180  private static function includeModuleInternal($path)
181  {
182  /** @noinspection PhpUnusedLocalVariableInspection */
183  global $DB, $MESS;
184  return include_once($path);
185  }
186 
187  /**
188  * Includes shareware module by its name.
189  * Module must initialize constant <module name>_DEMO = Y in include.php to define demo mode.
190  * include.php must return false to define trial period expiration.
191  * Constants is used because it is easy to obfuscate them.
192  *
193  * @param string $moduleName Name of the included module
194  * @return int One of the following constant: Loader::MODULE_NOT_FOUND, Loader::MODULE_INSTALLED, Loader::MODULE_DEMO, Loader::MODULE_DEMO_EXPIRED
195  */
196  public static function includeSharewareModule($moduleName)
197  {
198  if (isset(self::$sharewareModules[$moduleName]))
199  {
200  return self::$sharewareModules[$moduleName];
201  }
202 
203  $module = str_replace(".", "_", $moduleName);
204 
205  if (self::includeModule($moduleName))
206  {
207  if (defined($module."_DEMO") && constant($module."_DEMO") == "Y")
208  {
209  self::$sharewareModules[$moduleName] = self::MODULE_DEMO;
210  }
211  else
212  {
213  self::$sharewareModules[$moduleName] = self::MODULE_INSTALLED;
214  }
215 
216  return self::$sharewareModules[$moduleName];
217  }
218 
219  if (defined($module."_DEMO") && constant($module."_DEMO") == "Y")
220  {
221  return (self::$sharewareModules[$moduleName] = self::MODULE_DEMO_EXPIRED);
222  }
223 
224  return (self::$sharewareModules[$moduleName] = self::MODULE_NOT_FOUND);
225  }
226 
227  public static function clearModuleCache($moduleName)
228  {
229  if (!is_string($moduleName) || $moduleName == "")
230  {
231  throw new LoaderException("Empty module name");
232  }
233 
234  if($moduleName !== "main")
235  {
236  unset(self::$loadedModules[$moduleName]);
237  unset(self::$modulesHolders[$moduleName]);
238  }
239 
240  unset(self::$sharewareModules[$moduleName]);
241  }
242 
243  /**
244  * Returns document root
245  *
246  * @return string Document root
247  */
248  public static function getDocumentRoot()
249  {
250  static $documentRoot = null;
251  if ($documentRoot === null)
252  {
253  $documentRoot = rtrim($_SERVER["DOCUMENT_ROOT"], "/\\");
254  }
255  return $documentRoot;
256  }
257 
258  /**
259  * Registers classes for auto loading.
260  * All the frequently used classes should be registered for auto loading (performance).
261  * It is not necessary to register rarely used classes. They can be found and loaded dynamically.
262  *
263  * @param string $moduleName Name of the module. Can be null if classes are not part of any module
264  * @param array $classes Array of classes with class names as keys and paths as values.
265  * @throws LoaderException
266  */
267  public static function registerAutoLoadClasses($moduleName, array $classes)
268  {
269  if (empty($classes))
270  {
271  return;
272  }
273  if (($moduleName !== null) && empty($moduleName))
274  {
275  throw new LoaderException(sprintf("Module name '%s' is not correct", $moduleName));
276  }
277 
278  foreach ($classes as $class => $file)
279  {
280  $class = ltrim($class, "\\");
281  $class = strtolower($class);
282 
283  self::$autoLoadClasses[$class] = [
284  "module" => $moduleName,
285  "file" => $file,
286  ];
287  }
288  }
289 
290  /**
291  * Registers namespaces with custom paths.
292  * e.g. ('Bitrix\Main\Dev', '/home/bitrix/web/site/bitrix/modules/main/dev/lib')
293  *
294  * @param string $namespace A namespace prefix.
295  * @param string $path An absolute path.
296  */
297  public static function registerNamespace($namespace, $path)
298  {
299  $namespace = trim($namespace, "\\")."\\";
300  $namespace = strtolower($namespace);
301 
302  $path = rtrim($path, "/\\");
303  $depth = substr_count(rtrim($namespace, "\\"), "\\");
304 
305  self::$namespaces[$namespace] = [
306  "path" => $path,
307  "depth" => $depth,
308  ];
309  }
310 
311  /**
312  * Unregisters a namespace.
313  * @param string $namespace
314  */
315  public static function unregisterNamespace($namespace)
316  {
317  $namespace = trim($namespace, "\\")."\\";
318  $namespace = strtolower($namespace);
319 
320  unset(self::$namespaces[$namespace]);
321  }
322 
323  /**
324  * Registers an additional autoload handler.
325  * @param callable $handler
326  */
327  public static function registerHandler(callable $handler)
328  {
329  \spl_autoload_register($handler);
330  }
331 
332  /**
333  * PSR-4 compatible autoloader.
334  * https://www.php-fig.org/psr/psr-4/
335  *
336  * @param $className
337  */
338  public static function autoLoad($className)
339  {
340  // fix web env
341  $className = ltrim($className, "\\");
342 
343  $classLower = strtolower($className);
344 
345  static $documentRoot = null;
346  if ($documentRoot === null)
347  {
348  $documentRoot = self::getDocumentRoot();
349  }
350 
351  //optimization via direct paths
352  if (isset(self::$autoLoadClasses[$classLower]))
353  {
354  $pathInfo = self::$autoLoadClasses[$classLower];
355  if ($pathInfo["module"] != "")
356  {
357  $module = $pathInfo["module"];
358  $holder = (isset(self::$modulesHolders[$module])? self::$modulesHolders[$module] : self::BITRIX_HOLDER);
359  require_once($documentRoot."/".$holder."/modules/".$module."/".$pathInfo["file"]);
360  }
361  else
362  {
363  require_once($documentRoot.$pathInfo["file"]);
364  }
365  return;
366  }
367 
368  if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $className))
369  {
370  return;
371  }
372 
373  $tryFiles = [[
374  "real" => $className,
375  "lower" => $classLower,
376  ]];
377 
378  if (substr($classLower, -5) == "table")
379  {
380  // old *Table stored in reserved files
381  $tryFiles[] = [
382  "real" => substr($className, 0, -5),
383  "lower" => substr($classLower, 0, -5),
384  ];
385  }
386 
387  foreach ($tryFiles as $classInfo)
388  {
389  $classParts = explode("\\", $classInfo["lower"]);
390 
391  //remove class name
392  array_pop($classParts);
393 
394  while(!empty($classParts))
395  {
396  //go from the end
397  $namespace = implode("\\", $classParts)."\\";
398 
399  if(isset(self::$namespaces[$namespace]))
400  {
401  //found
402  $depth = self::$namespaces[$namespace]["depth"];
403  $path = self::$namespaces[$namespace]["path"];
404 
405  $fileParts = explode("\\", $classInfo["real"]);
406 
407  for ($i=0; $i <= $depth; $i++)
408  {
409  array_shift($fileParts);
410  }
411 
412  $classPath = implode("/", $fileParts);
413 
414  $classPathLower = strtolower($classPath);
415 
416  // final path lower case
417  $filePath = $path.'/'.$classPathLower.".php";
418 
419  if (file_exists($filePath))
420  {
421  require_once($filePath);
422  break 2;
423  }
424 
425  // final path original case
426  $filePath = $path.'/'.$classPath.".php";
427 
428  if (file_exists($filePath))
429  {
430  require_once($filePath);
431  break 2;
432  }
433  }
434 
435  //try the shorter namespace
436  array_pop($classParts);
437  }
438  }
439  }
440 
441  /**
442  * @param $className
443  *
444  * @throws LoaderException
445  */
446  public static function requireClass($className)
447  {
448  $file = ltrim($className, "\\"); // fix web env
449  $file = strtolower($file);
450 
451  if (preg_match("#[^\\\\/a-zA-Z0-9_]#", $file))
452  return;
453 
454  $tryFiles = [$file];
455 
456  if (substr($file, -5) == "table")
457  {
458  // old *Table stored in reserved files
459  $tryFiles[] = substr($file, 0, -5);
460  }
461 
462  foreach ($tryFiles as $file)
463  {
464  $file = str_replace('\\', '/', $file);
465  $arFile = explode("/", $file);
466 
467  if ($arFile[0] === "bitrix")
468  {
469  array_shift($arFile);
470 
471  if (empty($arFile))
472  {
473  break;
474  }
475 
476  $module = array_shift($arFile);
477  if ($module == null || empty($arFile))
478  {
479  break;
480  }
481  }
482  else
483  {
484  $module1 = array_shift($arFile);
485  $module2 = array_shift($arFile);
486 
487  if ($module1 == null || $module2 == null || empty($arFile))
488  {
489  break;
490  }
491 
492  $module = $module1.".".$module2;
493  }
494 
495  if (!self::includeModule($module))
496  {
497  throw new LoaderException(sprintf(
498  "There is no `%s` class, module `%s` is unavailable", $className, $module
499  ));
500  }
501  }
502 
503  self::autoLoad($className);
504  }
505 
506  /**
507  * Checks if file exists in /local or /bitrix directories
508  *
509  * @param string $path File path relative to /local/ or /bitrix/
510  * @param string|null $root Server document root, default self::getDocumentRoot()
511  * @return string|bool Returns combined path or false if the file does not exist in both dirs
512  */
513  public static function getLocal($path, $root = null)
514  {
515  if ($root === null)
516  {
517  $root = self::getDocumentRoot();
518  }
519 
520  if (file_exists($root."/local/".$path))
521  {
522  return $root."/local/".$path;
523  }
524  elseif (file_exists($root."/bitrix/".$path))
525  {
526  return $root."/bitrix/".$path;
527  }
528  else
529  {
530  return false;
531  }
532  }
533 
534  /**
535  * Checks if file exists in personal directory.
536  * If $_SERVER["BX_PERSONAL_ROOT"] is not set than personal directory is equal to /bitrix/
537  *
538  * @param string $path File path relative to personal directory
539  * @return string|bool Returns combined path or false if the file does not exist
540  */
541  public static function getPersonal($path)
542  {
543  $root = self::getDocumentRoot();
544  $personal = (isset($_SERVER["BX_PERSONAL_ROOT"])? $_SERVER["BX_PERSONAL_ROOT"] : "");
545 
546  if ($personal <> '' && file_exists($root.$personal."/".$path))
547  {
548  return $root.$personal."/".$path;
549  }
550 
551  return self::getLocal($path, $root);
552  }
553 
554  /**
555  * Changes requireModule behavior
556  *
557  * @param bool $requireThrowException
558  */
560  {
561  self::$requireThrowException = (bool) $requireThrowException;
562  }
563 }
564 
565 class LoaderException extends \Exception
566 {
567  public function __construct($message = "", $code = 0, \Exception $previous = null)
568  {
569  parent::__construct($message, $code, $previous);
570  }
571 }
572 
573 //main usually is included directly
574 Loader::registerNamespace("Bitrix\\Main", Loader::getDocumentRoot()."/bitrix/modules/main/lib");
575 Loader::registerNamespace("Bitrix\\UI", Loader::getDocumentRoot()."/bitrix/modules/ui/lib");
576 Loader::registerNamespace("Psr\\Container", Loader::getDocumentRoot()."/bitrix/modules/main/vendor/psr/container/src");
577 
578 \spl_autoload_register([Loader::class, 'autoLoad']);
__construct($message="", $code=0, \Exception $previous=null)
const MODULE_INSTALLED
Returned by includeSharewareModule() if module is installed.
static requireModule($moduleName)
Includes module by its name, throws an exception in case of failure.
static getDocumentRoot()
Returns document root.
static includeModule($moduleName)
Includes a module by its name.
const MODULE_DEMO_EXPIRED
Returned by includeSharewareModule() if the trial period is expired.
static clearModuleCache($moduleName)
static registerHandler(callable $handler)
Registers an additional autoload handler.
static registerAutoLoadClasses($moduleName, array $classes)
Registers classes for auto loading.
static unregisterNamespace($namespace)
Unregisters a namespace.
static registerNamespace($namespace, $path)
Registers namespaces with custom paths.
const MODULE_NOT_FOUND
Returned by includeSharewareModule() if module is not found.
static includeSharewareModule($moduleName)
Includes shareware module by its name.
const SAFE_MODE
Can be used to prevent loading all modules except main and fileman.
static getLocal($path, $root=null)
Checks if file exists in /local or /bitrix directories.
static setRequireThrowException($requireThrowException)
Changes requireModule behavior.
static getPersonal($path)
Checks if file exists in personal directory.
static requireClass($className)
const MODULE_DEMO
Returned by includeSharewareModule() if module works in demo mode.
static autoLoad($className)
PSR-4 compatible autoloader.
__construct(Base $connector)
Constructor.
Definition: resultview.php:40