我们用于项目增强有两种方式:
PHP 原生扩展
PHP 原生扩展(PHP Native Extension),我们通常指基于 C/C++ 语言开发的对 PHP 语言的扩展,需要编译安装,比如我们最常使用的 phpredis、GD、MySQL、cURL 扩展等,这里有一个 PHP 的扩展列表。
PHP 扩展包
PHP 扩展包(PHP Package)或者 PHP 包,我们通常指用 PHP 代码编写的代码包。它通常是一些特定功能的封装,比如 Intervention/image
,它是基于 PHP 图像处理库 GD/Imagick
实现的图像处理功能包,具有非常强大且优雅的图片处理 API,我们可以非常便捷的基于它完成常规的图片操作,简化开发工作。
本文要讨论的就是 PHP 扩展包的开发,而不是 PHP 扩展。
在制作php扩展包之前,需要对php的包管理工具composer有一定了解。简单来说,composer是php的包管理工具,可以类比于其他语言来理解它的作用:
语言 | 包管理工具 |
---|---|
PHP | composer |
NodeJs | npm |
Java | maven |
Go | govendor |
Composer 主要由三个部分组成:命令行工具、包仓库、代码库:
包仓库(Packagist)
它是官方仓库,也就是我们平常说的 Composer 源,它的作用是存储这些包的信息,版本,代码来源,依赖,作者,主页等信息。官网是 packagist.org, 你也可以将自己的包发布在上面,这样 Composer 工具就能搜索与安装你的包了。
代码仓库(Repository)
代码仓库,Packagist 支持公开与私有仓库,通常是 GitHub 作为代码仓库,当然也可以是 BitBucket 或者 GitLab。
本地存储路径(Vendor directory)
我们的 Composer 依赖包都统一安装在项目的 vendor
目录下,其中还有 vendor/composer
目录用于存储依赖包的一些基本信息,比如命名空间等。
我们安装依赖的时候,composer 命令行工具会向包仓库发起请求,请求需要安装的包以及它依赖包的信息,它的依赖,版本等,然后在本地检查依赖关系,检查完毕后根据包信息里的代码库地址(或者压缩包地址)进行下载,下载到本地后安装到 vendor
目录。
Composer 命令在本文中不做赘述,相信大家都已熟练掌握,如有需要可查阅我的另一篇文章 Composer 基本命令介绍,更多指令可查阅 官方文档。
此章节将以 laravel-onelogin 项目为例穿插讲解
写代码我们习惯按照一定的规律安排目录结构,规范的目录结构有助于我们扩展项目,也有助于他人阅读。
比如我们按这种结构设计项目:
your-composer-package/
├── .editorconfig # 编辑器配置文件,比如缩进大小、换行模式等
├── .gitattributes # git 配置文件,可以设计导出时忽略文件等
├── .gitignore # git 忽略文件配置列表
├── .php_cs # PHP-CS-Fixer 配置文件
├── README.md # 说明文档
├── composer.json # composer依赖文件
├── phpunit.xml.dist # 单元测试xml文件
├── src # 源代码目录
│ └── .gitkeep
└── tests # 单元测试代码目录
└── .gitkeep
src 目录
通常我们将源代码放置到此目录下,文件名与类命名遵循驼峰命名法,目录与命名空间一致。
注意:我们命名空间通常是按包名来的,然后 src 映射到驼峰写法的命名空间,代码组织结构请符合 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 配置来编排测试套件》- P…。
.gitkeep
如果一个目录为空,我们是无法纳入到版本控制中的,所以我们创建了一个随意命名(最好还是按业界通用做法命名为 .gitkeep)的隐藏文件来保证 目录不为空。
下面介绍几种初始化composer包的方式:
composer init
composer init 指令可以帮助我们初始化生成 composer.json
文件。
$ composer init
Welcome to the Composer config generator
This command will guide you through creating your composer.json config.
Package name (/) [wyq/test]:
Description []: test
Author [wyq , n to skip]:
Minimum Stability []:
Package Type (e.g. library, project, metapackage, composer-plugin) []: library
License []: mit
Define your dependencies.
Would you like to define your dependencies (require) interactively [yes]? yes
Search for a package:
Would you like to define your dev dependencies (require-dev) interactively [yes]? no
{
"name": "wyq/test",
"description": "test",
"type": "library",
"license": "mit",
"authors": [
{
"name": "wyq",
"email": "[email protected]"
}
],
"require": {}
}
Do you confirm generation [yes]?
指引流程完成后当前文件夹会增加 composer.json
文件。其他目录结构或文件需要手动创建。
package-builder 工具
我们在开发过程中每次都去建立这个目录会比较麻烦,我们可以使用包结构生成工具来完成这些基础工作:overtrue/package-builder
安装:
composer global require "overtrue/package-builder" --prefer-source
使用:
package-builder build [目标目录]
Bootpack 工具
Bootpack 是 Laravel 5 包启动器,它会生成更详细的与Laravel结构相似的目录结构:
packages/LaravelNews
└── example
├── composer.json
├── LICENSE
├── README.md
└── src
├── Assets
│ └── README.md
├── Classes
│ ├── ExampleClass.php
│ └── README.md
├── Commands
│ ├── ExampleCommand.php
│ └── README.md
├── Config
│ └── example.php
├── Contracts
│ ├── ExampleContract.php
│ └── README.md
├── Controllers
│ ├── ExampleController.php
│ └── README.md
├── ExampleServiceProvider.php
├── Middleware
│ ├── ExampleMiddleware.php
│ └── README.md
├── Migrations
│ ├── 2017_08_11_171401_create_Example_table.php
│ └── README.md
├── Routes
│ ├── api.php
│ ├── README.md
│ └── web.php
├── Translations
│ ├── en
│ │ └── basic.php
│ └── README.md
└── Views
├── README.md
└── sample.blade.php
在此不做过多介绍,可查阅 bootpack 仓库 来获取更多关于 Bootpack 的信息。
简单来说,我们只需要像平时封装service类一样,编写代码,将功能封装起来即可。composer包实际上相当于提供了自动加载和方便版本控制,结合代码仓库方便托管和共享,让我们在不同项目中复用代码时,不用再手动粘贴文件,让我门的代码能够以更规范且方便的形式提供给他人使用。
【此章节主要以线下分享、演示为主】
在 Laravel 应用的 config/app.php
配置文件中,providers
选项定义了能够被 Laravel 加载的服务提供者列表。当有人安装你的扩展包时,你需要将你的服务提供者包含到这个列表中。你可以将服务提供者定义到扩展包的 composer.json 文件中的 extra 部分,而不是让用户手动将你的服务提供者添加到列表中。
"extra": {
"laravel": {
"providers": [
"Wyq\\Onelogin\\Providers\\OneLoginServiceProvider"
]
}
}
扩展阅读:Laravel 自动发现扩展包是怎样实现的
服务提供者负责将一些东西绑定到 Laravel 的 服务容器 中,并且告诉 Laravel 从哪里加载扩展包的资源文件,例如路由、配置文件、视图、语言包等。
以 wyq/laravel-onelogin
包为例,我们在composer的extra项中设置了 OneLoginServiceProvider
服务提供者,让他在自动加载时被发现,之后被框架注册并执行。在 register
方法中,我们让默认配置被加载到框架中。在 boot
方法中,我们让路由自动注册到框架中,并且提供了配置发布功能:
class OneLoginServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
// 让i1默认配置合并到框架中
$this->mergeConfigFrom(
__DIR__.'/../config/i1.php', 'i1'
);
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//自动注册路由
$this->loadRoutesFrom(__DIR__.'/../routes.php');
//发布配置
$this->publishes([
__DIR__.'/../config/i1.php' => config_path('i1.php'),
], 'config');
}
}
上边的示例只用到了 config、route 资源的注册,此外还提供了数据库迁移、语言包、视图、命令、公共资源文件、发布群组文件等,他们的使用方式都是相似的,都十分简单。
更多关于laravel提供的资源文件注册方式,可以查阅官方文档:扩展包开发
当我们PHP扩展包完成后,就可以尝试发布上线,在其他项目中使用它。
你可以选择上传到github,或者其他git仓库托管平台,如gitee、coding、gitlab等。但是值得注意的是:composer官方仓库 Packagist 是以github作为代码仓库的。也就是说,如果你选择github托管代码,可以很方便创建composer仓库。一般情况下,开源项目我们强烈推荐选择github,配合Packagist发布自己的扩展包,但如果不希望代码外泄,则需要使用私有git仓库,或者自建composer仓库,然后通过配置repository来获取你的私有扩展包。
首先,我们来介绍一般场景下开源项目扩展包的发布流程:
创建github代码仓库
此步骤不做描述
提交到 Packagist
Composer 安装包都是从 Packagist 源读取信息的,所以我们需要去注册我们的扩展包,别人才能安装,如果你还没有 Packagist 账号,先注册一个,建议使用 GitHub 登录:
登录以后,点击顶部菜单栏 “Submit” 开始提交项目,填入我们 代码所在的 GitHub 的仓库 URL,然后点 “Check”,然后提交即可:
新版 GitHub 已经不需要手动注册 webhook 了,当然前提是你在 packgist.org 使用github账号登录授权。
//@TODO 由于写此文时主要是私有包仓库建设,所以关于github的部分没有实际验证,如有问题后续会补充。
在项目中使用
在packgist.org发布后,直接在项目中require即可:
composer require wyq/laravel-onelogin
如果我们不想让所有人都能使用我们的扩展包,则需要使用私有仓库,让拥有私有仓库访问权限的人才能正常获取扩展包:
创建私有git仓库
此过程不做描述
对composer.json文件增加repository
"repositories": [
{
"type": "vcs",
"url": "[email protected]:wyq/laravel-onelogin.git"
}
]
其中 repositories 可以是数组,也可以命名个key,如下:
"repositories": {
"laravel-onelogin": {
"type": "vcs",
"url": "[email protected]:wyq/laravel-onelogin.git"
}
}
需要注意的是,repository 支持多种 type 类型,上文使用的 vcs
即 version control system,下文会介绍另一种type:composer
类型,完整信息可见官方文档 Repositores 部分
在项目中使用
当我们添加了新的repository之后,composer在官方仓库找不到对应的扩展包时,会继续依次使用配置的源来寻找扩展包,因此我们依然是直接执行 require
指令即可:
composer require wyq/laravel-onelogin
上边我们介绍了私有扩展包如何使用,但是我们使用vcs仓库时会面临一个问题:每增加一个扩展包,就要增加一条repositries的仓库。
这时,我们可以通过搭建包仓库来管理扩展包。这里我们可以选择satis或toran他们都是官方提供。
Satis 是开源的,它只是一个静态的 Composer 储存库生成器。它有点类似于一个超轻量的 packagist , 可以用于托管公司的私有 packages 或者 保存自己的私有 packages 。可以从 GitHub 下载 或者 在命令行运行以下命令:
php composer.phar create-project composer/satis --stability=dev --keep-vcs
创建完成后,如果目录中没有satis.json文件,则需要手动创建:
{
"name": "huanqiu/repository",
"homepage": "http://repository.demo.com",
"repositories": [
{
"type": "vcs",
"url": "[email protected]/wyq/laravel-onelogin.git"
}
],
"require": {
"wyq/laravel-onelogin": "*"
}
}
更完整的配置示例:
{
"name": "Easy Repository",
"homepage": "http://packagist.satis.cc",
"repositories": [
{"type": "vcs","url": "https://gitlab.local.com/aBigPackage/helloWorld"},
{"type": "composer", "url": "https://packagist.laravel-china.org"}
],
"archive": {
"directory": "dist",
"format": "tar",
"skip-dev": true,
"prefix-url": "http://packagist.satis.cc"
},
"abandoned":{
"lastcraft/simpletest" : "simpletest/simpletest"
},
"require":{
"monolog/monolog": "*",
"darkaonline/l5-swagger": "~5.4",
"laravel/laravel":"~5.4",
"league/flysystem-aws-s3-v3":"*",
"zircote/swagger-php":"*",
"simpletest/simpletest":"*"
},
"require-all": false,
"require-dependencies": true,
"require-dev-dependencies": true
}
配置解释:
// Composer 私有源的名称,可随意
"name": "Easy Repository",
// 建立之后home页面的地址(用于查看这个源有哪些package)
"homepage": "http://packagist.satis.cc",
// 获取package的地址
/**
这里如果你是需要从私有的git源获取package的话,就参照如下这样写就可以了
(带.git和不带.git似乎都ok)
{"type": "vcs","url": "https://gitlab.local.com/aBigPackage/helloWorld"}
如果你是需要构建内网的源,且内外网分离的情况下从外网获取package到内网,就参照下面这样写就好了
(没有必要挨个去写需要引用的package的github地址)
{"type": "composer", "url": "https://packagist.laravel-china.org"}
*/
"repositories": [
{"type": "vcs","url": "https://gitlab.local.com/aBigPackage/helloWorld"},
{"type": "composer", "url": "https://packagist.laravel-china.org"}
],
//如果需要satis将package下载到本地,直接从本地拉取,则需要配置这一项
//(内网源必须配置此项)
"archive": {
"directory": "dist",
//tar or zip
"format": "tar",
//是否需要为分支创建下载(默认只对有tag的提交创建下载)
"skip-dev": false,
"prefix-url": "http://packagist.satis.cc"
},
// 被抛弃或替换的package
"abandoned":{
//true表示这个 package 真正的被抛弃
"lox/simpletest":true
//表示 lastcraft/simpletest 被 simpletest/simpletest 替换
"lastcraft/simpletest" : "simpletest/simpletest"
},
// 需要 satis 的全部的 package
"require":{
"monolog/monolog": "*",
"darkaonline/l5-swagger": "~5.4",
"laravel/laravel":"~5.4",
"league/flysystem-aws-s3-v3":"*",
"zircote/swagger-php":"*",
"simpletest/simpletest":"*"
},
//是否需要将配置的源的全部的package都拉取
"require-all": false,
//是否自动解决依赖
"require-dependencies": true
//是否自动解决dev依赖
"require-dev-dependencies": true
仓库构建:
// 全部需要的package重新检查更新并构建
// 强烈推荐追加 --skip-errors 参数,否则碰到某些已经被放弃的 package 会卡住构建
php bin/satis build satis.json public/ --skip-errors
// 仅仅重新检查更新并构建指定的几个包
php bin/satis build satis.json public/ A/package B/other-package
// 如果只想扫描单个存储库并更新其中找到的所有包,请将VCS存储库URL作为可选参数传递:
php bin/satis build --repository-url https://only.my/repo.git satis.json public/
当构建完成后,public目录下会生成package.json文件,记录所有扩展包的索引;include文件夹存储压缩包(如果设置了archive)
Toran-Proxy是一套相对完整的私有仓库管理和代理工具( https://toranproxy.com/ ),也是composer作者开发的,可靠性也有保证。相比Satis而言主要有以下几个优势:
关于toran的内容在此不做详细介绍。
向 composer.json 文件的 repositories 增加新的仓库源即可。
"repositories": [
{
"type": "composer",
"url": "https://repository.demo.com/composer"
}
]