Version Iteration(版本迭代)

PHP 是一种被广泛应用的开放源代码的多用途脚本语言,它可嵌入到 HTML 中,尤其适合 Web 开发。当前最新稳定版本是 PHP 7.X,相比 PHP 5.X 版本增加了不少新特性。PHP 引擎也大部分被重写,PHP 的运行速度也提到了极大的提升。PHP 5.X 现已停止维护更新,建议使用 PHP 7.X 作为开发和生产的环境。

各个版本新特性:

Comments & Split(注释与代码分离)

注释的代码如下:

  1. <?php
  2. // 注释方式一
  3. /* 注释方式二 */
  4. /**
  5. * 注释方式三
  6. *
  7. * @param string name
  8. *
  9. * @return string
  10. */

代码分离的代码如下:

  1. <?php
  2. require("../path/file.php");
  3. require_once("../path/file.php");
  4. include("../path/file.php");
  5. include_once("../path/file.php");

require()require_once()include()include_once() 的异同:

  • 运行时
    • require()require_once()include()include_once() 的区别是带 once 后缀的会判断在这个文件之前是否已经加载过了文件,避免重复加载。
    • require()require_once() 加载文件时,如果出错,将会产生一个 E_COMPILE_ERROR 级别的错误,脚本将会终止运行。
    • include()include_once() 加载文件时,如果出错,只会产生一个 E_WARNING 级别的警告,脚本将会继续运行。
  • 性能上
    • include()include_once() 执行时,文件每次都要进行读取和评估。
    • require()require_once() 执行时,文件只处理一次。
    • include_once()require_once() 是在 include()require() 的基础上进步一步封装。
  • 返回值
    • 如果加载文件内使用 return 返回,那么 require()require_once() 加载文件成功会有该文件 return 值的返回,失败会产生致命错误。
    • 如果加载文件内使用 return 返回,那么 include()include_once() 加载文件成功会有该文件 return 值的返回,失败会抛出一个警告并返回 boolean(false)
    • 如果加载文件内没有使用 return 返回,那么 require()require_once()include()include_once() 加载文件成功会返回 int(1) ,失败分别会产生致命错误和抛出警告并返回 boolean(false)

代码分离相关函数如下:

SPL 是 Standard PHP Library (标准 PHP 库)的缩写。它是 PHP5 引入的一个扩展库,其主要功能包括 autoload 机制的实现及包括各种 Iterator 接口或类。SPL Autoload 具体有几个函数:

  • spl_autoload:__autoload 默认实现
  • spl_autoload_call:尝试所有已注册的函数来加载类
  • spl_autoload_register:注册 __autoload() 函数
  • spl_autoload_unregister:注销已注册的函数
  • spl_autoload_functions:返回所有已注册的函数
  • spl_autoload_extensions:注册并返回 spl_autoload 函数使用的默认文件扩展名

PHP 代码分离相关函数就上述这些,但是实现起来已经是非常灵活了的。既可以简单地实现自动加载,也可以注册多个自定义的自动加载函数去满足不同场景下的代码分离需求。但是过于灵活也不好,会导致不同的框架有不同的方式实现代码分离和自动加载。代码分离和自动加载关键就是类名和文件的映射,为了统一代码分离和自动加载的方式,PHP 配合命名空间推出了 PSR-0 和 PSR-4 自动加载规范,由于 PSR-4 比 PSR-0 能够带来更加简洁的文件结构和更加灵活,目前 PSR-0 已经废弃。

Builder & Dependence Manager(构建与依赖管理)

PEAR

PEAR 是一个常用的依赖包管理器。PEAR 需要扩展包有专属的结构, 开发者在开发扩展包的时候要提前考虑为 PEAR 定制, 否则后面将无法使用 PEAR。

PEAR 安装扩展包的时候, 是全局安装的, 意味着一旦安装了某个扩展包, 同一台服务器上的所有项目都能用上, 当然, 好处是当多个项目共同使用同一个扩展包的同一个版本, 坏处是如果你需要使用不同版本的话, 就会产生冲突。

