1. 程式人生 > >Composer自動載入機制原始碼剖析

Composer自動載入機制原始碼剖析

1、autoload.php
要使用Composer的自動載入,首先需要引入該檔案

<?php

// autoload.php @generated by Composer

// 引入autoload_real.php
require_once __DIR__ . '/composer' . '/autoload_real.php';

// 下面一大長串是在我們安裝composer時由Composer自動生成的,我們需要關注的是它呼叫的autoload_real.php裡的靜態方法 getLoader()
return ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041::getLoader();

2、autoload_real.php

<?php

// autoload_real.php @generated by Composer

class ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041
{
    private static $loader;

    // 在例項化ClassLoader時呼叫該函式
    public static function loadClassLoader($class)
    {   
    // 該函式在getLoader方法裡被註冊為__autoload的實現,在例項化類時,如果類不存在,會自動呼叫該方法
// 如果例項化的函式是Composer\Autoload\ClassLoader 則引入該類 if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } public static function getLoader() { if (null !== self::$loader) { return self::$loader; } // 註冊 loadClassLoader函式作為 __autoload 的實現 spl_autoload_register(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader'), true, true);
// 例項化該方法時會自動呼叫上述方法註冊的函式, // loadClassLoader函式裡引入 ClassLoader類 self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit85b4dbf6b714d62ec745fcf3dd1a5041', 'loadClassLoader')); // PSR-0 的規則 $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } // PSR-4 的規則 $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 的實現,具體參考下文 // Class-Map部分 $loader->register(true); // Files方式 直接載入需要訪問的檔案 $includeFiles = require __DIR__ . '/autoload_files.php'; foreach ($includeFiles as $fileIdentifier => $file) { composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file); } return $loader; } } function composerRequire85b4dbf6b714d62ec745fcf3dd1a5041($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { // 載入需要呼叫的檔案 require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } }

3、ClassLoader.php (部分核心程式碼)

① PSR-0 規則

     /**
     * Registers a set of PSR-0 directories for a given prefix,
     * replacing any others previously set for this prefix.
     *
     * @param string       $prefix The prefix
     * @param array|string $paths  The PSR-0 base directories
     */

    // $prefix、$paths分別相當於autoload_namespaces.php裡返回陣列的key和value值

    public function set($prefix, $paths)
    {   
        if (!$prefix) {
            $this->fallbackDirsPsr0 = (array) $paths;
        } else {
        // $prefix[0]取得是首個字元
            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
        }
    }

② PSR-4規則

    /**
     * Registers a set of PSR-4 directories for a given namespace,
     * replacing any others previously set for this namespace.
     *
     * @param string       $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths  The PSR-4 base directories
     *
     * @throws \InvalidArgumentException
     */
    public function setPsr4($prefix, $paths)
    {
        if (!$prefix) {
            $this->fallbackDirsPsr4 = (array) $paths;
        } else {
            $length = strlen($prefix);

            // PSR-4 名稱空間以 \ 結尾,如果不是,則不符合規則
            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;

        }
    }

③ Class-map方式

    /**
     * @param array $classMap Class to filename map
     */
    public function addClassMap(array $classMap)
    {
        if ($this->classMap) {
            $this->classMap = array_merge($this->classMap, $classMap);
        } else {
            $this->classMap = $classMap;
        }
    }

 /**
     * Registers this instance as an autoloader.
     *
     * @param bool $prepend Whether to prepend the autoloader or not
     */
    public function register($prepend = false)
    {   // $prepend為true時,spl_autoload_register() 會新增函式到佇列之首,而不是佇列尾部。 (具體請參考php文件) 
        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
    }

    /**
     * Loads the given class or interface.
     *
     * @param  string    $class The name of the class
     * @return bool|null True if loaded, null otherwise
     */
    public function loadClass($class)
    {   
        if ($file = $this->findFile($class)) {
            includeFile($file);

            return true;
        }
    }

什麼時候才會去呼叫上述註冊的loadClass函式呢?舉例來說

<?php 
require './vendor/autoload.php';
// 當我們例項化TestController的時候,就會自動呼叫spl_autoload_register註冊的函式loadClass,在loadClass中又會去呼叫findFile方法去查詢類檔案所在的位置,然後require引入

$test = new TestController;

echo $test->show();

至此Composer自動載入流程已講述完畢。具體的程式碼還需各位自己去研究。不得不驚歎,有了Composer真是太方便了!