composer详解

news/2024/5/19 17:04:49/文章来源:https://blog.csdn.net/zhoupenghui168/article/details/129745291

一.composer简介

  1. 什么是Composer

Composer 是 PHP 的一个依赖管理工具,它涉及 "packages" 和 "libraries",简单的说就是我们的项目通常会使用其它代码工具库,这时仅仅是在项目中申明依赖哪些代码工具库,它在每个项目的基础上进行管理,会在你的项目的某个目录中(例如 vendor)进行安装,默认情况下它不会在全局安装任何东西
  1. Composer的意义

对于现代语言而言,依赖管理工具基本上是标配。Java 有 Maven,Python 有 pip,Ruby 有 gem,Nodejs 有 npm,PHP 的则是 PEAR,不过 PEAR 坑不少:依赖处理容易出问题,配置非常复杂,难用的命令行接口等等。正是因为Composer的出现,解决了项目依赖的问题,并使PHP开发工作因此变得如同堆积木一般
  1. Composer工作原理

Composer又是如何工作的呢,举个例子当我们去安装一个软件的时候,一般是通过app store 去安装。当我们开发PHP项目的时候,也会面临同样的问题。比如我们需要一个工具记录业务log,那这样我们是不是可以通过一个php的应用商店来下载我们需要的工具
Packagist 是 Composer 的默认的开发包仓库。你可以将自己的安装包提交到 packagist,将来你在自己的 VCS (源码管理软件,比如 Github)仓库中新建了 tag 或更新了代码,packagist 都会自动构建一个新的开发包。这就是 packagist 目前的运作方式,将来 packagist 将允许直接上传开发包,发布自己的包

4.Composer 的优点

(1).你有一个项目依赖于若干个库

(2).其中一些库依赖于其他库

(3).你声明你所依赖的东西

(4).Composer 会找出哪个版本的包需要安装,并安装它们(将它们下载到你的项目中)

5.安装Composer

composer中文网:https://www.phpcomposer.com/
composer官网:https://getcomposer.org/

5.1 windows安装

5.1.1使用安装程序

下载并且运行 Composer-Setup.exe,它将安装最新版本的 Composer ,并设置好系统的环境变量,因此你可以在任何目录下直接使用 composer 命令

5.1.2手动安装

(1).设置系统的环境变量 PATH 并运行安装命令下载 composer.phar 文件
C:\Users\username>cd C:\bin
C:\bin>php -r "readfile('https://getcomposer.org/installer');" | php
注意: 如果收到 readfile 错误提示,请使用 http 链接或者在 php.ini 中开启 php_openssl.dll
(2).在 composer.phar 同级目录下新建文件 composer.bat
C:\bin>echo @php "%~dp0composer.phar" %*>composer.bat
(3).关闭当前的命令行窗口,打开新的命令行窗口进行测试
C:\Users\username>composer -V
Composer version 27d8904

5.1.3 命令行安装

安装前请务必确保已经正确安装了 PHP。打开命令行窗口并执行 php -v 查看是否正确输出版本号

打开命令行并依次执行下列命令安装最新版本的 Composer

#下载安装脚本 - composer-setup.php - 到当前目录
php -r "copy('https://install.phpcomposer.com/installer', 'composer-setup.php');"#执行安装过程
php composer-setup.php#删除安装脚本
php -r "unlink('composer-setup.php');"
执行第一条命令下载下来的 composer-setup.php 脚本将简单地检测 php.ini 中的参数设置,如果某些参数未正确设置则会给出警告;然后下载最新版本的 composer.phar 文件到当前目录
5.1.3.1 局部安装
上述下载 Composer 的过程正确执行完毕后,可以将 composer.phar 文件复制到任意目录(比如项目根目录下),然后通过 php composer.phar 指令即可使用 Composer 了!
5.1.3.2 全局安装
全局安装是将 Composer 安装到系统环境变量 PATH 所包含的路径下面,然后就能够在命令行窗口中直接执行 composer 命令了

(1).找到并进入 PHP 的安装目录(和你在命令行中执行的 php 指令应该是同一套 PHP)

(2).将 composer.phar 复制到 PHP 的安装目录下面,也就是和 php.exe 在同一级目录

(3).在 PHP 安装目录下新建一个 composer.bat 文件,并将下列代码保存到此文件中

@php "%~dp0composer.phar" %*

(4).最后重新打开一个命令行窗口试一试执行 composer --version 看看是否正确输出版本号

5.2 *nix安装

下载 Composer 的可执行文件:composer.phar

5.2.1局部安装

要真正获取 Composer,需要做两件事:首先安装 Composer (同样的,这意味着它将下载到你的项目中):

curl -sS https://getcomposer.org/installer | php
注意: 如果上述方法由于某些原因失败了,你还可以通过 php >下载安装器:
php -r "readfile('https://getcomposer.org/installer');" | php

这将检查一些 PHP 的设置,然后下载 composer.phar 到你的工作目录中,这是 Composer 的二进制文件:一个 PHAR 包(PHP 的归档),这个 PHP 的归档格式可以帮助用户在命令行中执行一些操作.可以通过 --install-dir 选项指定 Composer 的安装目录(它可以是一个绝对或相对路径):

curl -sS https://getcomposer.org/installer | php -- --install-dir=bin

5.2.2全局安装

可以将此文件放在任何地方,如果把它放在系统的 PATH 目录中,就能在全局访问它. 在类Unix系统中,甚至可以在使用时不加 php 前缀,可以执行这些命令让 composer 在系统中进行全局调用:

curl -sS https://getcomposer.org/installer | php
mv composer.phar /usr/local/bin/composer
注意 如果上诉命令因为权限执行失败, 请使用 sudo 再次尝试运行 mv 那行命令
现在只需要运行 composer 命令就可以使用 Composer 而不需要输入 php composer.phar

6.升级 Composer

执行composer selfupdate 以保持 Composer 一直是最新版本

Composer 升级时是无法利用我们的镜像加速下载的,而必须连接到 Composer 国外官网的服务器上下载升级文件,某些时候就会导致升级的速度非常慢甚至失败。

这里提供一个简单的办法:

如果系统中已经有可以正常使用的 Composer 了,说明系统环境是符合要求的,那么只需要下载新的 Composer 文件并覆盖原来的文件即可。

首先要确定现有的 Composer 的安装目录,然后通过下面的链接下载 composer.phar 文件(复制以下地址到浏览器地址栏可直接下载),

https://install.phpcomposer.com/composer.phar

将前面下载的 composer.phar 文件覆盖系统中已经安装的 composer.phar 文件即可。

注意:有可能在安装时将 composer.phar 改名为 composer 了,注意用同样的名字覆盖即可

二.Packagist 中国全量镜像使用方法

有两种方式启用本镜像服务:

  • 系统全局配置: 即将配置信息添加到 Composer 的全局配置文件 config.json

  • 单个项目配置: 将配置信息添加到某个项目的 composer.json 文件中

1.系统全局配置

修改 composer 的全局配置文件(推荐方式)

打开命令行窗口(windows用户)或控制台(Linux、Mac 用户)并执行如下命令:

composer config -g repo.packagist composer https://packagist.phpcomposer.com

2.单个项目配置

修改当前项目的 composer.json 配置文件:

打开命令行窗口(windows用户)或控制台(Linux、Mac 用户),进入你的项目的根目录(也就是 composer.json 文件所在目录),执行如下命令:

composer config repo.packagist composer https://packagist.phpcomposer.com

上述命令将会在当前项目中的 composer.json 文件的末尾自动添加镜像的配置信息(也可以自己手工添加):

"repositories": {"packagist": {"type": "composer","url": "https://packagist.phpcomposer.com"}
}

以yii2 项目的 composer.json 配置文件为例,执行上述命令后如下所示(注意最后几行):

{"name": "yiisoft/yii2-app-advanced","description": "Yii 2 Advanced Project Template","keywords": ["yii2", "framework", "advanced", "project template"],"homepage": "http://www.yiiframework.com/","type": "project","license": "BSD-3-Clause","support": {"issues": "https://github.com/yiisoft/yii2/issues?state=open","forum": "http://www.yiiframework.com/forum/","wiki": "http://www.yiiframework.com/wiki/","irc": "irc://irc.freenode.net/yii","source": "https://github.com/yiisoft/yii2"},"minimum-stability": "stable","require": {"php": ">=5.6.0","yiisoft/yii2": "~2.0.14","yiisoft/yii2-bootstrap4": "~2.0.0","yiisoft/yii2-swiftmailer": "~2.0.0 || ~2.1.0","yiisoft/yii2-redis": "^2.0","dmstr/yii2-adminlte-asset": "2.*","mdmsoft/yii2-admin": "~2.0","yiisoft/yii2-mongodb": "^2.1","yiisoft/yii2-elasticsearch": "^2.1","2amigos/yii2-date-picker-widget": "^1.0","2amigos/yii2-date-time-picker-widget": "^1.0","ext-json": "*"},"require-dev": {"yiisoft/yii2-debug": "~2.1.0","yiisoft/yii2-gii": "~2.2.0",},"config": {"allow-plugins": {"yiisoft/yii2-composer": true}},"repositories": [#{ //如果下面的不得行,就用这个#    "type": "composer",#    "url": "https://asset-packagist.org"#}"packagist": {"type": "composer","url": "https://packagist.phpcomposer.com"}]
}

3.镜像原理

一般情况下,安装包的数据(主要是 zip 文件)一般是从 github.com 上下载的,安装包的元数据是从 packagist.org 上下载的,然而,由于众所周知的原因,国外的网站连接速度很慢,并且随时可能被“墙”甚至“不存在”,“Packagist 中国全量镜像”所做的就是缓存所有安装包和元数据到国内的机房并通过国内的 CDN 进行加速,这样就不必再去向国外的网站发起请求,从而达到加速 composer install 以及 composer update 的过程,并且更加快速、稳定。因此,即使 packagist.orggithub.com 发生故障(主要是连接速度太慢和被墙),仍然可以下载、更新安装包。

4.解除镜象

如果需要解除镜像并恢复到 packagist 官方源,请执行以下命令:

composer config -g --unset repos.packagist

执行之后,composer 会利用默认值(也就是官方源)重置源地址

三.基本用法

要开始在项目中使用 Composer,就需要先安装 Composer,要检查 Composer 是否正常工作,只需要通过 php 来执行 PHAR: php composer.phar,这将返回一个可执行的命令列表。
在项目中使用 Composer,只需要一个 composer.json 文件,该文件包含了项目的依赖和其它的一些元数据

1.关于 require Key

第一件事情(并且往往只需要做这一件事),需要在 composer.json 文件中指定 require key 的值,只需要简单的告诉 Composer 你的项目需要依赖哪些包

{"require": {"monolog/monolog": "1.0.*"}
}
可以看到, require 需要一个 包名称 (例如 monolog/monolog) 映射到 包版本 (例如 1.0.*) 的对象

2.包名称

包名称由供应商名称和其项目名称构成,通常容易产生相同的项目名称,而供应商名称的存在则很好的解决了命名冲突的问题。它允许两个不同的人创建同样名为 json 的库,而之后它们将被命名为 igorw/jsonseldaek/json

这里需要引入 monolog/monolog,供应商名称与项目的名称相同,对于一个具有唯一名称的项目,推荐这么做,它还允许以后在同一个命名空间添加更多的相关项目。如果维护一个库,这将可以很容易的把它分离成更小的部分

3.包版本

在前面的例子中,引入的 monolog 版本指定为 1.0.*,这表示任何从 1.0 开始的开发分支,它将会匹配 1.0.0、1.0.2 或者 1.0.20

版本约束可以用几个不同的方法来指定。

名称

实例

描述

确切的版本号

1.0.2

可以指定包的确切版本

范围

>=1.0 >=1.0,<2.0 >=1.0,<1.1|>=1.2

通过使用比较操作符可以指定有效的版本范围。

有效的运算符:>>=<<=!=

可以定义多个范围,用逗号隔开,这将被视为一个逻辑AND处理。一个管道符号|将作为逻辑OR处理。

AND 的优先级高于 OR

通配符

1.0.*

可以使用通配符*来指定一种模式,1.0.*>=1.0,<1.1是等效的

赋值运算符

~1.2

这对于遵循语义化版本号的项目非常有用,~1.2相当于>=1.2,<2.0

3.下一个重要版本(波浪号运算符)

~ 最好用例子来解释: ~1.2 相当于 >=1.2,<2.0,而 ~1.2.3 相当于 >=1.2.3,<1.3,这对于遵循 语义化版本号 的项目最有用,一个常见的用法是标记所依赖的最低版本,像 ~1.2 (允许1.2以上的任何版本,但不包括2.0),由于理论上直到2.0应该都没有向后兼容性问题,所以效果很好。它的另一种用法,使用 ~ 指定最低版本,但允许版本号的最后一位数字上升。

注意: 虽然 2.0-beta.1 严格地说是早于 2.0,但是,根据版本约束条件, 例如 ~1.2 却不会安装这个版本。就像前面所讲的 ~1.2 只意味着 .2 部分可以改变,但是 1. 部分是固定的

4.稳定性

默认情况下只有稳定的发行版才会被考虑在内。如果想获得 RC、beta、alpha 或 dev 版本,可以使用 稳定标志。可以对所有的包做 最小稳定性 设置,而不是每个依赖逐一设置

4.1稳定标志

requirerequire-dev 还支持稳定性标签(@,仅针对“root 包”)。这允许你在 minimum-stability 设定的范围外做进一步的限制或扩展。例:如果你想允许依赖一个不稳定的包,你可以在一个包的版本约束后使用它,或者是一个空的版本约束内使用它。

实例:

{"require": {"monolog/monolog": "1.0.*@beta","acme/foo": "@dev"}
}

如果你的依赖之一,有对另一个不稳定包的依赖,你最好在 require 中显示的定义它,并带上足够详细的稳定性标识。

实例:

{"require": {"doctrine/doctrine-fixtures-bundle": "dev-master","doctrine/data-fixtures": "@dev"}
}

requirerequire-dev 还支持对 dev(开发)版本的明确引用(即:版本控制系统中的提交编号 commit),以确保它们被锁定到一个给定的状态,即使你运行了更新命令。你只需要明确一个开发版本号,并带上诸如 #<ref> 的标识。

实例:

{"require": {"monolog/monolog": "dev-master#2eb0c0978d290a1c45346a1955188929cb4e5db7","acme/foo": "1.0.x-dev#abc123"}
}
注意: 虽然这有时很方便,但不应该长期在你的包中使用,因为它有一个技术上的限制。 composer.json 将仍然在哈希值之前指定的分支名称读取元数据, 正因为如此,在某些情况下,它不会是一个实用的解决方法, 如果可能,你应该总是尝试切换到拥有标签的版本。

它也可以应用于行内别名,这样它将匹配一个约束,否则不会

require

必须的软件包列表,除非这些依赖被满足,否则不会完成安装。

require-dev (root-only)

这个列表是为开发或测试等目的,额外列出的依赖。“root 包”的 require-dev 默认是会被安装的。然而 installupdate 支持使用 --no-dev 参数来跳过 require-dev 字段中列出的包

4.2 最小稳定性(minimum-stability (root-only))

这定义了通过稳定性过滤包的默认行为。默认为 stable(稳定)。因此如果你依赖于一个 dev(开发)包,你应该明确的进行定义。

对每个包的所有版本都会进行稳定性检查,而低于 minimum-stability 所设定的最低稳定性的版本,将在解决依赖关系时被忽略。对于个别包的特殊稳定性要求,可以在 requirerequire-dev 中设定(请参考 Package links)。

可用的稳定性标识(按字母排序):devalphabetaRCstable

4.安装依赖包

获取定义的依赖到本地项目,只需要调用 composer.phar 运行 install 命令。

php composer.phar install

接着前面的例子,这将会找到 monolog/monolog 的最新版本,并将它下载到 vendor 目录, 这是一个惯例把第三方的代码到一个指定的目录 vendor,如果是 monolog 将会创建 vendor/monolog/monolog 目录

小技巧: 如果正在使用Git来管理你的项目, 你可能要添加 vendor 到你的 .gitignore 文件中

另一件事是 install 命令将创建一个 composer.lock 文件到你项目的根目录中

5.composer.lock - 锁文件

在安装依赖后,Composer 将把安装时确切的版本号列表写入 composer.lock 文件。这将锁定改项目的特定版本。

这是非常重要的,因为 install 命令将会检查锁文件是否存在,如果存在,它将下载指定的版本(忽略 composer.json 文件中的定义)。

这意味着,任何人建立项目都将下载与指定版本完全相同的依赖。你的持续集成服务器、生产环境、你团队中的其他开发人员、每件事、每个人都使用相同的依赖,从而减轻潜在的错误对部署的影响。即使你独自开发项目,在六个月内重新安装项目时,你也可以放心的继续工作,即使从那时起你的依赖已经发布了许多新的版本。

如果不存在 composer.lock 文件,Composer 将读取 composer.json 并创建锁文件。

这意味着如果你的依赖更新了新的版本,你将不会获得任何更新。此时要更新你的依赖版本请使用 update 命令。这将获取最新匹配的版本(根据你的 composer.json 文件)并将新版本更新进锁文件。

php composer.phar update

如果只想安装或更新一个依赖,你可以白名单它们:

php composer.phar update monolog/monolog [...]

6.别名

6.1为什么使用别名?

当你使用 VCS 资源库,你将只会得到类似于这样的版本号:从分支发布的标签获取,它看起来像 2.02.0.x。比较特殊的是,对于你的 master 分支,你会得到一个最新提交的 dev-master 版本。对于你的 bugfix 分支,你会得到一个最新提交的 dev-bugfix 版本。以此类推,这些特殊的版本标识可以用来获取最新的分支源码。

如果你的 master 分支使用标签发布了 1.0 系列版本,即 1.0.11.0.21.0.3 等等,任何依赖它的资源包都可能会使用 1.0.* 这个版本约束。

如果有人想要最新的 dev-master 版本,他们将会碰到一个问题:另一些依赖它的包可能使用了 1.0.* 这个版本约束,因此在 require 这个开发版本时将会产生冲突,因为 dev-master 不符合 1.0.* 的约束。

这时,就可以使用别名。

6.2分支别名

dev-master 指向一个在你 VCS 项目上的主分支。有些用户会想要使用最新的开发版本,这是相当常见的情况。因此,Composer 允许你别名你的 dev-master 版本为一个 1.0.x-dev 的版本号。这是通过在 composer.json 文件中的 extra 下指定 branch-alias 字段来完成的:

{"extra": {"branch-alias": {"dev-master": "1.0.x-dev"}}
}

此处的分支版本必须以 dev- 开头(不可比较的版本名称),对应的别名必须是可比较的开发版本名称(即,以数字开头,并以 .x-dev 结束)。branch-alias 所引用的分支必须是存在的。对于 dev-master 你需要在 master 分支上提交它。

其结果是,任何人都可以使用 1.0.* 版本约束来得到 dev-master 版本。

为了定义分支别名,你必须是需要别名的包的所有者。如果你想别名一个第三方包,而又不想 fork 它到自己的版本库,可以使用行内别名,我们在接下来就会提到它。

6.3 使用行内别名

分支别名是非常适合用于主开发分支的。但为了使用它们,你需要拥有对源码的控制权,并且你需要提交别名修改到你控制的版本库。

当你只想在本地项目中尝试一些依赖包的 bug 修正时,这并不是最好的方式。

出于这个原因,你可以在 requirerequire-dev 字段中直接别名你需要的包。比方说那你找到了 monolog/monolog 的一个 bug。你在 GitHub 上克隆了 Monolog 并在名为 bugfix 的分支上修正了一个问题。现在你想安装这个版本到你的本地项目。

你所使用的 symfony/monolog-bundle require 了 monolog/monolog 并约束了版本 1.*. 因此你需要让你的 dev-bugfix 满足该版本约束。

只要在你项目根目录的 composer.json 文件中加入以下内容:

{"repositories": [{"type": "vcs","url": "https://github.com/you/monolog"}],"require": {"symfony/monolog-bundle": "2.0","monolog/monolog": "dev-bugfix as 1.0.x-dev"}
}

它将会在你的 GitHub 上获取 monolog/monologdev-bugfix 版本并将其版本别名为 1.0.x-dev

注意: 如果要对一个资源包使用行内别名,这个别名(as 的右边)必须能够使用版本约束。as 左边的部分在这之后将被丢弃。因此,如果 A 依赖 B 而 B 又依赖 monolog/monolog 且版本约束为 dev-bugfix as 1.0.x-dev,那么安装 A 时将使用 B 的版本约束,并识别为 1.0.x-dev,此时必须真实存在一个“分支别名”或“1.0 系列分支”。否则就必须在 A 的 composer.json 文件中再次定义行内别名。
注意: 应该尽量避免行内别名,特别是对已经发布的包。如果你发现了一个 bug,请尝试将你的修复合并到上游分支。这将避免使用你资源包的用户出现问题

7.常用命令

1、composer list:获取帮助信息;2、composer init:以交互方式填写composer.json文件信息;3、composer install:从当前目录读取composer.json文件,处理依赖关系,并安装到vendor目录下;4、composer update:获取依赖的最新版本,升级composer.lock文件;5、composer require:添加新的依赖包到composer.json文件中并执行更新;composer remove twbs/bootstrap; 卸载依赖包6、composer search:在当前项目中搜索依赖包;7、composer show:列举所有可用的资源包;8、composer validate:检测composer.json文件是否有效;9、composer self-update:将composer工具更新到最新版本;composer self-update -r :回滚到安装的上一个版本10、composer diagnose:执行诊断命令11、composer clear:清除缓存10、composer create-project:基于composer创建一个新的项目;11、composer dump-autoload:在添加新的类和目录映射是更新autoloader

四.composer 的自动加载原理

1.自动加载的由来

在 PHP 开发过程中,如果希望从外部引入一个 Class ,通常会使用 include 和 require 方法,去把定义这个 Class 的文件包含进来。这个在小规模开发的时候,没什么大问题。但在大型的开发项目中,使用这种方式会带来一些隐含的问题:如果一个 PHP 文件需要使用很多其它类,那么就需要很多的 require/include 语句,这样有可能会 造成遗漏 或者 包含进不必要的类文件。如果大量的文件都需要使用其它的类,那么要保证每个文件都包含正确的类文件肯定是一个噩梦, 况且 require或 incloud 的性能代价很大。
PHP5 为这个问题提供了一个解决方案,这就是 类的自动加载(autoload)机制。autoload机制 可以使得 PHP 程序有可能在使用类时才自动包含类文件,而不是一开始就将所有的类文件include进来,这种机制也称为 Lazy loading (惰性加载)
  • 总结起来,自动加载功能带来了几处优点:

(1).使用类之前无需 include / require
(2).使用类的时候才会 include / require 文件,实现了 lazy loading ,避免了 include / require 多余文件
(3).无需考虑引入 类的实际磁盘地址 ,实现了逻辑和实体文件的分离

2.PHP 自动加载函数 __autoload()

  • 从 PHP5 开始,当我们在使用一个类时,如果发现这个类没有加载,就会自动运行 __autoload() 函数,这个函数是我们在程序中自定义的,在这个函数中我们可以加载需要使用的类。下面是个简单的示例:

<?php
function__autoload($classname) {require_once ($classname . ".class.php");
}
  • 在我们这个简单的例子中,我们直接将类名加上扩展名 .class.php 构成了类文件名,然后使用 require_once 将其加载。

从这个例子中,我们可以看出 __autoload 至少要做三件事情:
根据类名确定类文件名;
确定类文件所在的磁盘路径;
将类从磁盘文件中加载到系统中。
  • 第三步最简单,只需要使用 include / require 即可。要实现第一步,第二步的功能,必须在开发时约定类名与磁盘文件的映射方法,只有这样我们才能根据类名找到它对应的磁盘文件。

  • 当有大量的类文件要包含的时候,我们只要确定相应的规则,然后在 __autoload() 函数中,将类名与实际的磁盘文件对应起来,就可以实现 lazy loading 的效果

  • 如果想详细的了解关于 autoload 自动加载的过程,可以查看手册资料:PHP autoload函数说明

3.__autoload() 函数存在的问题

  • 如果在一个系统的实现中,如果需要使用很多其它的类库,这些类库可能是由不同的开发人员编写的, 其类名与实际的磁盘文件的映射规则不尽相同。这时如果要实现类库文件的自动加载,就必须 在 __autoload() 函数中将所有的映射规则全部实现,这样的话 __autoload() 函数有可能会非常复杂,甚至无法实现。最后可能会导致 __autoload() 函数十分臃肿,这时即便能够实现,也会给将来的维护和系统效率带来很大的负面影响。

  • 那么问题出现在哪里呢?问题出现在 __autoload() 是全局函数只能定义一次 ,不够灵活,所以所有的类名与文件名对应的逻辑规则都要在一个函数里面实现,造成这个函数的臃肿。那么如何来解决这个问题呢?答案就是使用一个 __autoload调用堆栈 ,不同的映射关系写到不同的 __autoload函数 中去,然后统一注册统一管理,这个就是 PHP5 引入的 SPL Autoload 。

4.SPL Autoload

  • SPL是 Standard PHP Library(标准PHP库)的缩写。它是 PHP5 引入的一个扩展标准库,包括 spl autoload 相关的函数以及各种数据结构和迭代器的接口或类。spl autoload 相关的函数具体可见 php中spl_autoload

<?php
// __autoload 函数//
// function __autoload($class) {//     include 'classes/' . $class . '.class.php';
// }
functionmy_autoloader($class) {include'classes/' . $class . '.class.php';
}spl_autoload_register('my_autoloader');// 定义的 autoload 函数在 class 里// 静态方法classMyClass{publicstaticfunctionautoload($className) {// ...}
}spl_autoload_register(array('MyClass', 'autoload'));// 非静态方法classMyClass{
publicfunctionautoload($className) {// ...}
}$instance = newMyClass();
spl_autoload_register(array($instance, 'autoload'));