Composer 与 Packagist

Composer 是另一个常用的依赖包管理器。在 composer.json 文件中列出你项目所需的依赖包,加上一点简单的命令,Composer 将会自动帮你下载并设置你的项目依赖。Composer 有点类似于 Node.js 里的 NPM,或者 Ruby 世界里的 Bundler。

Packagist 是一个 Composer 官方的依赖包仓库,可以在 Packagist 上寻找想要的 PHP 依赖包。

Composer 基本用法

  • 扩展包名称
    • 扩展包名称由供应商和项目成名组成,例如:
      • monolog/monolog
      • laravel/laravel
  • 扩展包版本
    • 确切的版本号
    • 范围
      • 比较操作符 >、>=、<、<=、!=
        • 1.0

        • =1.0 <=2.0

      • 连字符 -
        • 1.0 - 2.0
    • 通配符 *
      • 1.0.*
    • 赋值运算符
      • 波浪号 ~
        • ~1.2 相当于 >=1.2 <2.0.0
        • ~1.2.3 相当于 >=1.2.3 <1.3.0
      • 折音号 ^
        • ^1.2.3 相当于 >=1.2.3 <2.0.0
        • ^0.3.0 相当于 >=0.3.0 <0.4.0
    • 版本稳定性
      • 如果没有显式的指定版本的稳定性,Composer 会根据使用的操作符,默认在内部指定为 -dev 或者 -stable。
      • 可用的稳定性标识
        • dev
        • alpha
        • beta
        • RC
        • stable
  • 扩展包定义
    • 只要有一个 composer.json 文件在目录中,那么整个目录就是一个包。当添加一个扩展包被 require 到项目时,就是创建了一个依赖于其他扩展包、没有名字的包。
    • Composer 将那些已经安装在系统上,但并不是由 Composer 安装的包视为一个虚拟的平台软件包。这包括 PHP 本身、PHP 扩展和一些系统库。
  • 扩展包常见命令
    • composer create-project - 创建项目
      • composer create-project --prefer-dist vendor/package demo
    • composer install - 安装项目所有扩展包
    • composer require - 安装一个新的扩展包
      • composer require vendor/package
      • composer require vendor/package:2.*
      • composer require vendor/package:dev-master -vvv
      • composer require vendeor/package:^2.0.0-alpha2
    • composer update - 更新扩展包
      • composer update
      • composer update vendor/package
      • composer update vendor/* - 支持通配符
      • 需要注意的是,能升级的版本收到版本约束的影响,扩展包不会升级到超出版本约束的范围。
    • composer remove - 移除扩展包
      • composer remove vendor/package
    • composer dump - 打印自动加载索引
    • composer search - 搜索扩展包信息
      • composer search monolog
    • composer show - 展示扩展包信息
      • composer serach monolog

Composer 文件

composer.json 文件

根据实际项目的 composer.json 文件解释其中字段,下面以 Laravel 项目的 composer.json 文件为例:

  1. {
  2. "name": "laravel/laravel",
  3. "description": "The Laravel Framework.",
  4. "keywords": ["framework", "laravel"],
  5. "license": "MIT",
  6. "type": "project",
  7. "require": {
  8. "php": "^7.1.3",
  9. "fideloper/proxy": "^4.0",
  10. "guzzlehttp/guzzle": "~6.0",
  11. "laravel/framework": "5.7.*",
  12. "laravel/horizon": "^2.0",
  13. "laravel/tinker": "^1.0",
  14. "predis/predis": "^1.1",
  15. },
  16. "require-dev": {
  17. "beyondcode/laravel-dump-server": "^1.0",
  18. "filp/whoops": "^2.0",
  19. "fzaninotto/faker": "^1.4",
  20. "mockery/mockery": "^1.0",
  21. "nunomaduro/collision": "^2.0",
  22. "orangehill/iseed": "^2.6",
  23. "phpunit/phpunit": "^7.0"
  24. },
  25. "autoload": {
  26. "classmap": [
  27. "database/seeds",
  28. "database/factories"
  29. ],
  30. "psr-4": {
  31. "App\\": "app/"
  32. }
  33. },
  34. "autoload-dev": {
  35. "psr-4": {
  36. "Tests\\": "tests/"
  37. }
  38. },
  39. "extra": {
  40. "laravel": {
  41. "dont-discover": [
  42. "beyondcode/laravel-dump-server"
  43. ]
  44. }
  45. },
  46. "scripts": {
  47. "post-root-package-install": [
  48. "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
  49. ],
  50. "post-create-project-cmd": [
  51. "@php artisan key:generate"
  52. ],
  53. "post-autoload-dump": [
  54. "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
  55. "@php artisan package:discover"
  56. ]
  57. },
  58. "config": {
  59. "preferred-install": "dist",
  60. "sort-packages": true,
  61. "optimize-autoloader": true
  62. },
  63. "minimum-stability": "dev",
  64. "prefer-stable": true
  65. }
  • name - 包名
  • description - 简介
  • keywords - 关键词
  • license - 许可协议
  • type - 安装类型
    • library
    • project
    • metapackage
    • composer-plugin
  • require - 必须的软件包列表,除非这些依赖被满足,否则不会完成安装
  • require-dev - 开发或测试等目的,额外列出的依赖
  • autoload - 用于生产或部署的 PHP 自动加载映射
    • psr-4
    • psr-0
    • classmap
    • files
  • autoload-dev - 用于开发或测试的 PHP 自动加载映射
    • psr-4
    • psr-0
    • classmap
    • files
  • extra - 供 scripts 使用的、任意的额外数据
  • scripts - Composer 可以在安装过程中的各个阶段挂接脚本
    • post-root-package-install - 在 create-project 命令期间,根包安装完成后触发
    • post-create-project-cmd - 在 create-project 命令执行后触发
    • post-autoload-dump - 在自动加载器被转储后触发,installupdatedump 命令后触发

点击查看更多 composer.json 文件字段说明。

composer.lock 文件

在项目中安装扩展包后,Composer 会把安装时确切的版本号列表写入 composer.lock 文件。

请提交项目中的 composer.lock、composer.json 文件到版本库中

这是非常重要,因为 install 命令将会检查锁文件是否存在,如果存在,它将忽略 composer.json 文件中的定义,直接下载指定的版本。这意味着,任何人简历项目都将下载与指定版本完全相同的依赖。持续集成服务器、生产环境、团队中的其他开发人员、每件事、每个人都使用相同的依赖,从而减轻潜在的错误对部署造成影响。

Composer 自动加载机制

Composer 将解决如下问题:

  • 有一个项目依赖于若干个扩展包
  • 其中一些库依赖于若干个扩展包
  • 声明项目所依赖的东西(文件,扩展包)
  • 找出项目所依赖的扩展包,并安装合适的版本

Composer 准备了一个自动加载文件,它可以加载 Composer 下载的所有的类文件。使用它,只需要将下面这行代码添加到项目的引导文件中即可:

  1. <?php
  2. require 'vendor/autoload.php';

以 Laravel 框架为例,在查看文件 public/index.php 时,可以看到代码如下:

  1. <?php
  2. /**
  3. * Laravel - A PHP Framework For Web Artisans
  4. *
  5. * @package Laravel
  6. * @author Taylor Otwell <taylor@laravel.com>
  7. */
  8. define('LARAVEL_START', microtime(true));
  9. /*
  10. |--------------------------------------------------------------------------
  11. | Register The Auto Loader
  12. |--------------------------------------------------------------------------
  13. |
  14. | Composer provides a convenient, automatically generated class loader for
  15. | our application. We just need to utilize it! We'll simply require it
  16. | into the script here so that we don't have to worry about manual
  17. | loading any of our classes later on. It feels great to relax.
  18. |
  19. */
  20. require __DIR__.'/../vendor/autoload.php';
  21. ...

代码 require __DIR__.'/../vendor/autoload.php'; 注册 Composer 自动加载机制,查看文件 autoload.php 代码如下:

  1. <?php
  2. // autoload.php @generated by Composer
  3. require_once __DIR__ . '/composer/autoload_real.php';
  4. return ComposerAutoloaderInit814a92544ccdda06795b3f7f5a08b060::getLoader();

autoload.php 文件是一个入口文件,关于 Composer 自动加载机制,需要查看 autoload_real.php 文件,打开文件代码如下:

  1. <?php
  2. // autoload_real.php @generated by Composer
  3. class ComposerAutoloaderInit814a92544ccdda06795b3f7f5a08b060
  4. {
  5. private static $loader;
  6. public static function loadClassLoader($class)
  7. {
  8. if ('Composer\Autoload\ClassLoader' === $class) {
  9. require __DIR__ . '/ClassLoader.php';
  10. }
  11. }
  12. public static function getLoader()
  13. {
  14. if (null !== self::$loader) {
  15. return self::$loader;
  16. }
  17. spl_autoload_register(array('ComposerAutoloaderInit814a92544ccdda06795b3f7f5a08b060', 'loadClassLoader'), true, true);
  18. self::$loader = $loader = new \Composer\Autoload\ClassLoader();
  19. spl_autoload_unregister(array('ComposerAutoloaderInit814a92544ccdda06795b3f7f5a08b060', 'loadClassLoader'));
  20. $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
  21. if ($useStaticLoader) {
  22. require_once __DIR__ . '/autoload_static.php';
  23. call_user_func(\Composer\Autoload\ComposerStaticInit814a92544ccdda06795b3f7f5a08b060::getInitializer($loader));
  24. } else {
  25. $map = require __DIR__ . '/autoload_namespaces.php';
  26. foreach ($map as $namespace => $path) {
  27. $loader->set($namespace, $path);
  28. }
  29. $map = require __DIR__ . '/autoload_psr4.php';
  30. foreach ($map as $namespace => $path) {
  31. $loader->setPsr4($namespace, $path);
  32. }
  33. $classMap = require __DIR__ . '/autoload_classmap.php';
  34. if ($classMap) {
  35. $loader->addClassMap($classMap);
  36. }
  37. }
  38. $loader->register(true);
  39. if ($useStaticLoader) {
  40. $includeFiles = Composer\Autoload\ComposerStaticInit814a92544ccdda06795b3f7f5a08b060::$files;
  41. } else {
  42. $includeFiles = require __DIR__ . '/autoload_files.php';
  43. }
  44. foreach ($includeFiles as $fileIdentifier => $file) {
  45. composerRequire814a92544ccdda06795b3f7f5a08b060($fileIdentifier, $file);
  46. }
  47. return $loader;
  48. }
  49. }
  50. function composerRequire814a92544ccdda06795b3f7f5a08b060($fileIdentifier, $file)
  51. {
  52. if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
  53. require $file;
  54. $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
  55. }
  56. }

上述代码中的 $loader 是 composer/ClassLoader.php 的 ClassLoader 类的实例。ClassLoader 类和 spl_autoload_register、spl_autoload_unregister 都是实现自动加载机制的。我们先重点关注代码中的几处 required 语句:

  1. <?php
  2. $map = require __DIR__ . '/autoload_namespaces.php';
  3. $map = require __DIR__ . '/autoload_psr4.php';
  4. $classMap = require __DIR__ . '/autoload_classmap.php';
  5. $includeFiles = require __DIR__ . '/autoload_files.php';

每个 require 语句都加载了 vendor/composer 下的一个 PHP 文件,它们分别对应不同的加载方式,在 composer.json 文件中它们分别被命名为:

  • psr-4 - autoload_psr4.php
  • psr-0 - autolod_namespaces.php
    • 自动加载规范(已废弃)
  • classmap - autoload_classmap.php
    • 通过配置指定的目录或文件,在 Composer 安装或更新时生成新的映射
  • files - autoload_files.php
    • 手动指定直接加载的文件

以 Laravel 的 composer.json 文件为例:

  1. {
  2. ...
  3. "autoload": {
  4. "psr-4": {
  5. "App\\": "app/"
  6. },
  7. "classmap": [
  8. "database/seeds",
  9. "database/factories"
  10. ]
  11. },
  12. ...
  13. }

