如何使用composer的autoload來自動加載自己編寫的函數庫與類庫?


composer的出現真是讓人們眼前一亮,web開發從此變成了一件很『好玩』的事情,開發一個CMS就像在搭積木,從packagist中取出『積木』搭建在自己的代碼中,一點一點搭建出一個屬於自己的王國。
從此以后我基本就拋棄了require和include函數,一個項目中,這兩個函數只可能出現一次,那就是require '../vendor/autoload.php'
那么,既然拋棄了傳統的文件包含方法,我們使用所有類庫都將用namespace和composer自帶的autoload。可是,我們自己編寫的函數庫與類庫,怎么用composer的方法來自動加載呢?

這就要從composer.json說起,我們需要通過修改這個文件來達到目的。
composer.json相當於是composer的配置文件,這個配置文件中有一個autoload段,比如我的一個項目:

1.png

其中又包含主要的兩個選項: files 和 psr-4。
files就是需要composer自動幫我們加載的函數庫(不含類),只要在后面的數組中將函數庫的文件路徑寫入即可。
psr-4顧名思義,是一個基於psr-4(http://www.php-fig.org/psr/psr-4/)規則的類庫自動加載對應關系,只要在其后的對象中,以 "命名空間": "路徑" 的方式寫入自己的類庫信息即可。
修改完成后,只要執行一下composer update,即可完成對應工作。

之后,我們在項目中,用如下方式即可加載自定義類庫:

new \Core\View();

composer的autoload將會自動包含"./core/view.php",並找到其中的Core命名空間下的View類。

我們來深挖一下,探索一下autoload的原理。
在我們修改完composer.json並執行update后,將會修改./vender/composer/autoload_psr4.php,比如我的某個項目,其中增加了這樣一個對應關系:

2.png

這其實就是我剛剛在.json中添加的對應關系,他等於將.josn的配置文件,換成了php的形式。
那么我看到vendor/autoload.php:

<?php

// autoload.php @generated by Composer

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

return ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326::getLoader();

其執行了一個自動生成的類ComposerAutoloaderInitff1d77c91141523097b07ee2acc23326中的getLoader方法。
跟進:

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

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

        $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);
        }

        $loader->register(true);

        $includeFiles = require __DIR__ . '/autoload_files.php';
        foreach ($includeFiles as $file) {
            composerRequireff1d77c91141523097b07ee2acc23326($file);
        }

        return $loader;
    }

可以明顯看到,他將autoload_namespaces.php、autoload_psr4.php、autoload_classmap.php、autoload_files.php等幾個配置文件包含了進來,並進行了相關處理(setPsr4),最后注冊(register)。
那么我們跟進register方法:

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

這函數就一行,但簡單明了,直接調用php自帶的spl_autoload_register函數,注冊處理__autoload的方法,也就是loadClass方法。再跟進loadClass方法:

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

            return true;
        }
    }

從函數名字就可以大概知道流程:如果存在$class對應的這個$file,則include進來。
那么進findFile方法里看看吧:

    public function findFile($class)
    {
        // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
        if ('\\' == $class[0]) {
            $class = substr($class, 1);
        }

        // class map lookup
        if (isset($this->classMap[$class])) {
            return $this->classMap[$class];
        }
        if ($this->classMapAuthoritative) {
            return false;
        }

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

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

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

        return $file;
    }

通過類名找文件,最終鎖定在findFileWithExtension方法中。
不過發現了一個小寶藏:在php5.3.0~5.3.2版本下,類名的第一個字符是\的小bug,也許以后挖漏洞會用上。
還是跟進findFileWithExtension方法:

    private function findFileWithExtension($class, $ext)
    {
        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

        $first = $class[0];
        if (isset($this->prefixLengthsPsr4[$first])) {
            foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-4 fallback dirs
        foreach ($this->fallbackDirsPsr4 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
                return $file;
            }
        }

        // PSR-0 lookup
        if (false !== $pos = strrpos($class, '\\')) {
            // namespaced class name
            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
        } else {
            // PEAR-like class name
            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
        }

        if (isset($this->prefixesPsr0[$first])) {
            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
                if (0 === strpos($class, $prefix)) {
                    foreach ($dirs as $dir) {
                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                            return $file;
                        }
                    }
                }
            }
        }

        // PSR-0 fallback dirs
        foreach ($this->fallbackDirsPsr0 as $dir) {
            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
                return $file;
            }
        }

        // PSR-0 include paths.
        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
            return $file;
        }
    }

最終實現將命名空間\類這樣的類名,給轉換成目錄名/類名.php這樣的路徑,並返回完整路徑。
我發現composer的autoload與php自帶的spl_autoload,在包含文件時有一點小區別。那就是,spl_autoload會查找.inc類型的文件名,但composer不會。
另外也可以發現,雖然配置文件的名字是autoload_psr4.php,但實際上psr0格式的自動加載也是支持的。二者最大的不同就是psr0中用"_"來代替目錄間的"\"。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM