当前位置: 代码网 > it编程>编程语言>Php > PHP Composer自动加载使用实战

PHP Composer自动加载使用实战

2024年05月18日 Php 我要评论
一、没有 composer 时 php 是怎么做的php 的 autoload 机制,可以在使用一个未导入的类时动态加载该类,从而实现延迟加载和管理依赖类文件的目的。__autoload 自动加载器p

一、没有 composer 时 php 是怎么做的

php 的 autoload 机制,可以在使用一个未导入的类时动态加载该类,从而实现延迟加载和管理依赖类文件的目的。

__autoload 自动加载器

php 中想要使用一个类,必须通过 require (指代 require_once, include_once 等) 的方式在文件开头声明要使用的类。当项目中类较多时,一个个声明加载显然不可行。

在 php5 版本,php 支持通过 __autoload 定义一个自动加载器,尝试加载未定义的类。 如:

// we've writen this code where we need
function __autoload($classname) {
    $filename = "./". $classname .".php";
    include_once($filename);
}
// we've called a class ***
$obj = new myclass();

但 __autoload 函数缺点比较明显:他只能定义一次,这样就会耦合所有依赖的类的自动加载逻辑,统统写到这个方法里,这时候就需要用到 spl_autoload_register 函数了。

使用 spl_autoload_register 注册多个自动加载器

spl 是 standard php library 的缩写。spl_autoload_register 最大的特点是支持注册多个自动加载器,这样就能实现将各个类库的自动加载逻辑分开,自己处理自己的加载逻辑。

function my_autoloader($class) {
    var_dump("my_autoloader", $class);
}
spl_autoload_register('my_autoloader');
// 静态方法
class myclass1 {
    public static function autoload($classname) {
        var_dump("myclass1 autoload", $classname);
    }
}
spl_autoload_register(array('myclass1', 'autoload'));
// 非静态方法
class myclass2 {
    public function autoload($classname) {
        var_dump("myclass2 autoload", $classname);
    }
}
$instance = new myclass2();
spl_autoload_register(array($instance, 'autoload'));
new \notdefineclassname();
/*
输出
string(32) "my_autoloader notdefineclassname"
string(36) "myclass1 autoload notdefineclassname"
string(36) "myclass2 autoload notdefineclassname"
*/

二、psr 规范

psr 即 php standards recommendation 是一个社区组织:https://www.php-fig.org/psr/,声明一系列规范来统一开发风格,减少互不兼容的困扰。规范中的 psr-4 代表:autoloading standard,即自动加载规范。

psr-4

其中规定:一个类的完整类名应该遵循一下规范:

\<命名空间>(\<子命名空间>)*\<类名>

即:

  • 完整的类名必须要有一个顶级命名空间,被称为 “vendor namespace”;
  • 完整的类名可以有一个或多个子命名空间;
  • 完整的类名必须有一个最终的类名;
  • 完整的类名中任意一部分中的下滑线都是没有特殊含义的;
  • 完整的类名可以由任意大小写字母组成;
  • 所有类名都必须是大小写敏感的。

看看例子:

应用的效果简单来说就是:将命名空间前缀 namespace prefix 替换成 base directory 目录,并将 \ 替换成 / 。一句话,命名空间可以表明类具体的存放位置。

三、composer 自动加载的过程

结合 spl_auto_register 和 psr-4 的命名空间规范,可以想象,我们可以通过类的命名空间,来找到具体类的存放位置,然后通过 require 将其加载进来生效,composer 就是这么干的。

接下来我们分两步看 composer 是怎么做的。

第一步,建立类的命名空间和类存放位置的映射关系

首先看 vendor 目录下的 autoload.php 文件,所有项目启动必然要先 require 这个文件。

// autoload.php @generated by composer
// vendor/autoload.php
require_once __dir__ . '/composer/autoload_real.php';
// 返回了autoload_real文件中的类方法
return composerautoloaderinit7e421c277f7e8f810a19524f0d771cdb::getloader();
/* ------------- */
// vendor/composer/autoload_real.php
public static function getloader()
{
    if (null !== self::$loader) {
        return self::$loader;
    }
    // p0 初始化classloader
    spl_autoload_register(array('composerautoloaderinit7e421c277f7e8f810a19524f0d771cdb', 'loadclassloader'), true, true);
    self::$loader = $loader = new \composer\autoload\classloader();
    spl_autoload_unregister(array('composerautoloaderinit7e421c277f7e8f810a19524f0d771cdb', 'loadclassloader'));
    $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';
        // p1 向classloader中set命名空间和文件路径映射关系
        call_user_func(\composer\autoload\composerstaticinit7e421c277f7e8f810a19524f0d771cdb::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);
        }
    }
    // p2 将classloader中的loadclass方法,注册为加载器
    $loader->register(true);
    if ($usestaticloader) {
        $includefiles = composer\autoload\composerstaticinit7e421c277f7e8f810a19524f0d771cdb::$files;
    } else {
        $includefiles = require __dir__ . '/autoload_files.php';
    }
    foreach ($includefiles as $fileidentifier => $file) {
        composerrequire7e421c277f7e8f810a19524f0d771cdb($fileidentifier, $file);
    }
    return $loader;
}