spl_autoload_register() 就是我们上面所说的__autoload调用堆栈,我们可以向这个函数注册多个我们自己的 autoload() 函数,当 PHP 找不到类名时,PHP就会调用这个堆栈,然后去调用自定义的 autoload() 函数,实现自动加载功能。如果我们不向这个函数输入任何参数,那么就会默认注册 spl_autoload() 函数。

5.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-0
PSR-6 缓存接口规范
PSR-7 HTTP 消息接口规范

具体详细的规范标准可以查看PHP 标准规范

5.1 PSR4 标准

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\ClassNameTest

6.Composer自动加载过程

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 我们的命名空间了

7.Composer 源码分析

很多框架在初始化的时候都会引入 composer 来协助自动加载的,以Yii2为例,它入口文件 index.php 第一句就是利用 composer 来实现自动加载功能。

7.1启动

<?phpdefined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');require __DIR__ . '/../../vendor/autoload.php';
require __DIR__ . '/../../vendor/yiisoft/yii2/Yii.php';
require __DIR__ . '/../../common/config/bootstrap.php';
require __DIR__ . '/../config/bootstrap.php';$config = yii\helpers\ArrayHelper::merge(require __DIR__ . '/../../common/config/main.php',require __DIR__ . '/../../common/config/main-local.php',require __DIR__ . '/../config/main.php',require __DIR__ . '/../config/main-local.php'
);(new yii\web\Application($config))->run();

