Skip to content

PHP 扩展包开发教程

常见的扩展包类型

API SDK

为特定的服务商 API 提供 SDK,提供更方便的调用,隐藏不必要的开发细节,比如 EasyWeChat 是对微信一系列 API 的封装,overtrue/flysystem-qiniu 是对七牛 CDN API 的封装以及 EasySMS 是封装了各大国内短信平台的 SDK 等。

业务模块

封装一些通用的业务模块,比如购物车、登录注册、权限控制等。比如:

管理工具

对一些业务工具或者项目内容提供 UI 或者命令行管理入口。比如:

生成器

针对重复的内容提供基于模板或者项目脚手架的生成工具。比如:

系统模块封装

时间、图像处理、PDF 生成、文字提取等。比如:

  • PHPOffice —— Word、Excel 等 Office 产品的 PHP 阅读和编辑工具
  • Snappy —— HTML 页面生成 PDF,基于 Webkit 渲染引擎
  • DOMPdf —— HTML 页面生成 PDF,基于一些常见 PHP 原生扩展实现
  • Glide —— 高品质的图像处理扩展包

设计模式实践

通过一些巧妙的封装,实现设计模式在应用中的实践。比如 Laravel 中的

当然还有 一些其它的扩展包,不过我们常见的主要归类为以上几种,熟读一下这些优秀扩展的代码,你将获益匪浅。

Composer 基础使用

Composer 命令行工具的基础使用

注意:vendor/package 为包名示例,请替换为具体需要的包名称

安装依赖

sh
composer require vendor/package

或者

sh
composer install

更新依赖

sh
composer update [vendor/package]

包名是可选的,指定时将更新指定的包并更新它所依赖的包,不指定时检查所有包的新版本。

删除依赖

sh
composer remove vendor/package

学习资料

扩展包的基础结构

虽然说扩展包并没有什么强制的规定一定要如何组织代码,但是我们推荐根据业界约定俗成的结构:

md
weather/
├── .editorconfig      # 编辑器配置文件,比如缩进大小、换行模式等
├── .gitattributes     # git 配置文件,可以设计导出时忽略文件等
├── .gitignore         # git 忽略文件配置列表
├── .php_cs            # PHP-CS-Fixer 配置文件
├── README.md    
├── composer.json
├── phpunit.xml.dist
├── src
│   └── .gitkeep
└── tests
    └── .gitkeep

上面这个包结构适用于几乎所有的 PHP 扩展包,如果你的扩展包有其它附加内容,也可以自由地做一些调整,只要满足清晰合理即可。

结构说明

  • src 目录

    通常我们将源代码放置到此目录下,文件名与类命名遵循驼峰命名法,目录与命名空间一致。

    注意

    我们命名空间通常是按包名来的,然后 src 映射到驼峰写法的命名空间(例如:包 overtrue/weather 的命名空间为 Overtrue\Weather)。

    比如 src\Weather.php 文件的命名空间为 Overtrue\Weather,类名为 Overtrue\Weather\Weather。 代码组织结构请符合 PSR-4 规范

  • tests 目录

    用于存放单元测试或者功能测试的测试用例代码,与 src 组织规则基本一致。

  • .editorconfig 文件

    EditorConfig 的配置文件,EditorConfig 是一套用于统一代码格式的解决方案,很多项目都有用到,比如 Laravel、jQuery、Underscore 和 Ruby 等等。

    EditorConfig 可以帮助开发者在不同的编辑器和 IDE 之间定义和维护一致的代码风格。EditorConfig 包含一个用于定义代码格式的文件和一批编辑器插件,这些插件可以让编辑器读取配置文件并依此格式化代码。EditorConfig 的配置文件十分易读,并且可以很好的在 VCS(Version Control System)下工作。

    简单的说就是,这个配置文件定义了一些规则,比如 PHP 缩进是用空格还是用 Tab。它会被现代的编辑器所识别并应用(部分编辑器可能需要安装对应的插件,请参考 EditorConfig 官网 )使用。

  • .gitattributes

    Git 的属性配置文件,你可以对个别文件或目录定义不同的合并策略,让 Git 知道怎样比较非文本文件,在你提交或签出前让 Git 过滤内容。你将在这部分了解到能在自己的项目中使用的属性,以及一些实例。更多请参考:《自定义 Git - Git 属性》- git-scm.com

  • .gitignore

    Git 忽略文件列表配置文件,将不需要纳入版本控制的文件或者目录按行配置在该文件即可。

  • .php_cs

    代码格式修复工具 PHP-CS-Fixer 配置文件,它可以按配置的标准自动修复代码格式,以及统一文件头注释等非常多的功能。

  • README.md

    项目说明文档,一份项目介绍与使用指引,维护状态授权方式等。

  • composer.json

    Composer 配置文件。

  • phpunit.xml.dist

    PHPUnit 配置文件,指定测试目录与测试环境变量等,具体内容请参考官方文档:《组织测试:用 XML 配置来编排测试套件》- PHPunit

  • .gitkeep

    如果一个目录为空,我们是无法纳入到版本控制中的,所以我们创建了一个随意命名(最好还是按业界通用做法命名为 .gitkeep)的隐藏文件来保证 目录不为空