接着再重点分析 autoload_real.php 代码

  1. <?php
  2. // autoload_real.php @generated by Composer
  3. class ComposerAutoloaderInit87df794bebfc20f9e9133106778dd77b
  4. {
  5. private static $loader;
  6. public static function loadClassLoader($class)
  7. {
  8. if ('Composer\Autoload\ClassLoader' === $class) {
  9. require __DIR__ . '/ClassLoader.php';
  10. }
  11. }
  12. public static function getLoader()
  13. {
  14. // 单例模式,判断 loader 是否存在
  15. if (null !== self::$loader) {
  16. return self::$loader;
  17. }
  18. // 使用 spl_autoload_register 注册自动加载函数
  19. spl_autoload_register(array('ComposerAutoloaderInit87df794bebfc20f9e9133106778dd77b', 'loadClassLoader'), true, true);
  20. // 执行 new \Composer\Autoload\ClassLoader(); 时,会调用当前类的 loadClassLoader 类方法,返回 ClassLoader 类的实例
  21. self::$loader = $loader = new \Composer\Autoload\ClassLoader();
  22. // 注销刚才注册的自动加载函数
  23. spl_autoload_unregister(array('ComposerAutoloaderInit87df794bebfc20f9e9133106778dd77b', 'loadClassLoader'));
  24. // 当 PHP 版本大于 5.6、没有使用 HHVM_VERSION、没有使用 Zend Guard 编码,则执行下面代码
  25. $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
  26. if ($useStaticLoader) {
  27. require_once __DIR__ . '/autoload_static.php';
  28. // 使用静态映射的方式去获取所有 PSR-4、PSR-0、ClassMap、files 等加载方式的类名和文件
  29. call_user_func(\Composer\Autoload\ComposerStaticInit87df794bebfc20f9e9133106778dd77b::getInitializer($loader));
  30. } else {
  31. // 获取 PSR-0 加载方式的类和路径
  32. $map = require __DIR__ . '/autoload_namespaces.php';
  33. foreach ($map as $namespace => $path) {
  34. $loader->set($namespace, $path);
  35. }
  36. // 获取 PSR-4 加载方式的类和路径
  37. $map = require __DIR__ . '/autoload_psr4.php';
  38. foreach ($map as $namespace => $path) {
  39. $loader->setPsr4($namespace, $path);
  40. }
  41. // 获取 classmap 加载方式的类和路径
  42. $classMap = require __DIR__ . '/autoload_classmap.php';
  43. if ($classMap) {
  44. $loader->addClassMap($classMap);
  45. }
  46. }
  47. // ClassLoader 注册自动加载函数
  48. $loader->register(true);
  49. // 直接 require 所有 files 加载方式的文件
  50. if ($useStaticLoader) {
  51. $includeFiles = Composer\Autoload\ComposerStaticInit87df794bebfc20f9e9133106778dd77b::$files;
  52. } else {
  53. $includeFiles = require __DIR__ . '/autoload_files.php';
  54. }
  55. foreach ($includeFiles as $fileIdentifier => $file) {
  56. composerRequire87df794bebfc20f9e9133106778dd77b($fileIdentifier, $file);
  57. }
  58. return $loader;
  59. }
  60. }
  61. function composerRequire87df794bebfc20f9e9133106778dd77b($fileIdentifier, $file)
  62. {
  63. if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
  64. require $file;
  65. $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
  66. }
  67. }

至此,Composer 自动加载机制已经基本了解,如果想知道 $loader 是如何实现加载对应文件的,可以了解 ClassLoader 类的 loadClass 类方法的实现,在此就不在赘述了。