去 vendor 目录下的 autoload.php :

<?php// autoload.php @generated by Composerrequire_once __DIR__ . '/composer/autoload_real.php';return ComposerAutoloaderInit4e0d5e0478b034b868d50f2ae2cb5654::getLoader();

这里就是 Composer 真正开始的地方了

7.2 Composer自动加载文件

首先,我先大致了解一下Composer自动加载所用到的源文件

(1).autoload_real.php: 自动加载功能的引导类:
composer 加载类的初始化(顶级命名空间与文件路径映射初始化)和注册(spl_autoload_register())
(2).ClassLoader.php : composer 加载类:
composer 自动加载功能的核心类
(3).autoload_static.php : 顶级命名空间初始化类:
用于给核心类初始化顶级命名空间。
(4).autoload_classmap.php : 自动加载的最简单形式:
有完整的命名空间和文件目录的映射
(5).autoload_files.php : 用于加载全局函数的文件:
存放各个全局函数所在的文件路径名;
(6).autoload_namespaces.php : 符合 PSR0 标准的自动加载文件:
存放着顶级命名空间与文件的映射
(7).autoload_psr4.php : 符合 PSR4 标准的自动加载文件:
存放着顶级命名空间与文件的映射;

7.3 autoload_real 引导类

在 vendor 目录下的 autoload.php 文件中我们可以看出,程序主要调用了引导类的静态方法 getLoader() :