包构建工具

我们在开发过程中每次都去建立这个目录会比较麻烦,所以我们也做了一个包结构生成工具来完成这些基础工作:

overtrue/package-builder:

用法非常的简单,我推荐大家全局安装:

sh
composer global require "overtrue/package-builder" --prefer-source

基本用法

sh
package-builder build [目标目录]

比如我们打算做一个扩展包叫 weather ,目录是当前目录下:

sh
package-builder build ./weather

然后会以交互问答形式要求输入一些基本信息。

其中大部分都是默认值即可。然后我们就会得到这样的目录结构了:

md
weather/
├── .editorconfig      # 编辑器配置文件,比如缩进大小、换行模式等
├── .gitattributes     # git 配置文件,可以设计导出时忽略文件等
├── .gitignore         # git 忽略文件配置列表
├── .php_cs            # PHP-CS-Fixer 配置文件
├── README.md    
├── composer.json
├── phpunit.xml.dist
├── src
│   └── .gitkeep
└── tests
    └── .gitkeep

2 directories, 9 files

接下来就要进入到我们的开发部分了。

需求分析与功能设计 - 天气 SDK

我们以一个天气预报 SDK 为例来讲解本门课程,天气接口我们使用 高德开放平台 提供的,免费限次使用,开始之前我们需要做一下需求分析与功能设计。

需求分析

我们对天气接口的使用需求较为简单,主要为以下两个:

  • 按地名查询实时天气;
  • 获取最近的天气预报。

INFO

高德开放平台提供的天气接口满足以上需求。

功能设计

虽然我们提供的主要功能为上面两个,但是涉及到编码的层面我们需要考虑的将会更细化一些:

  • 主要业务:获取天气信息;
  • 异常处理:提供统一的异常类,方便调用方定位异常;
  • 自定义参数:需要支持自定义参数,比如超时时间等;
  • 提供 Laravel Service Provider :为 Laravel 框架提供专属封装的入口。

接下来让我们一起来实现这些功能吧。

准备工作 - 创建高德开放平台应用

高德开放平台

  • 注册登录
  • 创建应用
  • 获取 API Key

TIP

服务平台:选择 Web服务

创建项目并安装依赖包

Composer 加速

sh
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

安装 Package Builder 包结构创建工具

sh
composer global require overtrue/package-builder

TIP

记得配置全局变量,否则无法使用命令 package-builder build weather
记得重新打开 CMD 工具,否则命令还未生效。

  • windows 下 ,用 Git Bash 工具执行 composer global require overtrue/package-builder 就能看到 安装的路径了,然后把路径复制到 环境变量中。

创建目录结构

sh
package-builder build weather

按提示操作即可

声明自动加载

接下来我们需要在 composer.json 中声明包自动加载的命名空间。如果已存在,则可跳过。

composer.json

json
{
    "autoload": {
        "psr-4": {
            "Azhida\\Weather\\": "./src/"
        }
    }
}

安装依赖

我们的项目需要请求接口,所以我们选择 guzzle/guzzle 来做为 http client, 其它暂时用不到,后面用到的时候再安装即可:

sh
cd weather/
composer require guzzlehttp/guzzle

从接口获取天气数据

根据之前设计的功能,结合 天气查询接口文档 的参数说明,我们添加几个方法:

INFO

注意:方法名通常是 动名词 形式,比如: getUsersupdateProfiledeleteOrderrevertAction 等。

创建 Weather

src/Weather.php

php
<?php

namespace Azhida\Weather;

class Weather
{
    protected $key;
    protected $guzzleOptions = [];

    public function __construct(string $key)
    {
        $this->key = $key;
    }

    // 获取实例
    public function getHttpClient()
    {
        return new Client($this->guzzleOptions);
    }

    // 配置参数
    public function setGuzzleOptions(array $options)
    {
        $this->guzzleOptions = $options;
    }

    /**
     * $city - 城市名 / 高德地址位置 adcode,比如:“深圳” 或者(adcode:440300);
     * $type - 返回内容类型:base: 返回实况天气 / all: 返回预报天气;
     * $format - 输出的数据格式,默认为 json 格式,当 output 设置为 “xml” 时,输出的为 XML 格式的数据。
     */
    public function getWeather($city, string $type = 'base', string $format = 'json')
    {
        $url = 'https://restapi.amap.com/v3/weather/weatherInfo';

        $query = array_filter([
            'key' => $this->key,
            'city' => $city,
            'output' => $format,
            'extensions' =>  $type,
        ]);

        $response = $this->getHttpClient()->get($url, [
            'query' => $query,
        ])->getBody()->getContents();

        return 'json' === $format ? \json_decode($response, true) : $response;
    }
}

异常与错误处理

什么时候应该抛出异常?

按照单一职责原则,一个类只应该关心自己的逻辑,当出现问题的时候,如果不是当前类该处理的,我们就应该抛出而不是消化。怎么理解呢?比如,请求天气接口的时候,网络出现问题,Guzzle 抛出了异常,这时候应该抛出异常。可是为啥不是 catch 异常然后返回 false 呢?这就涉及到边界问题了,业务异常运行时异常 要区分开,对方接口说你要查询的城市不存在,返回错误,这属于业务异常,这时候是不会抛出异常的。而你请求的 API 不存在,或者对方服务器宕机,或者你的服务器网络出问题,这属于运行时异常,就应该以异常抛出来告知调用方,而不是自主处理该异常并返回 false 来结束逻辑。

当然了,可能刚开始你是不太好理解这部分的,当你有更多的应用设计与代码实践经验后你会慢慢感受到这个边界。

异常的分类与命名

异常通常分为几个种类,按名称来命名:

  • 参数错误,当传入的参数不合法时,我们通常抛出异常;
  • HTTP 异常,在请求 API 时异常终止时抛出的异常;
  • 其它异常,模块其它部分异常。

当然还有其它的,你可以命名非常细,比如 InvalidGatewayNameExceptionErrorResponseException 等等,你可以自由发挥,只要做到合理命名即可。

当扩展出现问题时,为了方便上层调用方更准确的定位问题,我们通常会为模块设计一个根异常类,在 src/Exceptions 创建类 Azhida\Weather\Exceptions\Exception 类。

异常类定义

根异常类

src/Exceptions/Exception.php

php
<?php

namespace Azhida\Weather\Exceptions;

class Exception extends \Exception
{

}

HTTP 网络请求异常类

src/Exceptions/HttpException.php

php
<?php

namespace Azhida\Weather\Exceptions;

/**
 * HTTP 请求异常类
 */
class HttpException extends Exception
{

}

参数异常类

src/Exceptions/InvalidArgumentException.php

php
<?php

namespace Azhida\Weather\Exceptions;

/**
 * 参数异常类
 */
class InvalidArgumentException extends Exception
{

}

异常类的使用

src/Weather.php

php
<?php

namespace Azhida\Weather;
use Azhida\Weather\Exceptions\HttpException;
use Azhida\Weather\Exceptions\InvalidArgumentException;

class Weather
{
    protected $key;
    protected $guzzleOptions = [];

    public function __construct(string $key)
    {
        $this->key = $key;
    }

    // 获取实例
    public function getHttpClient()
    {
        return new Client($this->guzzleOptions);
    }

    // 配置参数
    public function setGuzzleOptions(array $options)
    {
        $this->guzzleOptions = $options;
    }

