Deprecated
: htmlspecialchars(): Passing null to parameter #1 ($string) of type string is deprecated in
/www/wwwroot/testblog.58heshihu.com/var/Widget/Archive.php
on line
1057
首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
182 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
28
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
11
篇与
的结果
2024-05-28
Linux下卸载与安装Composer
卸载如果您尝试了上述步骤但未能成功卸载Composer,可能是由于权限或其他配置问题。您可以尝试以下方法来彻底卸载Composer:1. 查找Composer安装位置:在终端中执行以下命令,以查找Composer的安装位置:which composer这将显示Composer可执行文件的实际路径。您可以使用这个路径来手动删除Composer文件。2. 手动删除Composer文件:使用上一步中找到的Composer文件路径,您可以执行以下命令手动删除Composer文件:rm /path/to/composer请将/path/to/composer替换为您在第一步中找到的Composer文件路径。3. 清除Composer缓存:有时Composer会在全局位置或用户主目录下创建缓存文件,您可以尝试手动清除这些缓存文件。在终端中执行以下命令:rm -rf ~/.composer这将删除用户主目录下的Composer缓存文件。4. 验证卸载:最后,您可以通过在终端中运行composer命令来验证Composer是否已成功卸载。如果Composer已成功卸载,您将会看到类似“command not found”之类的消息。[root@xx ~]# composer -bash: /usr/bin/composer: No such file or directory看起来在您的系统上,Composer已经被删除了,但是系统仍在尝试执行Composer命令。这可能是因为之前的安装过程中创建了一些链接或者配置文件,导致系统仍然尝试执行已经删除的Composer命令。您可以尝试更新系统的命令缓存,以确保系统不再尝试执行已删除的Composer命令。在终端中执行以下命令:hash -r这个命令将清除当前shell的命令缓存,这样系统就会重新查找可执行文件的位置。安装执行下面命令,分别是下载Composer安装脚本验证安装脚本运行安装脚本删除安装脚本移动Composer可执行文件验证安装(如果一切顺利,能够看到Composer的版本信息)php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" php -r "if (hash_file('sha384', 'composer-setup.php') === 'dac665fdc30fdd8ec78b38b9800061b4150413ff2e3b6f88543c636f7cd84f6db9189d43a81e5503cda447da73c7e5b6') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" php composer-setup.php php -r "unlink('composer-setup.php');" mv composer.phar /usr/bin/composer composer --version参考:Composer下载页面
2024年05月28日
29 阅读
0 评论
0 点赞
2023-08-12
Composer命令全解析
Composer中文文档Composer命令全解析常用命令镜像配置中国镜像composer config -g repo.packagist composer https://packagist.phpcomposer.com配置其他厂商镜像阿里云 (好像说停用了)composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/腾讯composer config -g repo.packagist composer https://mirrors.cloud.tencent.com/composer/华为composer config -g repo.packagist composer https://repo.huaweicloud.com/repository/php/解除镜像composer config -g --unset repos.packagist项目搜索项目composer search ThinkPHP创建项目composer create-project topthink/ThinkPHP=5.1.* ./tp5安装扩展composer require laravel/laravel ">=5.5"移除扩展composer remove laravel/laravel生产最佳实践转换 PSR-0/4 autoloading 到 classmap 获得更快的载入速度,禁用开发者模式composer dump-autoload -o --no-dev 自动加载 autoload目前支持四种自动加载方式:PSR-0PSR-0 规范是 PHP5.2 之前的一种命名空间映射规范,它规定命名空间与文件路径的对应关系如下命名空间中的每个下划线字符(_)都会被转换成目录分隔符(/);命名空间中的每个命名空间分隔符(\)都会被转换成目录分隔符(/);命名空间中的首字母和下划线字符都会被转换成目录名和文件名中的小写字母;每个类的文件名必须与类名完全一致,包括大小写。"psr-0" : { "Foo\\" : "psr0src/", # "Foo_Bar_" : "psr0src/" },composer install/update 之后,PSR-0 引用全部合并到 vendor/composer/autoload_namespaces.phpPSR-4PSR-4 规范是较新的一种命名空间映射规范,它与 PSR-0 规范的区别在于:命名空间中的下划线字符不再被特殊处理,只有命名空间分隔符(\)会被转换成目录分隔符(/);命名空间中的首字母和下划线字符不再被强制转换成小写字母;类的文件名与类名可以不完全一致,但必须满足相对文件路径和类名的对应关系"psr-4": { #查找Afishpapa\Httptool\Http类时的路径为src/Http.php "Afishpapa\\Httptool\\": "src/" #可以在src/和lib/ 下面找 Monolog命名空间下的类 "psr-4": { "Monolog\\": ["src/", "lib/"] } #所有命名空间都来src/目录下找 "" : "src/" }composer install/update 之后,PSR-4 引用全部合并到 vendor/composer/automoad_psr4.php 中。return array( 'Afishpapa\\Httptool\\' => array($baseDir . '/src'), );Classmapcomposer install/update 之后,PSR-4 引用全部合并到 vendor/composer/autoload_classmap.php 中。你可以用 classmap 生成支持支持自定义加载的不遵循 PSR-0/4 规范的类库。Files通常作为函数库的载入方式(而非类库)。{ "autoload": { "files": ["src/MyLibrary/functions.php"] } }全局配置-v : 增加消息的详细程度,正常输出-vv : 增加消息的详细程度,更加详细得输出-vvv : 增加消息的详细程度,debug用-h : 显示帮助-q : 不要显示任何信息-n : 不要问任何交互问题-d : 设置工作目录--ansi: 强制输出 ANSI 编码--no-ansi: 禁用 ANSI 编码--version (-V): 展示所有应用版本--profile: 展示时间和内存信息常用配置--prefer-install: 默认值为distdist: 检查本地缓存压缩包,如果有直接复制到vendor目录,如果本地缓存没有,则去远程仓库下载压缩包,如果远程仓库没有提供压缩包,则尝试从github中安装包,并且删除.git版本信息,总之,dist能够快速地下载并安装依赖包,适用于大部分生产环境。 source: 直接从github中下载源码,保留.git信息,如果您需要对包进行自定义修改或者需要对其进行特殊的构建过程,则应该使用 source。 auto:2.1版本后已弃用-o: 生成自动加载器文件的优化版本,以加快类加载速度。-a: 默认情况下,Composer 会根据 composer.json 中的 PSR-4 和 PSR-0 配置来生成自动加载器, 使用 --classmap-authoritative 选项可以让 Composer 忽略 PSR-4 和 PSR-0 配置,而直接根据类文件生成一个类映射表(class map),并将其作为自动加载器的唯一来源。需要注意的是,使用 --classmap-authoritative 选项可能会导致一些问题,比如在添加新的类文件时需要重新生成类映射表,否则新添加的类无法被自动加载器加载。因此,建议在开发环境中使用这个选项来提高性能,但在生产环境中不要使用,以免出现问题。--dry-run: 执行安装过程的模拟运行,不会实际下载或安装任何软件包。--dev: 安装开发依赖项,包括测试框架和调试工具等。composer init以交互方式初始化 composer--name: 包名,格式为作者/名称,比如monolog/monolog--description: 简短描述--author: 作者名--type: 包的安装类型,默认librarylibrary : 它会简单的将文件复制到 vendor 目录 project : 当前包是一个项目,而不是一个库 metapackage : 一个空的包,包含依赖并且需要触发依赖的安装,这将不会对系统写入额外的文件。 composer-plugin : 它有一个自定义安装类型,可以为其它包提供一个 installler。--homepage: 官网首页--require: 引入包,格式为 包:版本 foo/bar:1.0.0--require-dev: 开发用的组件-s : 最小稳定性值 dev stable --license (-l): 许可证--repository: 指定一个或多个 Composer 仓库-a : 添加一个autoload.psr-4的对象到composer.jsoncomposer install如果存在 composer.lock 文件,它会从此文件读取依赖版本,这确保了该库的每个使用者都能得到相同的依赖版本。如果不存在 composer.lock 文件,它会从 composer.json 文件读取依赖版本,并把其安装到 vendor 目录下。如果没有 composer.lock 文件,composer 将在处理完依赖关系后创建它。composer require添加一个包到 composer.json 文件,如果没有 composer.json 就创建一个# 该命令会安装两个不同的软件包 # vendor/package:2.* : 版本号以 2. 开头,后面跟着任何版本号。 # vendor/package2:dev-master : 使用 dev-master 分支。这意味着它将安装该分支的最新版本,通常是开发版本,不属于正式发布。 composer require "vendor/package:2.*" vendor/package2:dev-mastercomposer update获取依赖的最新版本# 只更新这两个包vendor/package vendor/package2 composer update vendor/package vendor/package2 # 更新符合正则匹配的包 composer update "vendor/*" # 更新依赖包到指定版本,需符合composer.json的约束 composer update --with vendor/package:2.0.1composer remove移除依赖包# 移除这两个包 composer remove vendor/package vendor/package2 composer reinstall 重装包,如果不小心改了包文件,可以使用重装命令恢复 # 重装两个包 composer reinstall acme/foo acme/bar # 重装正则匹配的包 composer.phar reinstall "acme/*"composer check-platform-reqs用于检查您的 PHP 和扩展版本是否符合已安装包的平台要求--lock: 仅从锁定文件中检查要求,而不是从已安装的包中检查要求。 --no-dev: 不检查 require-dev 包要求。 -f: 格式> composer check-platform-reqs Checking platform requirements for packages in the vendor dir ext-json 1.7.0 success ext-libxml 7.3.4 success ext-mbstring 7.3.4 success ext-openssl 7.3.4 success ext-simplexml 7.3.4 success php 7.3.4 successcomposer globalglobal 允许您全局运行其他命令,如 install、remove、require,updatecomposer search monolog搜索依赖包-N : 只搜包名-O : 只搜作者-t : 搜全称λ composer search monolog monolog/monolog Sends your logs to files, sockets, inboxes, databases and various web services symfony/monolog-bundle Symfony MonologBundle symfony/monolog-bridge Provides integration for Monolog with various Symfony components ...composer show / composer info列出所有可用的包信息λ composer show defuse/php-encryption v2.3.1 Secure PHP Encryption Library laminas/laminas-diactoros 2.5.0 PSR HTTP Message implementations laminas/laminas-zendframework-bridge 1.4.1 Alias legacy ZF class names to Laminas Project equivalents. ...列出单个包的详情λ composer show slim/slim name : slim/slim descrip. : Slim is a PHP micro framework that helps you quickly write simple yet powerful web applications and APIs keywords : api, framework, micro, router versions : * 3.12.4 type : library license : MIT License (MIT) (OSI approved) https://spdx.org/licenses/MIT.html#licenseText homepage : https://slimframework.com source : [git] https://github.com/slimphp/Slim.git ce3cb65a06325fc9fe3d0223f2ae23113a767304 dist : [zip] https://api.github.com/repos/slimphp/Slim/zipball/ce3cb65a06325fc9fe3d0223f2ae23113a767304 ce3cb65a06325fc9fe3d0223f2ae23113a767304 path : D:\workspace\oauth\examples\vendor\slim\slim names : slim/slim, psr/http-message-implementation support issues : https://github.com/slimphp/Slim/issues source : https://github.com/slimphp/Slim/tree/3.12.4 autoload psr-4 Slim\ => Slim requires ext-json * ext-libxml * ext-simplexml * nikic/fast-route ^1.0 php >=5.5.0 pimple/pimple ^3.0 psr/container ^1.0 psr/http-message ^1.0 requires (dev) phpunit/phpunit ^4.0 squizlabs/php_codesniffer ^3.6.0 provides psr/http-message-implementation 1.0composer outdated列出所有安装包是否可以更新λ composer outdated Direct dependencies required in composer.json: laminas/laminas-diactoros 2.5.0 2.14.0 PSR HTTP Message implementations league/event 2.2.0 3.0.1 Event package slim/slim 3.12.4 4.9.0 Slim is a PHP micro framework that helps you quickly write simple yet po... Transitive dependencies not required in composer.json: psr/container 1.1.1 2.0.1 Common Container Interface (PHP FIG PSR-11)composer browse / composer home浏览器直接打开这个包的 github 仓库# 打开这个包的官网 composer browser -H slim/slim # 打印slim的github仓库链接 composer browser -s slim/slimcomposer suggests给你人生路上一点建议λ composer suggests lcobucci/jwt suggests: - lcobucci/clock: * 1 additional suggestions by transitive dependencies can be shown with --all composer fund给出你所用的包的捐赠链接composer depends / whycomposer depends 命令用于显示一个包的依赖关系树。如果你要删除一个包之前,可以用这个命令先看看它上面是不是有人> composer depends psr/log -t psr/log 1.1.4 Common interface for logging libraries ├──composer/composer 2.4.x-dev (requires psr/log ^1.0 || ^2.0 || ^3.0) ├──composer/composer dev-main (requires psr/log ^1.0 || ^2.0 || ^3.0) ├──composer/xdebug-handler 3.0.3 (requires psr/log ^1 || ^2 || ^3) │ ├──composer/composer 2.4.x-dev (requires composer/xdebug-handler ^2.0.2 || ^3.0.3) │ └──composer/composer dev-main (requires composer/xdebug-handler ^2.0.2 || ^3.0.3) └──symfony/console v5.4.11 (conflicts psr/log >=3) (circular dependency aborted here)composer prohibits / why-not告诉您哪些包在阻止你想安装的包,并给出理由composer validate如果你手动修改 composer.json, 当准备提交 composer.json 文件之前,最好执行这条命令检查一下composer status如果你手动修改过一个包,这个包安装来源是 source, 则可以用这个命令看你本地修改记录,相当于 git statuscomposer self-update / selfupdatecomposer 程序自更新# 更新到指定版本 composer self-update 2.4.0-RC1composer config修改当前项目或者全局的配置-g : 修改全局配置文件--unset: 移除配置-l: 展示全部配置信息,如果加上-g,就显示全局的--absolute: *-dir的配置返回绝对路径--append: 追加一个镜像时,设置比较低的优先级--source: 展示config从哪里加载的# 添加一个测试foo到repositories composer config repo.foo vcs https://github.com/foo/bar # 添加一个阿里云镜像到repositories(阿里云镜像凉了) composer config repo.packagist composer https://mirrors.aliyun.com/composer/ #效果如下 "repositories": { "packagist": { "type": "composer", "url": "https://mirrors.aliyun.com/composer/" }, "foo": { "type": "vcs", "url": "https://github.com/foo/bar" } } # 修改extra配置 composer config extra.foo.bar value composer config --json extra.foo.bar '{"baz": true, "qux": []}'composer create-project创建项目 / 包,相当于 git clone + composer installcomposer create-project doctrine/orm path "2.2.*"composer dump-autoload当修改了包里面的类名,或者增加删除文件之后,需要执行这个命令# -o 选项是为了生产环境中的性能优化, composer dump-autoload -o # -a 选项则是为了开发环境中的重新生成类映射。 composer dump-autoload -acomposer clear-cache / clearcache / cc清理本地包缓存archive从远程下载一个包,并打包成 zip/tar 压缩包php composer.phar archive vendor/package 2.0.21 --format=ziprun-script / run你可以运行此命令来手动执行,只需要指定脚本的名称,可选的 --no-dev 参数允许你禁用开发者模式。{ "scripts": { "post-update-cmd": "MyVendor\\MyClass::postUpdate", "post-package-install": [ "MyVendor\\MyClass::postPackageInstall" ], "post-install-cmd": [ "MyVendor\\MyClass::warmCache", "phpunit -c app/" ] } } # 将会运行所有 post-install-cmd 事件下定义的脚本。 `composer run-script post-install-cmd` diagnose可以用来检查当前 Composer 环境是否符合最佳实践,包括 PHP 环境、Composer 配置等。audit检查当前项目的依赖项是否存在已知的安全漏洞。help使用 help 可以获取指定命令的帮助信息。php composer.phar help install
2023年08月12日
20 阅读
0 评论
0 点赞
2023-08-12
laravel 如何覆盖composer的 vendor类文件?
laravel 如何覆盖composer的 vendor类文件?作为 Laravel 开发人员,你可能遇到过这样的情况:你不喜欢 vendor 文件夹某个包中的某些代码,并且想要更改它。你可以直接在该文件中更改它。但问题是当你点击composer update命令升级包时很容易覆盖已修改的vendor类文件。那么有什么解决办法呢?感谢Composer,它具有覆盖任何包/类的功能。Composer 使用PSR-4来加载类。因此,在 composer.json 中,你可以提及从哪些文件或文件夹加载类。同样你也可以排除加载它。操作步骤:windows用户排除文件请参考下面的示例以了解我如何从包tymon/jwt-auth 中排除 1个文件"exclude-from-classmap": ["vendor\tymon\jwt-auth\src\Middleware\BaseMiddleware.php"],在上面的例子中可以看出我已经排除了BaseMiddleware.php文件。你必须将此行放在composer.json的autoload段中。这里要注意的一件事是,我在路径中有双反斜杠,那是因为在 Windows 机器上,并且因为它是 JSON 文件,所以我必须在那里转义 () ,即写上双反斜杠。包含文件以覆盖vendor现在我们已经排除了我们想要覆盖的文件,现在需要包含有更改的新文件,以便 composer 知道要包含哪些文件。为此,向 composer.json 中的 'psr-4' 键添加另一个键值对,如下所示:"autoload": { "psr-4": { "App\\": "app/", "Tymon\\": "app/Overrides/" }, "exclude-from-classmap": [ "vendor\\tymon\\jwt-auth\\src\\Middleware\\BaseMiddleware.php" ] },添加了上面代码中的"Tymon\": "app/overrides",以指示comopser将文件包含在 app/overrides 文件夹中。现在创建覆盖文件夹。从要覆盖的包中复制粘贴所有文件。根据您的需要更改文件。在 composer.json 中添加以上行运行命令“composer dump-autoload”(不带引号)上面的命令将刷新所有自动加载文件以包含您的新文件。请记住,无论何时更改 composer.json 文件中的任何内容,您都需要触发上述5的命令以反映更改。对于Unix/Linux用户Linux 用户的说明是相同的,只是代码会改变,我在下面提到。按照与上述 Windows 用户相同的说明进行操作。排除文件如果您在 Linux/Unix 机器上,路径将如下所示:"exclude-from-classmap": ["vendor/tymon/jwt-auth/src/Middleware/BaseMiddleware.php"],包含文件"autoload": { "psr-4": { "App\\": "app/", "Tymon\\": "app/Overrides/" }, "exclude-from-classmap": [ "vendor/tymon/jwt-auth/src/Middleware/BaseMiddleware.php" ] }, Overrides 只是一个名字,你可以选择任何你喜欢的。重要的是提供它的 composer.json 文件的相对路径。谢谢以上在composer2.0下测试通过,参考 https://shyammakwana.me/php/laravel-override-vendor-classes.html
2023年08月12日
26 阅读
0 评论
0 点赞
2023-08-11
深入解析 composer 的自动加载原理
深入解析 composer 的自动加载原理前言PHP 自5.3的版本之后,已经重焕新生,命名空间、性状(trait)、闭包、接口、PSR 规范、以及 composer 的出现已经让 PHP 变成了一门现代化的脚本语言。PHP 的生态系统也一直在演进,而 composer 的出现更是彻底的改变了以往构建 PHP 应用的方式,我们可以根据 PHP 的应用需求混合搭配最合适的 PHP 组件。当然这也得益于 PSR 规范的提出。大纲PHP 自动加载功能PSR 规范comoposer 的自动加载过程composer 源码分析一、PHP 自动加载功能PHP 自动加载功能的由来在 PHP 开发过程中,如果希望从外部引入一个 Class ,通常会使用 include 和 require 方法,去把定义这个 Class 的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会带来一些隐含的问题:如果一个 PHP 文件需要使用很多其它类,那么就需要很多的 require/include 语句,这样有可能会 造成遗漏 或者 包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦, 况且 require或 incloud 的性能代价很大。PHP5 为这个问题提供了一个解决方案,这就是 类的自动加载(autoload)机制。autoload机制 可以使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为 Lazy loading (惰性加载)。总结起来,自动加载功能带来了几处优点:使用类之前无需 include / require使用类的时候才会 include / require 文件,实现了 lazy loading ,避免了 include / require 多余文件。无需考虑引入 类的实际磁盘地址 ,实现了逻辑和实体文件的分离。PHP 自动加载函数 __autoload()从 PHP5 开始,当我们在使用一个类时,如果发现这个类没有加载,就会自动运行 __autoload() 函数,这个函数是我们在程序中自定义的,在这个函数中我们可以加载需要使用的类。下面是个简单的示例:<?phpfunction __autoload($classname) { require_once ($classname . ".class.php");}在我们这个简单的例子中,我们直接将类名加上扩展名 .class.php 构成了类文件名,然后使用 require_once 将其加载。从这个例子中,我们可以看出 __autoload 至少要做三件事情:根据类名确定类文件名;确定类文件所在的磁盘路径;将类从磁盘文件中加载到系统中。第三步最简单,只需要使用 include / require 即可。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在 __autoload() 函数中,将类名与实际的磁盘文件对应起来,就可以实现 lazy loading 的效果 。如果想详细的了解关于 autoload 自动加载的过程,可以查看手册资料:PHP autoload函数说明__autoload() 函数存在的问题如果在一个系统的实现中,如果需要使用很多其它的类库,这些类库可能是由不同的开发人员编写的, 其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则全部实现,这样的话 __autoload() 函数有可能会非常复杂,甚至无法实现。最后可能会导致 __autoload()函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。那么问题出现在哪里呢?问题出现在 __autoload() 是全局函数只能定义一次 ,不够灵活,所以所有的类名与文件名对应的逻辑规则都要在一个函数里面实现,造成这个函数的臃肿。那么如何来解决这个问题呢?答案就是使用一个 __autoload调用堆栈 ,不同的映射关系写到不同的 __autoload函数 中去,然后统一注册统一管理,这个就是 PHP5 引入的 SPL Autoload 。SPL AutoloadSPL是 Standard PHP Library(标准PHP库)的缩写。它是 PHP5 引入的一个扩展标准库,包括 spl autoload 相关的函数以及各种数据结构和迭代器的接口或类。spl autoload 相关的函数具体可见 php中spl_autoload<?php// __autoload 函数//// function __autoload($class) {// include 'classes/' . $class . '.class.php';// }function my_autoloader($class) {include 'classes/' . $class . '.class.php';}spl_autoload_register('my_autoloader');// 定义的 autoload 函数在 class 里// 静态方法class MyClass { public static function autoload($className) {// ...}}spl_autoload_register(array('MyClass', 'autoload'));// 非静态方法class MyClass { public function autoload($className) {// ...}}$instance = new MyClass();spl_autoload_register(array($instance, 'autoload'));spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。二、PSR 规范与自动加载相关的规范是 PSR4,在说 PSR4 之前先介绍一下 PSR 标准。PSR 标准的发明和推出组织是:PHP-FIG,它的网站是:www.php-fig.org。由几位开源框架的开发者成立于 2009 年,从那开始也选取了很多其他成员进来,虽然不是 “官方” 组织,但也代表了社区中不小的一块。组织的目的在于:以最低程度的限制,来统一各个项目的编码规范,避免各家自行发展的风格阻碍了程序员开发的困扰,于是大伙发明和总结了 PSR,PSR 是 PHP Standards Recommendation 的缩写,截止到目前为止,总共有 14 套 PSR 规范,其中有 7 套PSR规范已通过表决并推出使用,分别是:PSR-0 自动加载标准(已废弃,一些旧的第三方库还有在使用)PSR-1 基础编码标准PSR-2 编码风格向导PSR-3 日志接口PSR-4 自动加载的增强版,替换掉了 PSR-0PSR-6 缓存接口规范PSR-7 HTTP 消息接口规范具体详细的规范标准可以查看PHP 标准规范PSR4 标准2013 年底,PHP-FIG 推出了第 5 个规范——PSR-4。PSR-4 规范了如何指定文件路径从而自动加载类定义,同时规范了自动加载文件的位置。1)一个完整的类名需具有以下结构:\<命名空间>\<子命名空间>\<类名>完整的类名必须要有一个顶级命名空间,被称为 "vendor namespace";完整的类名可以有一个或多个子命名空间;完整的类名必须有一个最终的类名;完整的类名中任意一部分中的下滑线都是没有特殊含义的;完整的类名可以由任意大小写字母组成;所有类名都必须是大小写敏感的。2)根据完整的类名载入相应的文件完整的类名中,去掉最前面的命名空间分隔符,前面连续的一个或多个命名空间和子命名空间,作为「命名空间前缀」,其必须与至少一个「文件基目录」相对应;紧接命名空间前缀后的子命名空间 必须 与相应的「文件基目录」相匹配,其中的命名空间分隔符将作为目录分隔符。末尾的类名必须与对应的以 .php 为后缀的文件同名。自动加载器(autoloader)的实现一定不可抛出异常、一定不可触发任一级别的错误信息以及不应该有返回值。3) 例子PSR-4风格类名:ZendAbc命名空间前缀:Zend文件基目录:/usr/includes/Zend/文件路径:/usr/includes/Zend/Abc.php类名:SymfonyCoreRequest命名空间前缀:SymfonyCore文件基目录:./vendor/Symfony/Core/文件路径:./vendor/Symfony/Core/Request.php目录结构-vendor/| -vendor_name/| | -package_name/| | | -src/| | | | -ClassName.php # Vendor_Name\Package_Name\ClassName| | | -tests/| | | | -ClassNameTest.php # Vendor_Name\Package_Name\ClassNameTestComposer自动加载过程Composer 做了哪些事情你有一个项目依赖于若干个库。其中一些库依赖于其他库。你声明你所依赖的东西。Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)。例如,你正在创建一个项目,需要做一些单元测试。你决定使用 phpunit 。为了将它添加到你的项目中,你所需要做的就是在 composer.json 文件里描述项目的依赖关系。{ "require": { "phpunit/phpunit":"~6.0",} }然后在 composer require 之后我们只要在项目里面直接 use phpunit 的类即可使用。执行 composer require 时发生了什么composer 会找到符合 PR4 规范的第三方库的源将其加载到 vendor 目录下初始化顶级域名的映射并写入到指定的文件里(如:'PHPUnit\Framework\Assert' => DIR . '/..' . '/phpunit/phpunit/src/Framework/Assert.php')写好一个 autoload 函数,并且注册到 spl_autoload_register()里题外话:现在很多框架都已经帮我们写好了顶级域名映射了,我们只需要在框架里面新建文件,在新建的文件中写好命名空间,就可以在任何地方 use 我们的命名空间了。Composer 源码分析下面我们通过对源码的分析来看看 composer 是如何实现 PSR4标准 的自动加载功能。很多框架在初始化的时候都会引入 composer 来协助自动加载的,以 Laravel 为例,它入口文件 index.php 第一句就是利用 composer 来实现自动加载功能。启动<?php define('LARAVEL_START', microtime(true));require DIR . '/../vendor/autoload.php';去 vendor 目录下的 autoload.php :<?php require_once DIR . '/composer' . '/autoload_real.php';return ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29::getLoader();这里就是 Composer 真正开始的地方了Composer自动加载文件首先,我们先大致了解一下Composer自动加载所用到的源文件。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 标准的自动加载文件,存放着顶级命名空间与文件的映射;autoload_real 引导类在 vendor 目录下的 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() ,我们接着看看这个函数。<?phppublic static function getLoader() { if (null !== self::$loader) { return self::$loader; } spl_autoload_register( array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true ); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister( array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader') ); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func( \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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); } } /***********************注册自动加载核心类对象********************/ $loader->register(true); /***********************自动加载全局函数********************/ if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); } return $loader; }我把自动加载引导类分为 5 个部分。第一部分——单例第一部分很简单,就是个最经典的单例模式,自动加载类只能有一个。<?php if (null !== self::$loader) { return self::$loader;}第二部分——构造ClassLoader核心类第二部分 new 一个自动加载的核心类对象。<?php /获得自动加载核心类对象*/ spl_autoload_register(array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true);self::$loader = $loader = new \Composer\Autoload\ClassLoader();spl_autoload_unregister(array('ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'));loadClassLoader()函数:<?phppublic static function loadClassLoader($class){if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; }}从程序里面我们可以看出,composer 先向 PHP 自动加载机制注册了一个函数,这个函数 require 了 ClassLoader 文件。成功 new 出该文件中核心类 ClassLoader() 后,又销毁了该函数。第三部分 —— 初始化核心类对象<?php /初始化自动加载核心类对象*/ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func( \Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::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 静态初始化 ( PHP >= 5.6 )静态初始化只支持 PHP5.6 以上版本并且不支持 HHVM 虚拟机。我们深入 autoload_static.php 这个文件发现这个文件定义了一个用于静态初始化的类,名字叫 ComposerStaticInit7b790917ce8899df9af8ed53631a1c29,仍然为了避免冲突而加了 hash 值。这个类很简单:<?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() 函数,它将自己类中的顶级命名空间映射给了 ClassLoader 类。值得注意的是这个函数返回的是一个匿名函数,为什么呢?原因就是 ClassLoader类 中的 prefixLengthsPsr4 、prefixDirsPsr4等等变量都是 private的。利用匿名函数的绑定功能就可以将这些 private 变量赋给 ClassLoader 类 里的成员变量。关于匿名函数的绑定功能。接下来就是命名空间初始化的关键了。classMap(命名空间映射)<?php public static $classMap = array ( 'App\\Console\\Kernel' => __DIR__ . '/../..' . '/app/Console/Kernel.php', 'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php', 'App\\Http\\Controllers\\Auth\\ForgotPasswordController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/ForgotPasswordController.php', 'App\\Http\\Controllers\\Auth\\LoginController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/LoginController.php', 'App\\Http\\Controllers\\Auth\\RegisterController' => __DIR__ . '/../..' . '/app/Http/Controllers/Auth/RegisterController.php',...)直接命名空间全名与目录的映射,简单粗暴,也导致这个数组相当的大。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', ),...)PSR4 标准顶级命名空间映射用了两个数组,第一个是用命名空间第一个字母作为前缀索引,然后是 顶级命名空间,但是最终并不是文件路径,而是 顶级命名空间的长度。为什么呢?因为 PSR4 标准是用顶级命名空间目录替换顶级命名空间,所以获得顶级命名空间的长度很重要。具体说明这些数组的作用:假如我们找 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,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败。ClassLoader 接口初始化( PHP < 5.6 )如果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); }PSR4 标准的映射autoload_psr4.php 的顶级命名空间映射<?phpreturn 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'), ... )PSR4 标准的初始化接口:<?phppublic 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_static 的 $prefixLengthsPsr4 、 $prefixDirsPsr4 。命名空间映射autoload_classmap:<?phppublic static $classMap = array ('App\\Console\\Kernel' => __DIR__ . '/../..' . '/app/Console/Kernel.php', 'App\\Exceptions\\Handler' => __DIR__ . '/../..' . '/app/Exceptions/Handler.php', ...)addClassMap:<?phppublic function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } }自动加载核心类 ClassLoader 的静态初始化到这里就完成了!其实说是5部分,真正重要的就两部分——初始化与注册。初始化负责顶层命名空间的目录映射,注册负责实现顶层以下的命名空间映射规则。第四部分 —— 注册讲完了 Composer 自动加载功能的启动与初始化,经过启动与初始化,自动加载核心类对象已经获得了顶级命名空间与相应目录的映射,也就是说,如果有命名空间 'App\Console\Kernel,我们已经可以找到它对应的类文件所在位置。那么,它是什么时候被触发去找的呢?这就是 composer 自动加载的核心了,我们先回顾一下自动加载引导类:public static function getLoader() {/***************************经典单例模式********************/ if (null !== self::$loader) { return self::$loader; } /***********************获得自动加载核心类对象********************/ spl_autoload_register(array('ComposerAutoloaderInit 7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); spl_autoload_unregister(array('ComposerAutoloaderInit 7b790917ce8899df9af8ed53631a1c29', 'loadClassLoader')); /***********************初始化自动加载核心类对象********************/ $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit 7b790917ce8899df9af8ed53631a1c29::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); } } /***********************注册自动加载核心类对象********************/ $loader->register(true); /***********************自动加载全局函数********************/ if ($useStaticLoader) { $includeFiles = Composer\Autoload\ComposerStaticInit 7b790917ce8899df9af8ed53631a1c29::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { composerRequire 7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); } return $loader;} 现在我们开始引导类的第四部分:注册自动加载核心类对象。我们来看看核心类的 register() 函数:public function register($prepend = false){spl_autoload_register(array($this, 'loadClass'), true, $prepend);}其实奥秘都在自动加载核心类 ClassLoader 的 loadClass() 函数上:public function loadClass($class){ if ($file = $this->findFile($class)) { includeFile($file); return true; } }这个函数负责按照 PSR 标准将顶层命名空间以下的内容转为对应的目录,也就是上面所说的将 'App\Console\Kernel 中' Console\Kernel 这一段转为目录,至于怎么转的在下面 “运行”的部分讲。核心类 ClassLoader 将 loadClass() 函数注册到PHP SPL中的 spl_autoload_register() 里面去。这样,每当PHP遇到一个不认识的命名空间的时候,PHP会自动调用注册到 spl_autoload_register 里面的 loadClass() 函数,然后找到命名空间对应的文件。全局函数的自动加载Composer 不止可以自动加载命名空间,还可以加载全局函数。怎么实现的呢?把全局函数写到特定的文件里面去,在程序运行前挨个 require就行了。这个就是 composer 自动加载的第五步,加载全局函数。if ($useStaticLoader) {$includeFiles = Composer\Autoload\ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files;} else {$includeFiles = require __DIR__ . '/autoload_files.php';}foreach ($includeFiles as $fileIdentifier => $file) {composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file);}跟核心类的初始化一样,全局函数自动加载也分为两种:静态初始化和普通初始化,静态加载只支持PHP5.6以上并且不支持HHVM。静态初始化:ComposerStaticInit7b790917ce8899df9af8ed53631a1c29::$files:public static $files = array ('0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => DIR . '/..' . '/symfony/polyfill-mbstring/bootstrap.php','667aeda72477189d0494fecd327c3641' => DIR . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',...);普通初始化autoload_files:$vendorDir = dirname(dirname(__FILE__));$baseDir = dirname($vendorDir);return array('0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php','667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', ....);其实跟静态初始化区别不大。加载全局函数class ComposerAutoloaderInit7b790917ce8899df9af8ed53631a1c29{ public static function getLoader(){ ... foreach ($includeFiles as $fileIdentifier => $file) { composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file); } ...}}function composerRequire7b790917ce8899df9af8ed53631a1c29($fileIdentifier, $file) {if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; }}第五部分 —— 运行到这里,终于来到了核心的核心—— composer 自动加载的真相,命名空间如何通过 composer 转为对应目录文件的奥秘就在这一章。前面说过,ClassLoader 的 register() 函数将 loadClass() 函数注册到 PHP 的 SPL 函数堆栈中,每当 PHP 遇到不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。所以 loadClass() 函数就是自动加载的关键了。看下 loadClass() 函数:public function loadClass($class){if ($file = $this->findFile($class)) { includeFile($file); return true; }}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;}我们看到 loadClass() ,主要调用 findFile() 函数。findFile() 在解析命名空间的时候主要分为两部分:classMap 和 findFileWithExtension() 函数。classMap 很简单,直接看命名空间是否在映射数组中即可。麻烦的是 findFileWithExtension() 函数,这个函数包含了 PSR0 和 PSR4 标准的实现。还有个值得我们注意的是查找路径成功后 includeFile() 仍然是外面的函数,并不是 ClassLoader 的成员函数,原理跟上面一样,防止有用户写 $this 或 self。还有就是如果命名空间是以\开头的,要去掉\然后再匹配。看下 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; }}最后小结我们通过举例来说下上面代码的流程:如果我们在代码中写下 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 数组里面的目录继续判断是否存在文件以上就是 composer 自动加载的原理解析!
2023年08月11日
14 阅读
0 评论
0 点赞
2023-08-10
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)
你必须知道的 17 个 Composer 最佳实践(已更新至 22 个)尽管大多数 PHP 开发人员都知道如何使用 Composer,但并不是所有的人都在有效的或以最好的方式来使用它。 所以我决定总结一些在我日常工作流程很重要的东西。大多数技巧的哲学是 “稳,不冒险”,这意味着如果有更多的方法来处理某些事情,我会使用最有把握不容易出错的方法。Tip #1: 阅读文档我是真心这样认为的. 文档 是一个非常有用的东西并且在长远看来几个小时的阅读将会为您节省更多的时间。你会惊讶原来 Composer 可以做这么多事情.Tip #2: 注意 项目 和 库 之间的区别无论你是创建 项目 还是 库 , 了解这一点非常重要,它们每一个都需要单独的一套做法。一个库是一个可重用的包,你可以添加一个依赖比如: symfony / symfony,doctrine / orm 或者 elasticsearch/elasticsearch.一个项目通常是一个应用程序,依赖于几个库。它通常是不可重用的(没有其它项目会要求它作为依赖。典型的例子是电子商务网站,客户支持系统等)。我将在下面的提示中区分库和项目。Tip #3: 使用特定的依赖关系 #关于应用程序的版本如果你正在创建一个应用程序,您应该使用最具体的版本来定义依赖项,假如你需要解析 YAML 文件,你应该指定这样的依赖版本 “symfony / YAML”:“4.0.2”。即使你遵循了库的依赖版本控制,在次要版本和补丁版本中也有可能中断向后兼容性。例如,如果你使用的是 “symfony / Symfony”:“^ 3.1”, 会有 3.2 的版本有可能会破坏你的应用程序测试用列。或者可能新版本有 bug 没有修正,那么 php_codesniffer 检测你的代码格式的时候会导致新问题,这在很大程度上可能会破坏一个应用的构建。依赖关系的更新应该是深思熟虑的,而不是偶然的。其中的一个技巧更详细地讨论了它。这听起来像一个多余的,但它可以防止你的同事不小心在项目中添加一个新的库文件或更新所有依赖的时候出错,(不然的话,你们有可能会导致浪费大量的时间在审核代码上)。Tip #4: 对库依赖项使用版本范围创建库时,应尽可能定义最大的可用版本范围。比如创建了一个库,要使用 symfony/yaml 库进行 YAML 解析,就应这样写:"symfony/yaml": "^3.0 || ^4.0"这表示该库能从 Symfony 3.x 或 4.x 中任意版本中使用 symfony/yaml 。这相当重要,因为这个版本约束会传递给使用该库的应用程序。万一有两个库的请求存在冲突,比如一个要 ~3.1.0 ,另一个需要 ~3.2.0 ,则安装会失败。Tip #5: 开发应用程序要提交 composer.lock 文件到 git 版本库中创建了 一个项目,一定要把 composer.lock 文件提交到 git 中。 这会确保每一个人 —— 你、你的合作伙伴、你的 CI 服务器以及你的产品服务器 —— 所运行的应用程序拥有相同依赖的版本。乍一看有些画蛇添足,在 Tip #3 中已经提过要使用明确的版本号的约束了啊。这并不多余,要知道你使用的依赖项的依赖项并不受这些约束绑定(如 symfony/console 还依赖 symfony/polyfill-mbstring)。如果不提交 composer.lock 文件,就不会获取到相同版本的依赖集合。Tip #6: 开发库要把 composer.lock 文件添加到 .gitignore 文件中创建 一个库 (比如说叫 acme/my-library), 这就不应该把 composer.lock 文件提交到 git 库中了。该文件对使用该库的项目 It 不会有任何影响 。假设 acme/my-library 使用 monolog/monolog 作依赖项。你已经在版本库中提交了 composer.lock,开发 acme/my-library 的每个人都可能在使用 Monolog 的老旧版本。该库开发完成后,在实际项目中使用该库,就可能存在安装的 Monolog 是一个新版本 , 而此时就会和该库存在不兼容。可是你在之前根本就不会注意到兼容问题就因为这个 composer.lock!因此,最佳处理方式就是把 composer.lock 添加到 .gitignore 文件中,这样就避免了不小心提交它到版本库中引发的问题。如果还想确保该库与它的依赖项的不同版本保持兼容性,那继续阅读下一个 Tip !Tip #7: Travis CI 构建依赖项的不同版本当前 Tip 仅适合库(对于应用程序要指明具体的版本号)。如果你在构建开源的库,很有可能你会使用 Travis CI 来跑构建过程。默认情况下,在 composer.json 文件约束允许的条件下,composer 安装会安装依赖的最新可能版本。这就意味着对于 ^3.0 || ^4.0 这样的依赖约束,构建安装总是使用最新的 v4 版本发行包。 而 3.0 版本根本不会测试,所构建的库就可能与该版本不兼容,你的用户要哭了。幸好,composer 为安装低版本依赖项提供了一个开关 --prefer-lowest (应使用 --prefer-stable ,可阻止不稳定版本的安装)。已上传的 .travis.yml 配置类似下面的格式:language: phpphp:7.17.2env: matrix:- PREFER_LOWEST="--prefer-lowest --prefer-stable" - PREFER_LOWEST="" before_script:composer update $PREFER_LOWESTscript:composer ci代码详见 my mhujer/fio-api-php library 及 the build matrix on Travis CI虽然这解决了多数的不兼容问题,不过仍然要记得,依赖项的最低和最高版本间有太多的组合。他们仍旧可能存在不兼容的情况。Tip #8: 按名称对 require 和 require-dev 中的包排序按名称对 require 及 require-dev 中的包排序是非常好的实践。这在衍合一个分支时可以避免不必要的合并冲突。假如你把一个包添加到两个分支文件中的列表末尾,那每次合并都可能遇到冲突。手动进行包排序的话会很乏味,所以最好办法就是在 composer.json 中 配置一下 即可:{..."config": { "sort-packages": true },...}以后再要 require 一个新的包,它会自动添加到一个正确位置(不会跑到尾部)。Tip #9: 进行版本衍合或合并时不要合并 composer.lock 如果你在 composer.json (和 composer.lock)中添加了一个新依赖项,并且在该分支被合并前主分支中添加另一个依赖项,此时就需要对你的分支进行衍合处理。那么 composer.lock 文件就会得到一个合并冲突。千万别试图手动解决冲突,这是因为 composer.lock 文件包含了定义 composer.json 中依赖项的哈希值。所以即使你解决了冲突,这个最终合并结果的 lock 文件仍是错误的。最佳方案应该这样做,用下面一行代码在项目根目录创建一个 .gitattributes 文件,它会告诉 git 不要试图对 composer.lock 文件进行合并操作:/composer.lock -merge推荐 Trunk Based Development 方式(常用佳品,不会有错),使用临时的特性分支纠正这种问题。当你有个临时分支需要即时合并时,因此导致的 composer.lock 文件合并冲突的风险极小。你甚至可以仅仅为添加一个依赖项而创建分支,然后马上进行合并。假如在衍合过程中 composer.lock 遇到合并冲突又当如何呢? 使用主分支版本解决,这样仅仅修改 composer.json 文件即可(新增一个包)。然后运行 composer update --lock ,就会把 composer.json 文件的修改更新到 composer.lock 文件中。现在把已经更新的 composer.lock 文件提交到版本暂存区,然后继续衍合操作。Tip #10: 了解 require 和 require-dev 之间的区别能够意识到 require 和 require-dev 模块之间的区别是非常重要的。需要运行在应用中或者库中的包都应该被定义在 require (例如: Symfony, Doctrine, Twig, Guzzle, ...) 中。如果你正在创建一个库, 注意将什么内容定义为 require。因为这个部分的 每个依赖项同时也是使用了该库的应用的依赖。 开发应用程序 (或库) 所需的包应该定义在 require-dev (例如:PHPUnit, PHP_CodeSniffer, PHPStan) 中。Tip #11: 安全地升级依赖项我想大家对如下事实存有共识:应该定期对依赖项升级。 此处我想讨论的是依赖项的升级应该放在明处且慎之又慎,而不能是因其他活计的需要才顺手为之。如果在重构应用的同时又升级了库,那么就很难区分应用崩溃的原因是重构还是升级带来的。可用 composer outdated 命令查看哪些依赖项需要升级。追加一个 --direct (或 -D)参数开关是个聪明之举,这只会查看 composer.json 指定的依赖项。还有一个 -m 参数开关,只查看次版本号的升级列表。对每一个老版本的依赖项进行升级都要尊循如下步骤:创建新分支在 composer.json 文件中更新该依赖项版本到最新版本号运行 composer update phpunit/phpunit --with-dependencies (使用升级过的库替换 phpunit/phpunit)检查 Github 上库的版本库中 CHANGELOG 文件,检查是否存在重大变化。 如果存在就升级应用程序本地测试应用程序(使用 Symfony 的话还能在调试栏看到弃用警告)提交修改(包括 composer.json 、 composer.lock 及其他新版本正常运行所做的必要修改)等 CI 构建结束合并然后部署有时需要一次升级多个依赖项,比如升级 Doctrine 或 Symfony。这种情况下,就要在升级命令中把他们全部罗列出来:composer update symfony/symfony symfony/monolog-bundle --with-dependencies或者使用通配符升级所有指定命名空间的依赖:composer update symfony/* --with-dependencies这全都是很乏味的工作,但相对于不小心升级依赖项而言,这提供了额外保障。一个可接受的简捷方式就是一次升级所有 require-dev 中的依赖项(如果程序代码没有修改的话,否则还是建议创建独立分支以便代码审查)。Tip #12: 在 composer.json 中定义其他类型的依赖除了定义库作为依赖项外,也以在这儿定义其他东西。可以定义应用程序和库所支持的 PHP 版本:"require": {"php": "7.1.* || 7.2.*",},也能定义应用程序和库所需要的扩展。在尝试 docker 化自己的应用时,或是你的同伴头一次设置应用环境时,这招超级实用。"require": {"ext-mbstring": "*", "ext-pdo_mysql": "*",},(当 扩展版本不一致 时,版本号要用 * )。Tip #13: 在 CI 构建期间验证 composer.jsoncomposer.json 和 composer.lock 应当一直保持同步。因此,一直为他们保持自动核对是一个好主意。将此添加成为你的构建脚本的一部分将会确保 composer.lock 与 composer.json 保持同步:composer validate --no-check-all --strictTip #14: 在 PHPStorm 中使用 Composer 插件这里有一个 composer.json plugin for PHPStorm. 当手动修改 composer.json 时,插件会自动完成及执行一些验证.如果你在使用其他 IDE (或者只是一个编辑器), 你可以使用 its JSON schema 设置验证.Tip #15: 在 composer.json 中指明生产环境的 PHP 版本号如果你和我一样,有时还 在本地环境跑 PHP 最新预释版本, 那么就会处于升级依赖项的版本不能运行于生产环境的风险。现在我就在使用 PHP 7.2.0 ,也就意味着我安装的库可能在 7.1 版本中运行不了。如果生产环境跑的是 7.1 版本,安装就会失败。不过不用担心,有个非常简单的解决办法,在 composer.json 文件的 config 部分指明生产环境的 PHP 版本号即可:"config": {"platform": { "php": "7.1" }}别把它和 require 部分的设置搞混了,它的作用不同。你的应用就可以运行 7.1 或 7.2 版本下,而且同时指定了平台版本为 7.1 (这意味着依赖项的升级版本要和 平台版本 7.1 保持兼容):"require": {"php": "7.1.* || 7.2.*"},"config": {"platform": { "php": "7.1" }},Tip #16: 使用自有托管 Gitlab 上的私有包推荐使用 vcs 作为版本库类型,并且 Composer 决定获取包的合适的方法。比如,从 Github 上添加一个 fork,使用它的 API 下载整个版本库的 .zip 文件,而不用克隆。不过对一个私有的 Gitlab 安装来讲会更复杂。如果用 vcs 作版本库类型,Composer 会检测到它是个 Gitlab 类型的安装,会尝试使用 API 下载包(这要求有 API key。我不想设置,所以我只用 SSH 克隆安装了) :首先指明版本库类型是 git:"repositories": [{ "type": "git", "url": "git@gitlab.mycompany.cz:package-namespace/package-name.git" }]然后指明常用的包:"require": {"package-namespace/package-name": "1.0.0"}Tip #17: 临时使用 fork 下 bug 修复分支的方法如果在某个公共的库中找到一个 bug,并且在 Github 上自己的 fork 中修复了它, 这就需要从自己的版本库里安装这个库,而不是官方版本库(要到修复合并且修复的版本释出才行)。使用 内嵌别名 可轻松搞定:{"repositories": [ { "type": "vcs", "url": "https://github.com/you/monolog" } ], "require": { "symfony/monolog-bundle": "2.0", "monolog/monolog": "dev-bugfix as 1.0.x-dev" }}可以通过 设置 path 作为版本库类型 在本地测试这次修复,然后再 push 更新版本库。更新于 2018-01-08:文章发布后,我收到了一些建议,提供了更多的使用技巧。它们分别是:Tip #18:使用 prestissimo 加速你的包安装Composer 有个 hirak/prestissimo 插件,通过该插件能够以并行的方式进行下载,从而提高依赖包的安装速度。那么,这么好的东西,你现在该如何做?你仅仅需要马上全局安装这个插件,然后就可以自动地在所有项目中使用。composer global require hirak/prestissimoTip #19: 当你不确定时,测试你的版本约束即使在阅读 the documentation 之后,书写正确的版本约束在一些时候也是很棘手的.幸运的是,这里有 Packagist Semver Checker 可以用来检查哪个本部匹配特定的约束。他不是仅仅的分析版本约束,他从 Packagist 下载数据以来展示实际的发布版本.查看 the result for symfony/symfony:^3.1.Tip #20: 在生产环境中使用使用权威类映射文件应该在生产环境中 生成权威类映射文件 。这会让类映射文件中包含的所有类快速加载,而不必到磁盘文件系统进行任何检查。可以在生产环境构建时运行以下命令:composer dump-autoload --classmap-authoritativeTip #21: 为测试配置 autoload-dev你也不想在生产环境中加载测试文件(考虑到测试文件的大小和内存使用)。这可以通过配置 autoload-dev 解决(与 autoload 相似):"autoload": {"psr-4": { "Acme\\": "src/" }},"autoload-dev": {"psr-4": { "Acme\\": "tests/" }},Tip #22: 尝试 Composer 脚本Composer 脚本是一个创建构建脚本的轻量级工具。关于这个,我有另文述及。总结如果你不同意某些观点且阐述出你为什么不同意的意见(不要忘记标注 tip 的编号)我将很高兴。
2023年08月10日
13 阅读
0 评论
0 点赞
1
2
3