<?php// autoload_real.php @generated by Composerclass ComposerAutoloaderInit4e0d5e0478b034b868d50f2ae2cb5654
{private static $loader;public static function loadClassLoader($class){if ('Composer\Autoload\ClassLoader' === $class) {require __DIR__ . '/ClassLoader.php';}}/*** @return \Composer\Autoload\ClassLoader*/public static function getLoader(){/***********************第一部分: 单例---自动加载类只能有一个***********************/if (null !== self::$loader) {return self::$loader;}/***********************第一部分: end ***********************/require __DIR__ . '/platform_check.php';/***********************第二部分: 构造ClassLoader核心类---获得自动加载核心类对象********************//*** 从程序里面我们可以看出:*  composer 先向 PHP 自动加载机制注册了一个函数,这个函数 require 了 ClassLoader 文件。*  成功 new 出该文件中核心类 ClassLoader() 后,又销毁了该函数*/spl_autoload_register(array('ComposerAutoloaderInit4e0d5e0478b034b868d50f2ae2cb5654', 'loadClassLoader'), true, true);self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));spl_autoload_unregister(array('ComposerAutoloaderInit4e0d5e0478b034b868d50f2ae2cb5654', 'loadClassLoader'));/***********************第二部分: end ********************//***********************第三部分: 初始化核心类对象---初始化自动加载核心类对象********************//*** 这一部分就是对自动加载类的初始化,主要是给自动加载核心类初始化顶级命名空间映射* 初始化的方法有两种:*  1. 使用 autoload_static 进行静态初始化*  2. 调用核心类接口初始化*/$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());if ($useStaticLoader) {require __DIR__ . '/autoload_static.php';call_user_func(\Composer\Autoload\ComposerStaticInit4e0d5e0478b034b868d50f2ae2cb5654::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);}}/***********************第三部分: end ********************//***********************第四部分: 注册---注册自动加载核心类对象********************//*** 经过启动与初始化,自动加载核心类对象已经获得了顶级命名空间与相应目录的映射,* 也就是说,如果有命名空间 'App\Console\Kernel,我们已经可以找到它对应的类文件所在位置,然后注册自动加载核心类对象*/$loader->register(true);/***********************第四部分: end ********************//***********************自动加载全局函数 ********************//*** Composer 不止可以自动加载命名空间,还可以加载全局函数。怎么实现的呢?* 把全局函数写到特定的文件里面去,在程序运行前挨个 require就行了*/if ($useStaticLoader) {$includeFiles = Composer\Autoload\ComposerStaticInit4e0d5e0478b034b868d50f2ae2cb5654::$files;} else {$includeFiles = require __DIR__ . '/autoload_files.php';}foreach ($includeFiles as $fileIdentifier => $file) {composerRequire4e0d5e0478b034b868d50f2ae2cb5654($fileIdentifier, $file);}return $loader;}
}/*** @param string $fileIdentifier* @param string $file* @return void*/
function composerRequire4e0d5e0478b034b868d50f2ae2cb5654($fileIdentifier, $file)
{if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;require $file;}
}

第三部分 autoload_static 静态初始化

autoload_static.php
<?phpclass 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(命名空间映射)
 public static $classMap = array ('Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php','Codeception\\Exception\\ExternalUrlException' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Exception/ExternalUrlException.php','Codeception\\Lib\\Connector\\Yii2' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2.php','Codeception\\Lib\\Connector\\Yii2\\ConnectionWatcher' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/ConnectionWatcher.php','Codeception\\Lib\\Connector\\Yii2\\FixturesStore' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/FixturesStore.php','Codeception\\Lib\\Connector\\Yii2\\Logger' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/Logger.php','Codeception\\Lib\\Connector\\Yii2\\TestMailer' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TestMailer.php','Codeception\\Lib\\Connector\\Yii2\\TransactionForcer' => __DIR__ . '/..' . '/codeception/module-yii2/src/Codeception/Lib/Connector/Yii2/TransactionForcer.php','Codeception\\Lib\\Framework' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Lib/Framework.php','Codeception\\Lib\\InnerBrowser' => __DIR__ . '/..' . '/codeception/lib-innerbrowser/src/Codeception/Lib/InnerBrowser.php','Codeception\\Module\\AbstractAsserts' => __DIR__ . '/..' . '/codeception/module-asserts/src/Codeception/Module/AbstractAsserts.php',
...);
PSR4 标准顶级命名空间映射数组
<?phppublic 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,先验证磁盘上这个文件是否存在,如果不存在接着遍历。如果遍历后没有找到,则加载失败