    /**
     * $city - 城市名 / 高德地址位置 adcode,比如:“深圳” 或者(adcode:440300);
     * $type - 返回内容类型:base: 返回实况天气 / all: 返回预报天气;
     * $format - 输出的数据格式,默认为 json 格式,当 output 设置为 “xml” 时,输出的为 XML 格式的数据。
     */
    public function getWeather($city, string $type = 'base', string $format = 'json')
    {
        $url = 'https://restapi.amap.com/v3/weather/weatherInfo';

        if (!\in_array(\strtolower($format), ['xml', 'json'])) {
            // 参数异常
            throw new InvalidArgumentException('Invalid response format: '.$format);
        }

        if (!\in_array(\strtolower($type), ['base', 'all'])) {
            // 参数异常
            throw new InvalidArgumentException('Invalid type value(base/all): '.$type);
        }

        $query = array_filter([
            'key' => $this->key,
            'city' => $city,
            'output' => \strtolower($format),
            'extensions' =>  \strtolower($type),
        ]);

        try {
            $response = $this->getHttpClient()->get($url, [
                'query' => $query,
            ])->getBody()->getContents();

            return 'json' === $format ? \json_decode($response, true) : $response;
        } catch (\Exception $e) {
            // HTTP 网络请求异常
            throw new HttpException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

编写单元测试

准备工作

首先需要安装两个包:单元测试框架 PHPUnit 、依赖模拟 Mockery

项目根目录下执行

sh
composer require phpunit/phpunit mockery/mockery --dev

在开始写测试之前我们先大致了解一下 PHPUnit 的使用:

  • 测试用例与类名对应,以 Test.php 结尾,比如 WeatherTest.php,类名与文件名一致;
  • 测试用例需要继承 PHPUnit\Framework\TestCase 基类;
  • 测试用例的目录结构与源码一致;
  • 测试用例的方法名格式为 test源方法名,比如:testGetWeather

以上这些规约虽然有一些不是强制的,但是也建议你按照这样子来写,以保证代码清晰与可读性。

未完待续

todo ...

本地测试扩展包

创建测试项目

TIP

在开发好的扩展包同级目录下创建空项目

sh
mkdir weather-test
cd weather-test

根目录使用 composer 引入开发好的扩展包 azhida/weather

sh
# 需要先初始化 composer.json, 一路回车即可
composer init

# 配置包路径,注意,这里 `../weather` 为相对路径,不要弄错了
composer config repositories.weather path ../weather

# 安装扩展包  这里  `dev-master`  中的 dev 指该分支下最新的提交,master 是指定的包中的分支名
composer require azhida/weather:dev-master
# 或者
composer require azhida/weather:dev-main
# 或者
composer require azhida/weather @dev
# 如果提示版本号找不到,就在扩展包的 composer.json 文件加上版本号
# "version": "dev-master",

weather-test 根目录新建文件 index.php

index.php

php
<?php

require __DIR__ .'/vendor/autoload.php';

use Azhida\Weather\Weather;

// 高德开放平台应用 API Key
$key = '7d54670aa0f9395bf6c82bc5359b3bef';
$w = new Weather($key);

echo "获取实时天气:\n";

$response = $w->getWeather('南宁');
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

echo "\n\n获取天气预报:\n";

$response = $w->getWeather('南宁', 'all');
echo json_encode($response, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);


echo "\n\n获取实时天气(XML):\n";

echo $w->getWeather('南宁', 'base', 'xml');

运行 index.php 文件就有数据了

sh
php index.php

原理说明

sh
composer config repositories.weather path ../weather

它在 composer.json 中添加了如下部分:

composer.json

json
    .
    .
    .
     "repositories": {
        "weather": {
            "type": "path",
            "url": "../weather"
        }
      }
    .
    .
    .

这样我们在安装的时候 composer 会创建一个软链接 vendor/azhida/weather 到包所在目录 ../weather,这样一来,你可以直接在测试项目的 vendor/azhida/weather 下修改文件,包里的文件也会跟着变了,是不是对于开发过程中来讲非常的方便?

TIP

🚨注意:如果在包的原目录创建了新文件,你可能需要刷新一下目录树才能看到新的文件哦。

为 Laravel 集成优化

为了方便 Laravel 应用集成我们的扩展包,我们需要做一个 Laravel Service Provider,这里由于需要考虑到一个配置问题,因为调用接口需要用到高德开放平台应用 API Key,所以我们设计将它放置到 Laravel 应用的 config/services.php 中:

在扩展包 weather 目录下

config/services.php

php
<?php

return [
    'weather' => [
        'key' => env('WEATHER_API_KEY'),
    ],
];

src/ServiceProvider.php

php
<?php

namespace Azhida\Weather;

class ServiceProvider extends \Illuminate\Support\ServiceProvider
{
    protected $defer = true;

    public function register()
    {
        $this->app->singleton(Weather::class, function(){
            return new Weather(config('services.weather.key'));
        });

        $this->app->alias(Weather::class, 'weather');
    }

    public function provides()
    {
        return [Weather::class, 'weather'];
    }
}

其中我们设置了 $defer 属性为 true ,并且添加了方法 provides ,这是 Laravel 扩展包的延迟注册方式,它不会在框架启动就注册,而是当你调用到它的时候才会注册。

配置 Laravel Auto Discovery

我们需要在 composer.json 中添加如下部分:

composer.json

json
.
.
.
"extra": {
    "laravel": {
        "providers": [
            "Azhida\\Weather\\ServiceProvider"
        ]
    }
}

这样一来我们 Laravel 应用安装后无需手动注册服务提供器即可使用。

在 Laravel 项目中测试

在扩展包的同级目录下,先创建一个 Laravel 项目:

sh
composer create-project laravel/laravel laravel-weather-test
cd laravel-weather-test

# 配置包路径,注意,这里 `../weather` 为相对路径,不要弄错了
composer config repositories.weather path ../weather 

# 安装扩展包
composer require azhida/weather @dev

laravel-weather-test 项目下添加配置项:

laravel-weather-test/config/services.php

php
    .
    .
    .
    'weather' => [
        'key' => env('WEATHER_API_KEY'),
    ],

另外还需要配置一个 .env 变量:

TIP

WEATHER_API_KEY 为我们前面准备工作时申请的应用 API Key , 即 高德开放平台应用的 API_KEY

WEATHER_API_KEY=7d54670aa0f9395bf6c82bc5359b3bef

创建 控制器

app/Http/Controllers/WeatherController.php

php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Azhida\Weather\Weather;

class WeatherController extends Controller
{
    public function show(Request $request, Weather $weather, $city)
    {
        return $weather->getWeather($city);
    }
}

laravel 根目录生成命令控制文件

sh
php artisan make:command Test

会生成文件 laravel-weather-test\app\Console\Commands\Test.php

laravel-weather-test\app\Console\Commands\Test.php

php
<?php

namespace App\Console\Commands;

use Azhida\Weather\Weather;
use Illuminate\Console\Command;

class Test extends Command
{
    protected $signature = 'test';

    protected $description = '测试';

    public function handle(Weather $weather)
    {
        $res = $weather->getWeather('深圳');
        dd($res);
    }
}

laravel 根目录执行该方法就能看到结果了

sh
php artisan test
结果
[
  "status" => "1"
  "count" => "1"
  "info" => "OK"
  "infocode" => "10000"
  "lives" => array:1 [
    0 => array:11 [
      "province" => "广东"
      "city" => "深圳市"
      "adcode" => "440300"
      "weather" => "晴"
      "temperature" => "24"
      "winddirection" => "东南"
      "windpower" => "≤3"
      "humidity" => "54"
      "reporttime" => "2023-04-07 15:47:17"
      "temperature_float" => "24.0"
      "humidity_float" => "54.0"
    ]
  ]
]

测试通过

编写友好的说明文档

一个优秀的扩展包应该包含友好的说明文档,所以发布前我们先来完善说明文档,开源项目一般是在项目根目录创建 README.md 作为使用文档,当然,如果你的项目过于复杂,那么你可能需要将文档放到项目 docs 文件夹下,我们这里主要介绍普通复杂度的扩展包文档的编写。

一份友好的文档应该包含下面这些信息:

  • 项目简介及创作动机
  • 项目维护、CI、依赖更新状态(如果有)
  • features & 适用人群
  • 运行的平台或硬件要求
  • 重要依赖
  • 如何安装与测试
  • 使用示例及文档地址
  • 贡献指南
  • License
  • 鸣谢
  • 其它特有的信息

如果 使用 docs 目录存放项目文档,可以选择 前端框架 VitePress , 也可以选择其他自己熟悉的方式,不限编程语言。

发布上线

创建 GitHub 代码仓库

  • 填写名称与描述

    名称: weather
    描述:天气 SDK

  • 提交扩展包代码

    git init                                                    # 初始化 git 项目
    git add -A                                                  # 添加全部文件到 git 
    git commit -am "first commit"                               # 提交添加的文件
    git remote add origin git@github.com:azhida/weather.git     # 设置远程地址
    git push -u origin master                                   # 将提交推送到远程仓库

提交到 Packagist

Composer 安装包都是从 Packagist 源读取信息的,所以我们需要去注册我们的扩展包,别人才能安装,如果你还没有 Packagist 账号,先注册一个,建议使用 GitHub 登录:

地址:https://packagist.org/

登录以后,点击顶部菜单栏 “Submit” 开始提交项目,填入我们 代码所在的 GitHub 的仓库 URL,然后点 "Check",然后提交即可:

我的代码仓库地址: https://github.com/azhida/weather.git

接着会到达项目主页,你会看到一条提示:

INFO

“This package is not auto-updated. Please set up the GitHub Service Hook for Packagist so that it gets updated whenever you push!”:

更新
新版 GitHub 已经不需要手动像下面这样注册 webhook 了,当然前提是你在 packgist.org 登录时授权即可。

这条提示的意思是说你的项目不会自动抓取更新,怎么理解呢?下面是原理说明:

💡原理说明:

当我们在 Packagist 提交项目的时候,它会抓取一遍项目信息,但是后续我们在修改代码的时候(包括推送代码、发布版本等操作),Packagist 不会知道这个变更,所以我们需要使用 GitHub 提供的通知服务,在我们对代码库做修改的时候,向 Packagist 发出一条通知,通知 Packagist 这个项目发生了更新,这时候 Packagist 才会触发更新任务,抓取新的版本信息,我们才能在最短时间内安装到刚发的版本。

启用项目的 Packagist 通知服务

获取 Packagist 的 API Token

访问你在 Packagist 的个人主页:packagist.org/profile/ ,点击 "Show API Token",复制 token 备用。

给项目代码库启用 Packagist 通知服务

回到我们的 GitHub 项目,点击项目的 Settings 菜单:

进到设置页以后选择左边的 “Webhooks” 菜单,点击 “Add webhook”,如图:

填写对应的内容:

  • Payload URL: https://packagist.org/api/github?username=Packagist 的用户名
  • Content type 选择为 application/json
  • Secret 填写为您刚刚复制的 token

然后提交即可。

我们再次有代码提交动作后,刷新 Packagist 的包页面就已经没有那条提示了。

那包已经上线成功了,如果你的网络没问题,过几分钟,应该就可以安装开发版了。

TIP

没有发布正式版之前,只能安装开发版,像下面这样在包名后面加上 dev-master。

sh
composer require azhida/weather::dev-master -vvv

正式包安装

sh
composer require azhida/weather -vvv

发布第一个版本

我们刚才安装了开发版,开发版意味着不稳定与不安全,线上项目肯定是要用稳定版的,所以,在我们测试完整后,我们将会发布一个正式版本,测试期间你可以发布 0.0.x 版本,小于 1 的版本在 Composer 中是有特殊处理的哦,这个要弄清楚,具体我就不介绍了,自己去看 Composer 文档

什么是版本号

我们的版本号通常建议遵循 语言化版本 ,语义化版本的规范在前面链接里有详细说明,主要为:

版本说明

版本格式:主版本号。次版本号。修订号,版本号递增规则如下:
主版本号:当你做了不兼容的 API 修改,
次版本号:当你做了向下兼容的功能性新增,
修订号:当你做了向下兼容的问题修正。
先行版本号及版本编译信息可以加到 “主版本号。次版本号。修订号” 的后面,作为延伸。

简单介绍就是,如果你现在的最新版本是 1.0.0 ,下面的动作的区别是:

  • 打补丁,修了一些小 bug,没做 API 修改,那么你应该发布 1.0.1,同理以后也是递增第三位。
  • 有一天网友在你的基础上提交了新功能,原来的 API 调用方式也没改变,这时候你应该发布 1.1.0 。
  • 一段时间以后,你心血来潮重构了你的扩展包,调用方式也发生了变化,也就是说安装了以前版本的是无法直接升级的,这时候你需要发布 2.0.0 了。

发布第一个版本

GitHub 项目主页点击 "releases" 进入到版本管理页面;

然后点击 "Create new release" 创建一个新的版本:

填写版本号、这次发版的标题、以及这次版本变化的内容描述,点击提交。

我们的第一个版本就发布完成了,过几分钟你就可以安装刚刚发布的 0.0.1 版本了:

sh
composer require azhida/weather -vvv

代码重构实践

todo ...

使用 GitHub Actions 做自动化测试

todo ...

使用 StyleCI 自动修复代码格式

StyleCI 它是一个自动检查并修复代码规范的工具,目前支持 PHP、JS、CSS。

原理与 Travis CI 一样,都是利用 webhook 代码变更通知来创建检查任务,然后检查代码规范生成报告,可选创建 PR 到你的代码仓库以修复格式。

登录 StyleCI

同样是支持 GitHub 登录的,所以在首页点击 Login 进入登录页,然后选择 GitHub 或者你的账号账号方式登录即可。

启用项目

登录成功以后,会进到列表页面。

同样在搜索框输入包名来启用,如果没有看到你的扩展包,则你需要按以下步骤同步一下:

  • Settings 按钮
  • Repos 按钮

然后应该可以看到仓库列表了,在点击按钮 ENABLE STYLECI

配置项目

启用后点击 “SHOW ANALYSES” 查看检查状态。

由于我们还没有配置过规范,所以没有检查报告,如下图点击设置按钮进入设置页。

代码规范

在配置框里输入 preset: symfony 简单的内置规范即可,symfony 是 StyleCI 按照 Symfony 框架规范配置的规则,我们也推荐你使用该规范:

记得点击保存。

文件头注释

文件头注释就是我们源代码里的头部注释,我们可以在配置里的 “Header Comment” 框里填写我们的文件头注释,一旦某文件缺少与其匹配的头注释检查将不能通过:

示例内容如下(请根据你的需求自行配置):

This file is part of the azhida/weather.

(c) azhida <i@azhida.me>

This source file is subject to the MIT license that is bundled
with this source code in the file LICENSE.

自动化模式

你可以根据你的喜好选择代码格式修复的自动化级别,默认级别为手动模式,也就是说你在查看完检查报告页点击 “CREATE FIX PR” 时才会向你的代码库创建 PR,各级别说明如下:

此项修改勾选即保存,不需要另外操作。

因为考虑到有的时候有特殊场景下它处理得不是很完美,我会选择第二档 “自动创建 PR”,然后我查看后再决定是否合并,请根据你的喜好来选择就好。

然后我们回到项目主页,点击 “ANALYZE NOW”,手动触发一个检查任务。

稍等一会儿,我们会看到检查报告。

可以看到检查并没通过,我们点击 “SHOW DETAILS” 查看报告。

原来是头注释不匹配,这时候可以点击 “CREATE FIX PR” 来向我们的代码库提交一个 PR,然后回到 GitHub 代码库,就会发现存在一条 PR 了,合并即可。

点击 “Merge pull request” 合并即可修复代码格式。

测试自动化触发 PR

我们现在故意将代码改得不规范,以测试刚才我们的自动化级别选择(自动创建 PR)是否生效:

src/Weather.php

php
    .
    .
    .
    // 我们将参数间的空格去掉,并且将 $url 定义放到了 } 同一行
    public function getWeather($location,$format = 'json',$coordType = null)
    {$url = 'http://api.map.baidu.com/telematics/v3/weather';  
    .
    .
    .

提交代码:

sh
git commit -am "Test StyleCI."
git pull -r  # 拉取并合并远程修改(刚才合并了一个 PR)
git push

点击 “Merge pull request” 合并即可。

StyleCI 是按照我们指定的规范来检查的,那这些规范我从哪里知道呢?所有你的疑问都可以从 官方文档 找到答案。

todo ...