在代码 p0 处,上来先实例化一个 \composer\autoload\classloader 类,这个类里面维护了所有命名空间到类具体存放位置的映射关系。

接下来在 p1 处,根据 php 版本和运行环境,如是否运行在 hhvm 环境下,来区分如何向 classloader 中载入映射关系。

autoload_static.php 文件中定义的映射关系有三种:

public static $prefixlengthspsr4 = array (
    'p' => 
    array (
        'phpdocumentor\\reflection\\' => 25,
    ),
    'w' => 
    array (
        'webmozart\\assert\\' => 17,
    ),
    's' => 
    array (
        'symfony\\polyfill\\ctype\\' => 23,
    ),
    'r' => 
    array (
        'refactoringguru\\' => 16,
    ),
    'p' => 
    array (
        'prophecy\\' => 9,
    ),
    'd' => 
    array (
        'doctrine\\instantiator\\' => 22,
        'deepcopy\\' => 9,
    ),
);
public static $prefixdirspsr4 = array (
    'phpdocumentor\\reflection\\' => 
    array (
        0 => __dir__ . '/..' . '/phpdocumentor/reflection-common/src',
        1 => __dir__ . '/..' . '/phpdocumentor/reflection-docblock/src',
        2 => __dir__ . '/..' . '/phpdocumentor/type-resolver/src',
    ),
    'webmozart\\assert\\' => 
    array (
        0 => __dir__ . '/..' . '/webmozart/assert/src',
    ),
    'symfony\\polyfill\\ctype\\' => 
    array (
        0 => __dir__ . '/..' . '/symfony/polyfill-ctype',
    ),
    'refactoringguru\\' => 
    array (
        0 => __dir__ . '/../..' . '/',
    ),
    'prophecy\\' => 
    array (
        0 => __dir__ . '/..' . '/phpspec/prophecy/src/prophecy',
    ),
    'doctrine\\instantiator\\' => 
    array (
        0 => __dir__ . '/..' . '/doctrine/instantiator/src/doctrine/instantiator',
    ),
    'deepcopy\\' => 
    array (
        0 => __dir__ . '/..' . '/myclabs/deep-copy/src/deepcopy',
    ),
);
public static $classmap = array (
        'file_iterator' => __dir__ . '/..' . '/phpunit/php-file-iterator/src/iterator.php',
        'file_iterator_facade' => __dir__ . '/..' . '/phpunit/php-file-iterator/src/facade.php',
        'file_iterator_factory' => __dir__ . '/..' . '/phpunit/php-file-iterator/src/factory.php',
        'phpunit\\exception' => __dir__ . '/..' . '/phpunit/phpunit/src/exception.php',
    ...
);

classmap 是完整映射关系,prefixlengthspsr4 和 prefixdirspsr4 是当通过完整命名空间找不到时,通过在目标类名后加上 .php 再次寻找用。

到此,建立命名空间到类存放路径的关系已经完成了。

第二步,如何找到类并加载

在上面代码中,将 classloader 的 loadclass 方法注册成加载器:

public function loadclass($class)
{
    if ($file = $this->findfile($class)) {
        includefile($file);
        return true;
    }
}
function includefile($file)
{
    include $file;
}

其中 findfile 方法,就是通过类名,去寻找文件实际的位置,如果找到了,就通过 includefile 将文件加载进来。主要看看 findfile 中的逻辑:

public function findfile($class)
{
    // 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;
        }
    }
    $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;
}

对于类的加载十分简单,直接去 classmap 中取。如果取不到,则将目标类名追加 .php 后缀,去$prefixlengthspsr4 和 $prefixdirspsr4 中查找。

第三步,如何加载全局函数

if ($usestaticloader) {
    $includefiles = composer\autoload\composerstaticinit7e421c277f7e8f810a19524f0d771cdb::$files;
} else {
    $includefiles = require __dir__ . '/autoload_files.php';
}
foreach ($includefiles as $fileidentifier => $file) {
    composerrequire7e421c277f7e8f810a19524f0d771cdb($fileidentifier, $file);
}
return $loader;

还是通过 autoload_static.php 中定义的数据去加载:

// autoload_static.php
public static $files = array (
    '320cde22f66dd4f5d3fd621d3e88b98f' => __dir__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
    '6124b4c8570aa390c21fafd04a26c69f' => __dir__ . '/..' . '/myclabs/deep-copy/src/deepcopy/deep_copy.php',
);
// vendor/symfony/polyfill-ctype/bootstrap.php
if (!function_exists('ctype_alnum')) {
    function ctype_alnum($text) { return p\ctype::ctype_alnum($text); }
}
if (!function_exists('ctype_alpha')) {
    function ctype_alpha($text) { return p\ctype::ctype_alpha($text); }
}

至此 composer 自动加载的逻辑基本就过了一遍。

composer 的 classloader 中的 classmap 是怎么生成出来的?

答案就在 composer 的源码中:

https://github.com/composer/composer/blob/d0aac44ed210e13ec4a4370908a5b36553a2f16c/src/composer/autoload/autoloadgenerator.php

扫描所有包中的类,然后生成一个 php 文件,例如:getstaticfile 方法

参考:

php类自动加载的说明:

以上就是php composer自动加载使用实战的详细内容,更多关于php composer自动加载的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com