第四部分 注册自动加载核心类对象

核心类的 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 自动加载的真相,命名空间如何通过 composer 转为对应目录文件 ,前面说过,ClassLoader 的 register() 函数将 loadClass() 函数注册到 PHP 的 SPL 函数堆栈中,每当 PHP 遇到不认识的命名空间时就会调用函数堆栈的每个函数,直到加载命名空间成功。所以 loadClass() 函数就是自动加载的关键了

看下 loadClass() 函数:
publicfunctionloadClass($class)
{if ($file = $this->findFile($class)) {includeFile($file);returntrue;}
}publicfunctionfindFile($class)
{// work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731if ('\\' == $class[0]) {$class = substr($class, 1);}// class map lookupif (isset($this->classMap[$class])) {return$this->classMap[$class];}if ($this->classMapAuthoritative) {returnfalse;}$file = $this->findFileWithExtension($class, '.php');// Search for Hack files if we are running on HHVMif ($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() 在解析命名空间的时候主要分为两部分:classMapfindFileWithExtension() 函数。classMap 很简单,直接看命名空间是否在映射数组中即可。麻烦的是 findFileWithExtension() 函数,这个函数包含了 PSR0 和 PSR4 标准的实现。还有个值得我们注意的是查找路径成功后 includeFile() 仍然是外面的函数,并不是 ClassLoader 的成员函数,原理跟上面一样,防止有用户写 $this 或 self。还有就是如果命名空间是以\开头的,要去掉\然后再匹配。

看下 findFileWithExtension 函数:
privatefunctionfindFileWithExtension($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 dirsforeach ($this->fallbackDirsPsr4 as$dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {return$file;}}// PSR-0 lookupif (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 ($dirsas$dir) {if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {return$file;}}}}}// PSR-0 fallback dirsforeach ($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(&dollar;logicalPathPsr4, &dollar;length)”文件是否存在,存在即返回。这里就是
'__DIR__/../phpdocumentor/reflection-common/src + substr(phpDocumentor/Reflection/Element.php,25)'
如果失败,则利用 fallbackDirsPsr4 数组里面的目录继续判断是否存在文件

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_275827.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于JavaWeb+jsp实现企业员工工资管理系统

一、项目简介 本项目是一套基于ServletJsp实现的学生成绩管理系统&#xff0c;主要针对计算机相关专业的正在做bishe的学生和需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目可以直接作为bishe使用。 项目都经过严格调试&#xff0…

Android开发工程师想找工作需要掌握哪些

前言 目前互联网行业越来越好&#xff0c;进入这个行业的人员也是越来越多。从开发的角度来看&#xff0c;开发的职位主要分前端&#xff0c;后端&#xff0c;客户端&#xff08;主要分为ios和android开发&#xff09;以及算法工程师等。 Android开发一直是当前互联网行业中最…

playwright--pytest-playwright、pytest-base-url插件编写用例

文章目录前言一、安装插件二、编写用例三、运行用例四、内置fixture五、全局配置base_url前言 官方的 pytest-playwright 插件可以编写端到端测试。它提供上下文隔离&#xff0c;开箱即用地在多个浏览器配置上运行。它继承了pytest框架&#xff0c;以及支持playwright的一些基…

【FPGA-DSP】第一期:DSP基础

目录 1. DSP基础 1.1 DSP基本概念 1.2 FPGA实现DSP的特点 2. DSP硬核的结构与使用 3. FPGA设计DSP技术 3.1. 浮点数与定点数的表示与转换 3.1.1. 双精度浮点数表示 3.1.2. 双精度浮点数与定点数的转换 本章作为FPGA数字信号处理的入门介绍课程&#xff0c;将介绍DSP的…

如何在Windows 10中恢复丢失的分区?

一般来说&#xff0c;未分配的空间不能在分区前直接用于存储数据&#xff0c;因此大多数Windows用户会将硬盘划分为不同的分区以存储各种数据。你可以在Windows安装期间或安装完系统后通过硬盘管理、DiskPart命令或第三方工具创建分区。 在分区丢失后&#xff0c;你会…

C的强符号/弱符号

首先上代码和结果&#xff1a; 代码&#xff1a; #include <stdio.h> int k; int k; int main() {printf("addr of k %p\n", &k);printf("value of k %d\n", k);return 0; }结果&#xff1a; addr of k 00408074 value of k 0问题&…

《辉煌优配》消费医疗加速回暖 数字化新竞赛鸣枪

“栽培牙集采将给口腔职业带来深远影响。”瑞尔齿科副总经理胡云帆3月23日承受证券时报记者采访时表明&#xff0c;资料费用下降将惠及公司运营端&#xff0c;同时激活了一大批存量客户。公司相关产品和服务价格已经开端呈现一些变动的迹象。 栽培牙费用包含栽培体费用、牙冠费…

狗都能看懂的VAE笔记

文章目录自编码器普通Auto-Encoder的问题解决的方法如何运作数学细节生成模型Auto-Encoder一直是一个非常有创造性的方向。期中的VAE变分编码器一直是我没搞懂的部分&#xff0c;在AI绘画突然火起来的时候&#xff0c;不得不搞清楚VAE了。看了很多VAE的讲解&#xff0c;没有良好…

The Shebeen——爱尔兰酒吧 NFT 来袭!

从爱尔兰神像到标志性的爱尔兰帽子&#xff0c;The Shebeen——爱尔兰酒吧 NFT 系列是一系列非常吸引的独特数字资产&#xff0c;体现了爱尔兰的精神和风俗。 The Shebeen NFT 系列均来自 The Shebeen——爱尔兰酒吧游戏体验&#xff0c;3 月 17 日至 3 月 29 日可在 The Sandb…

c/c++开发,无可避免的自定义类类型(篇八).为类妥善处理异常

目录 一、异常简述 1.1 异常是什么 1.2 异常处理概念 二、异常处理 2.1 try……catch异常处理语法 2.2 动态异常说明-throw 2.3 标准异常体系 2.4 try ...catch抛出异常对象的处理 2.5 异常捕获处理级别 2.6 抛出对象方式 2.7 try 块以及处理块内严禁跳转语法使用 2.8 异常捕…

【MySQL高级篇】 第8章_索引的创建与设计原则

第8章_索引的创建与设计原则 1. 索引的声明与使用 1.1 索引的分类 MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 从 功能逻辑 上说&#xff0c;索引主要有 4 种&#xff0c;分别是普通索引、唯一索引、主键索引、全文索引。 按照 物…

Python如何实现读写txt文件?读写txt文件的方法有哪些?

前言 又是一篇纯知识点的文章&#xff0c;现在看文章的人越来越少了&#xff0c;是都去看视频了吗 今天就来聊聊 - Python实现读写txt文件的方法 一、读写模式&#xff1a; w&#xff1a;向文件中写入内容&#xff0c;w会清空原来文本内容a&#xff1a;向文件中追加内容r&am…

sql性能优化:MS-SQL(SQL Server2012)服务器配置选项(sp_configure )对照表

sql性能优化&#xff1a;MS-SQL&#xff08;SQL Server&#xff09;服务器配置选项&#xff08;sp_configure &#xff09;对照表 2019服务器配置选项 (SQL Server) - SQL Server | Microsoft Learn 2012服务器配置选项 | Microsoft Learn 介绍 可以使用 SQL Server Managemen…

什么是蓝牙的核心协议层和产品类型?一文读懂BQB认证中两者的关联

蓝牙核心协议层 什么是蓝牙核心协议层? 所有蓝牙产品必须拥有相应资格才可以进行出售或分发。而蓝牙技术是需通过软件和硬件的 组合实现,分为蓝牙HOST主机(软件)和CONTROLLER控制器(硬件)。 蓝牙核心协议层的规范如何? 目前蓝牙技术分为经典蓝牙(BR/EDR) 和低功耗蓝牙(B…

【vulhub靶场】medium_socnet

文章目录环境搭建信息收集主机发现&#xff1a;端口扫描Web 渗透(CVE-2019-14322)Pallets Werkzeug 0.15.4 路径遍历漏洞代码注入-反弹shell关于Dockerfile判断是否在Docker环境中&#xff1a;Command SHELL 提升至 Meterpreter方法一方法二Docker环境中存在内网环境内网主机探…

二分查找【含左边界查询 + 右边界查询】

789. 数的范围 - AcWing题库 二分查找看起来是很简单的一个算法&#xff0c;但是其中涉及到比较多的细节问题。 一不小心就死循环了…… 使用条件 一般情况下&#xff0c;二分查找通常适用于一个有序的序列中&#xff08;一般为升序&#xff09; 算法解析 确定左右边界 首…

Linux内核文件系统知识大总结

1、文件系统特点文件系统要有严格的组织形式&#xff0c;使得文件能够以块为单位进行存储。文件系统中也要有索引区&#xff0c;用来方便查找一个文件分成的多个块都存放在了什么位置。如果文件系统中有的文件是热点文件&#xff0c;近期经常被读取和写入&#xff0c;文件系统应…

Cursor软件,内含GPT服务,软件免费,可以进行聊天

Cursor软件,内含GPT服务,软件免费,可以进行聊天 Cursor这个软件的安装 安装好后,双点击使用: 最后有软件地址 如果英文不好,怎么办,搭配有道词典: 效率嘎嘎快 用Ctrl+K进行代码指令后面如下自动生成代码 也可以在聊天框里面进行代码的编写和对话

linux 调试系列(二)coredump段错误查找使用详解

《linux 调试系列&#xff08;一&#xff09;coredump环境配置》 《linux 调试系列&#xff08;二&#xff09;coredump段错误查找使用详解》 一、 背景 这一节&#xff0c;通过一个样例&#xff0c;详细介绍怎么使用coredump来定位程序段错误。 构造两个常用错误&#xff0c;…

STM-32:GPIO 输出-点亮LED-流水灯-蜂鸣器

目录一、GPIO1.1GPIO简介1.2GPIO 硬件解析1.2.1保护二极管1.2.2 P-MOS、N-MOS 管1.2.3数据输入输出寄存器1.2.4复用功能输出1.2.5模拟输入输出1.3GPIO 的工作模式1.3.1 输入模式 (模拟/浮空/上拉/下拉)1.3.2 输出模式 (推挽/开漏)1.3.3 复用功能 (推挽/开漏)1.3.4 小结二、GPIO…