20 July 2016

composer.json


首先需要了解下Composer的配置文件,在composer.json中,与类加载相关的指令有:

  • require
  • require-dev(root-only)
  • autoload
  • autoload-dev(root-only)
  • include-path
  • target-dir

每个指令在The composer.json Schema中都有说明。

root-only指在我们正在开发的项目中定义了composer.json(里面声明了项目依赖的包等),那么这个项目就是一个根包。require-devautoload-dev只在根包起作用。

require定义了项目依赖的包,这些包会被安装到与composer.json同级的vendor目录下。例如我们的项目A->B、C,而B->D,那么A是根包,B、C、D都会被安装到vendor下。

autoload为自动加载器定义了类名到类文件的映射关系。

1、PSR-4:命名空间到类文件路径的映射。

假如根包的composer.json配置如下:

{
    ……
    "require": {
        "foo/bar": "2.*"
    },
    "autoload": {
        "psr-4": {
            "App\\": "app/"
        }
    },
    ……
}

foo/bar包的composer.json配置如下:

{
    ……
    "autoload": {
        "psr-4": {
            "TTD\\": "src/TTD"
        }
    },
    ……
}

那么类目录结构会是:

/* 注释为根包项目中使用的类名 */
|-composer.json
|-app
| |-Takk.php            /* App\Takk */
| |-BAA
| | |-Uk.php            /* App\BAA\Uk */
|
|-vendor
| |-foo
| | |-bar
| | | |-composer.json
| | | |-src
| | | | |-TTD
| | | | | |-Kok.php     /* TTD\Kok */

2、PSR-0:命名空间到类文件路径的映射、带_的类名到类文件路径的映射,相比psr-4,多了以命名空间作为层级目录(目录层级更深),以及对_的支持。

假如根包的composer.json配置如下:

{
    ……
    "require": {
        "foo/bar": "2.*"
    },
    "autoload": {
        "psr-0": {
            "Aaa\\Bbb\\": "src/",
            "Ccc_Ddd_": "tsrc/"
        }
    },
    ……
}

foo/bar包的composer.json配置如下:

{
    ……
    "autoload": {
        "psr-0": {
            "TTD\\": "src/TTD",
            "Eee_Fff_": "src/EF"
        }
    },
    ……
}

那么类目录结构会是:

/* 注释为根包项目中使用的类名 */
|-composer.json
|-src
| |-Aaa
| | |-Bbb
| | | |-Jkd.php             /* Aaa\Bbb\Jkd */
| |-Ccc
| | |-Ddd
| | | |-Jkd.php             /* Ccc_Ddd_Jkd */
|-vendor
| |-foo
| | |-bar
| | | |-src
| | | | |-TTD
| | | | | |-TTD
| | | | | | |-Ipl.php       /* TTD\Ipl */
| | | |-src
| | | | |-EF
| | | | | |-Eee
| | | | | | |-Fff
| | | | | | | |-Jud.php     /* Eee_Fff_Jud */

3、classmap:扫描目录下所有.php.inc文件中的类,建立类名和类文件的映射,以路径层级作为命名空间。

假如根包的composer.json配置如下:

{
    ……
    "require": {
        "foo/bar": "2.*"
    },
    "autoload": {
        "classmap": ["App"]
    },
    ……
}

foo/bar包的composer.json配置如下:

{
    ……
    "autoload": {
        "classmap": ["Uuu/Xxx"]
    },
    ……
}

那么类目录结构会是:

/* 注释为根包项目中使用的类名 */
|-composer.json
|-App
| |-Htt
| | |-Koo.php           /* Htt\Koo */
|-vendor
| |-foo
| | |-bar
| | | |-Uuu
| | | | |-Xxx
| | | | |-Ppp
| | | | | | |-Kuu.php       /* Ppp\Kuu */

4、files:用于加载一些php函数。

假如根包的composer.json配置如下:

{
    ……
    "require": {
        "foo/bar": "2.*"
    },
    "autoload": {
        "files": ["src/Aaa/Hhh/K.php"]
    },
    ……
}

foo/bar包的composer.json配置如下:

{
    ……
    "autoload": {
        "files": ["src/Yyy/Fd.php"]
    },
    ……
}

那么类目录结构会是:

/* 注释为根包项目中使用的类名 */
|-composer.json
|-src
| |-Aaa
| | |-Hhh
| | | |-K.php
|-vendor
| |-foo
| | |-bar
| | | |-src
| | | | |-Yyy
| | | | | |-Fd.php

autoload.php


在根包composer.json所在目录执行composer installcomposer dumpautoload后会在vendor目录下产生autoload.php和composer目录,那么我们的项目中只需要require这个文件autoload.php,就可以使用到上文分析出来的各种加载方式产生的类。

vendor目录结构:

|-vendor
|-autoload.php
| |-composer
| | |-autoload_psr4.php
| | |-autoload_namespaces.php
| | |-autoload_classmap.php
| | |-autoload_files.php
| | |-autoload_real.php
| | |-autoload_static.php
| | |-ClassLoader.php
| | |-installed.json
| | |-LICENSE
/* 其他包 */

直接跟踪autoload.php中的代码执行,可以了解到其流程主要就是实例化一个单例的\Composer\Autoload\ClassLoader,然后初始化其私有成员变量(根据PHP版本和是否为HHVM其初始化方式不同),之后调用spl_autoload_register注册\Composer\Autoload\ClassLoader::loadClass()方法到SPL __autoload函数队列中,最后逐个包含composer.json.autoload.files配置的文件。

其实看到autoload_real.php这个文件中ComposerAutoloaderInit***::getLoader()方法,对其代码有些疑惑的:

1、为啥 \Composer\Autoload\ClassLoader的实例化不直接require __DIR__ . '/ClassLoader.php';

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

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

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

2、根据PHP版本和是否为HHVM,对\Composer\Autoload\ClassLoader私有成员变量的初始化方式不同,其实看到autoload_static.php中的内容就想知道为何不在composer产生这些文件的时候,直接把结果赋值给\Composer\Autoload\ClassLoader私有成员变量?

<?php
    ……
    public static function getLoader()
    {
        $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION');
        if ($useStaticLoader) {
            require_once __DIR__ . '/autoload_static.php';

            call_user_func(\Composer\Autoload\ComposerStaticInitcdf2e5379301041a36605f84abb3abc4::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);
            }
        }
        ……
    }
    ……

类的查找


根据已注册的SPL __autoload函数,调用了\Composer\Autoload\ClassLoader的loadClass()->findFile()。在findFile中,查找顺序是:classmap->psr4->psr0,找到的话就返回类文件的路径名。