lavarel基础

4. index.php逻辑解析

因为所有的请求的入口点都是public下的index文件,而且index文件内也存在类的自动加载功能的实现,pop链的寻找也是需要类的引用的,这里就来看一下index文件内的类的自动加载是怎么实现的。

require __DIR__.'/../vendor/autoload.php';

首先引入了autoload.php文件,跟进文件内容。

require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitc5a142b250fa8e551e9c1fb2f8d7ada0::getLoader();

在文件内加载了autoload_real.php文件,调用了静态函数getloader()并将其处理之后的值作为返回值。跟进getloader()函数。

    public static function getLoader()
    {
        if (null !== self::$loader) {
            return self::$loader;
        }

        spl_autoload_register(array('ComposerAutoloaderInitc5a142b250fa8e551e9c1fb2f8d7ada0', 'loadClassLoader'), true, true);
        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
        spl_autoload_unregister(array('ComposerAutoloaderInitc5a142b250fa8e551e9c1fb2f8d7ada0', 'loadClassLoader'));

首先判断loader是否为空,因为自动加载类函数是只能有一个。注册了一个函数函数名为哈希值和单词的组合,实例化了一个classloader类的对象,跟进classloader函数,这个函数所在文件的名字为classloader.php是自动加载的核心类。这里需要注意这几个文件的意义:

autoload_real.php: 自动加载功能的引导类。
composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册(spl_autoload_register())。

ClassLoader.php : composer 加载类。
composer 自动加载功能的核心类。

autoload_static.php : 顶级命名空间初始化类,
用于给核心类初始化顶级命名空间。

autoload_classmap.php : 自动加载的最简单形式,
有完整的命名空间和文件目录的映射;

autoload_files.php : 用于加载全局函数的文件,
存放各个全局函数所在的文件路径名;

autoload_namespaces.php : 符合 PSR0 标准的自动加载文件,
存放着顶级命名空间与文件的映射;

autoload_psr4.php : 符合 PSR4 标准的自动加载文件,
存放着顶级命名空间与文件的映射;

此外还应该关注一下注册的那个函数:

    public static function loadClassLoader($class)
    {
        if ('Composer\Autoload\ClassLoader' === $class) {
            require __DIR__ . '/ClassLoader.php';
        }
    }

可以看到这个函数直接require了classloader文件,为下一步的类的实例化做准备,随后又销毁了这个的注册函数。

$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInitc5a142b250fa8e551e9c1fb2f8d7ada0::getInitializer($loader));
        } else {
            $map = require __DIR__ . '/autoload_namespaces.php';
            foreach ($map as $namespace => $path) {
                $loader->set($namespace, $path);
            }

            $map = require __DIR__ . '/autoload_psr4.php';
            foreach ($map as $namespace => $path) {
                $loader->setPsr4($namespace, $path);
            }

            $classMap = require __DIR__ . '/autoload_classmap.php';
            if ($classMap) {
                $loader->addClassMap($classMap);
            }
        }

这部分内容是对自动加载类的初始化。主要是给自动加载类初始化顶级命名空间映射。初始化类的方法有两种,一种是直接使用autoload_static文件进行初始化,另外一种就是使用核心类的接口进行初始化。继续跟进autoload_static文件,在上一步映射关系的初始化的两个方法中的第一个方法需要用到的函数时getInitializer($loader)函数。继续跟进。

<?php
  class ComposerStaticInit7b790917ce8899df9af8ed53631a1c29{
     public static $files = array(...);
     public static $prefixLengthsPsr4 = array(...);
     public static $prefixDirsPsr4 = array(...);
     public static $prefixesPsr0 = array(...);
     public static $classMap = array (...);

    public static function getInitializer(ClassLoader $loader)
    {
      return \Closure::bind(function () use ($loader) {
          $loader->prefixLengthsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixLengthsPsr4;

          $loader->prefixDirsPsr4
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixDirsPsr4;

          $loader->prefixesPsr0
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$prefixesPsr0;

          $loader->classMap
                          = ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$classMap;

      }, null, ClassLoader::class);
  }

getInitializer函数的作用是将自动加载类的命名空间的映射给了classload类。其中classmap是简单粗暴的命名空间与目录的映射所以会非常大。下面的是PSR4 标准顶级命名空间映射数组:

<?php
  public static $prefixLengthsPsr4 = array(
      'p' => array (
        'phpDocumentor\\Reflection\\' => 25,
    ),
      'S' => array (
        'Symfony\\Polyfill\\Mbstring\\' => 26,
        'Symfony\\Component\\Yaml\\' => 23,
        'Symfony\\Component\\VarDumper\\' => 28,
        ...
    ),
  ...);

  public static $prefixDirsPsr4 = array (
      'phpDocumentor\\Reflection\\' => array (
        0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
        1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
        2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
    ),
       'Symfony\\Polyfill\\Mbstring\\' => array (
        0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
    ),
      'Symfony\\Component\\Yaml\\' => array (
        0 => __DIR__ . '/..' . '/symfony/yaml',
    ),
  ...)

首先会根据命名空间的首字母去查找数组内的键值,但是对应的值仍为一个数组再根据顶级命名空间的长度去寻找检索对应顶级命名空间的名字。是什么样的一个流程呢?首先拿到一个命名空间之后Symfony\Polyfill\Mbstring\example,通过前缀索引和字符串匹配我们得到了

<?php
    'Symfony\\Polyfill\\Mbstring\\' => 26,

这条记录,键是顶级命名空间,值是命名空间的长度。拿到顶级命名空间后去 $prefixDirsPsr4数组 获取它的映射目录数组:(注意映射目录可能不止一条)

<?php
  'Symfony\\Polyfill\\Mbstring\\' => array (
              0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
          )

然后我们就可以将命名空间 Symfony\\Polyfill\\Mbstring\\example 前26个字符替换成目录 __DIR__ . '/..' . '/symfony/polyfill-mbstring ,我们就得到了__DIR__ . '/..' . '/symfony/polyfill-mbstring/example.php,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败。

那是怎么使用核心类的接口进行初始化的呢?如果PHP版本低于 5.6 或者使用 HHVM 虚拟机环境,那么就要使用核心类的接口进行初始化。

<?php
    // PSR0 标准
    $map = require __DIR__ . '/autoload_namespaces.php';
    foreach ($map as $namespace => $path) {
       $loader->set($namespace, $path);
    }

    // PSR4 标准
    $map = require __DIR__ . '/autoload_psr4.php';
    foreach ($map as $namespace => $path) {
       $loader->setPsr4($namespace, $path);
    }

    $classMap = require __DIR__ . '/autoload_classmap.php';
    if ($classMap) {
       $loader->addClassMap($classMap);
    }

autoload_psr4.php内是ps4标准的顶级命名空间的映射:

<?php
    return array(
    'XdgBaseDir\\'
        => array($vendorDir . '/dnoegel/php-xdg-base-dir/src'),

    'Webmozart\\Assert\\'
        => array($vendorDir . '/webmozart/assert/src'),

    'TijsVerkoyen\\CssToInlineStyles\\'
        => array($vendorDir . '/tijsverkoyen/css-to-inline-styles/src'),

    'Tests\\'
        => array($baseDir . '/tests'),

    'Symfony\\Polyfill\\Mbstring\\'
        => array($vendorDir . '/symfony/polyfill-mbstring'),
    ...
    )

初始化的接口:

<?php
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException(
                  "A non-empty PSR-4 prefix must end with a namespace separator."
                );
            }
            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
            $this->prefixDirsPsr4[$prefix] = (array) $paths;
        }
    }

和使用静态文件的初始化差不多:

( 前缀 -> 顶级命名空间,顶级命名空间 -> 顶级命名空间长度 )
( 顶级命名空间 -> 目录 )

命名空间映射:

autoload_classmap:

<?php
public static $classMap = array (
    'App\\Console\\Kernel'
        => __DIR__ . '/../..' . '/app/Console/Kernel.php',

    'App\\Exceptions\\Handler'
        => __DIR__ . '/../..' . '/app/Exceptions/Handler.php',
    ...
)

addClassMap:

<?php
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

自动加载核心类 ClassLoader 的静态初始化到这里就完成了。以上所作的是给loaderclassloader类的对象一个准备初始化前的数据库,也就是命名空间的映射关系。那么下一步就是命名空间的注册了,或者说是类的加载方式。

$loader->register(true);

跟进注册函数。

    public function register($prepend = false)
    {
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

在这个函数中注册了一个loadclass函数,继续跟进loadclass函数。

    public function loadClass($class)
    {
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

loadclass函数中看到首先调用了一个findfile函数,然后进行了一个包含的操作,其实includefile函数完成的就是文件的包含。loadclass函数负责按照 PSR 标准将顶层命名空间以下的内容转为对应的目录,也就是上面所说的将App\Console\KernelConsole\Kernel这一段转为目录。ClassLoaderregister() 函数将 loadClass()函数注册到 PHP 的 SPL 函数堆栈中,每当 PHP 遇到不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。所以loadClass()函数就是自动加载的关键了。

继续跟进findfile函数:

        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
            return false;
        }
        if (null !== $this->apcuPrefix) {
            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
            if ($hit) {
                return $file;
            }
        }

这半部分应该就是浏览classmap数组内查看是否有对应的命名空间的映射,不再赘述。

        $file = $this->findFileWithExtension($class, '.php');

        // Search for Hack files if we are running on HHVM
        if (false === $file && defined('HHVM_VERSION')) {
            $file = $this->findFileWithExtension($class, '.hh');
        }

        if (null !== $this->apcuPrefix) {
            apcu_add($this->apcuPrefix.$class, $file);
        }

        if (false === $file) {
            // Remember that this class does not exist.
            $this->missingClasses[$class] = true;
        }

        return $file;

核心函数是findFileWithExtension,因为函数比较复杂就不解释了,其实是因为懒。不过它的主要作用就是根据类名找到对应的文件路径然后进行包含。当然这是建立在classmap中没有找到对应的结果的前提下。

所以大体流程如下:

如果我们在代码中写下 new phpDocumentor\Reflection\Element(),PHP 会通过 SPL_autoload_register 调用 loadClass -> findFile -> findFileWithExtension。步骤如下:

  • 将 \ 转为文件分隔符/,加上后缀php,变成 $logicalPathPsr4, 即 phpDocumentor/Reflection//Element.php;
  • 利用命名空间第一个字母p作为前缀索引搜索 prefixLengthsPsr4 数组,查到下面这个数组:
        p' => 
            array (
                'phpDocumentor\\Reflection\\' => 25,
                'phpDocumentor\\Fake\\' => 19,
          )
  • 遍历这个数组,得到两个顶层命名空间 phpDocumentor\Reflection\ 和 phpDocumentor\Fake\
  • 在这个数组中查找 phpDocumentor\Reflection\Element,找出 phpDocumentor\Reflection\ 这个顶层命名空间并且长度为25。
  • 在prefixDirsPsr4 映射数组中得到phpDocumentor\Reflection\ 的目录映射为:
    'phpDocumentor\\Reflection\\' => 
        array (
            0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src',
            1 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src',
            2 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src',
        ),
  • 遍历这个映射数组,得到三个目录映射;
  • 查看 “目录+文件分隔符//+substr(logicalPathPsr4,length)”文件是否存在,存在即返回。这里就是
    '__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)'
  • 如果失败,则利用 fallbackDirsPsr4 数组里面的目录继续判断是否存在文件

参考链接:

https://segmentfault.com/a/1190000014948542#item-7-7
说点什么
支持Markdown语法
好耶,沙发还空着ヾ(≧▽≦*)o
Loading...