Flarum 二次开发项目配置指南

Flarum

技术

2023.05.11 更新: 本方案的复杂度仍较高,不建议使用,本文仅作存档 目前的最新方案已有更方便的 沉淀改动、打包部署 流程 可以参考:https://github.com/0xffff-one/flarum-0x

上篇文 我们介绍了适用 Flarum 的 Docker LNMP 环境的配置,本文将进一步从本地环境搭建、版本控制的设计的角度,介绍一套可操作的 Flarum 项目配置与二次开发迭代方案。

目前本文方案在 0xFFFF 社区 运行良好,它遵循了现有的 Composer 加载,版本管理的机制,可以跟踪本地的修改、随时切换官方分支或定制开发的分支,避免那些诸如定制修改丢失、与最新版本代码难以同步等问题。若你想用 Flarum 搭建论坛并想对它进行二次开发调整,不妨试试本文方案。

本文方案的前置技能要求:

  • Unix / Linux 命令行操作
  • 对 Composer 基础认识
  • 熟练的 Git 操作(基本操作、分支、远程仓库、子模块等概念)

注:本文基于当前最新的 Beta 13 版本 展开,列出的脚本与命令,需要在 Unix 命令行环境运行(Linux 或 macOS,Windows 最好安装 WSL 以及 Docker)。

基础

Flarum 项目结构

我们用官方指定的 composer 安装方式 安装 Flarum:

composer create-project flarum/flarum . --stability=beta

一切安装完成后,可得到如下目录结构(主要文件及介绍如下):

.
├── public         # 网站的根目录
│   ├── assets     # 资源文件路径
│   └── index.php  # 网站动态脚本的入口
├── storage        # 程序运行时的 session、缓存
├── vendor         # Composer 管理的第三方依赖库
├── CHANGELOG.md
├── LICENSE
├── README.md
├── composer.json  # Composer 描述文件
├── composer.lock
├── config.php     # 安装后生成的站点配置
├── extend.php     # 预留的扩展代码入口
├── flarum         # 命令行执行的程序
└── site.php       # 引入 Composer 的自动加载依赖的逻辑

我们看到的部分,只是一个脚手架。Flarum 所有的模块,包括核心 flarum/core、所有的扩展(包括官方维护与第三方维护的扩展),均通过依赖管理器 Composer 以第三方模块依赖的形式加载至 vendor 目录中。

Composer

Flarum 重度依赖 Composer 提供的依赖管理机制。这里也简单地梳理一下 Composer 是什么东西。

过去 PHP 刀耕火种年代,并不存在依赖管理的机制。开发者在使用第三方包,常常都是直接复制粘贴,没有统一标准,人们常常都在重复造轮子,搞得十分混乱,进一步来说也制约着 PHP 社区的发展壮大。

后来在社区的驱动下,有了 Composer,借鉴了 NPM 与 Gem Bundler,设计了 PHP 语言下的依赖加载机制。Composer 本身也是一个 PHP 脚本,可以直接在官网下载安装,在命令行运行。

使用 Composer 管理依赖的项目,会有一个依赖描述文件 composer.json。在 composer 安装后,会生成一个锁定依赖版本的 composer.lock,以及保存 Composer 配置的依赖包的 vendor 目录。

在项目入口的 PHP,需引入 Composer 根据依赖生成的自动加载配置文件 vendor/autoload.php,这一文件根据 PSR-4 约定的自动加载标准,使用 PHP 提供的 spl_autoload_register 函数注册自动加载器,处理一系列的依赖加载的问题。引入这个配置文件后,在使用类或函数时,PHP 会自动把需要的依赖加载进来。

Flarum 脚手架在 site.php 引入了 Composer 的 vendor/autoload.php,接下来所有的逻辑与扩展,均通过 Composer 提供的自动加载器加载。

正确加载本地修改

了解了 Composer 的依赖管理与自动加载机制后,我们可以发现,vendor 本质上是应全权交给 Composer 管理的。我们对 vendor 目录内文件的任何手动修改都会是不可控因素。它们有可能在下一次调用 Composer 时就会被覆盖,要想不产生影响,除非接下来永远不再使用 Composer 更新。

所以说,要实现本地定制 Flarum 的关键一步是,在保证 vendor 目录完全交由 Composer 控制的前提下,把网站所有的定制部分放在 vendor 之外。借助操作系统的符号链接,我们可以实现这个目标,恰好 Composer 本身提供了这样自动创建符号链接的机制,接下来详细介绍。

操作方法

参考 Flarum 的插件扩展文档,它提供了一种本地开发时候加载模块的方式,利用 Composer 的 "path repository"

约定 Flarum 本地的扩展包都放在 packages/ 下,运行:

composer config repositories.0 path "packages/*"

此时的 composer.json 会多出一项配置:

"repositories": [{
    "type": "path",
    "url": "packages/*"
}]

也就是说,Flarum 在收到 HTTP 请求启动时,触发自动加载搜索模块的时候,也会搜索 packages/ 下的各个目录。

我们以自定义 flarum/core 为例,在本地创建 packages 目录,然后将自定义的包拷贝到 packages/ 下,然后将 composer.json 定义的版本改为 dev-master(dev-分支名),参考如下方式修改:

"flarum/auth-facebook": "^0.1.0",
"flarum/auth-github": "^0.1.0",
"flarum/auth-twitter": "^0.1.0",
"flarum/bbcode": "^0.1.0",
"flarum/core": "dev-master",
"flarum/emoji": "^0.1.0",
"flarum/flags": "^0.1.0",
"flarum/lang-english": "^0.1.0",
"flarum/likes": "^0.1.0",

然后运行 composer update flarum/core,可以看到 composer 移除了原位置(vendor/flarum/core)的源码,创建了一个符号链接,把 vendor/flarum/core 指向了本地的 packages/core。实现本地的修改的替换加载。

$ composer update flarum/core
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 0 installs, 1 update, 0 removals
  - Removing flarum/core (v0.1.0-beta.13)
  - Installing flarum/core (dev-master): Symlinking from packages/core
Writing lock file
Generating autoload files

所有需要自定义修改的模块,均需要用这种方式从 vendor 中移出来,再做进一步工作。为避免多个插件的修改造成混乱,代码的修改版本控制,接下来介绍版本控制的实践。

版本控制方案

这里我们用 Git 与 Git Submodule 实现网站与各个模块的版本控制。网站主体在一个 Git 仓库,所有要修改的子模块,也加入各自单独的仓库,以 submodule 的形式嵌入到网站主体的 Git 仓库中。

网站主体

以上面的脚手架为例,我们首先网站主体的目录中,初始化一个新的 Git 仓库:

git init

接下来创建 .gitignore,内容如下(参考这里):

.DS_Store
Thumbs.db
.vagrant
.idea
.vscode
vendor
config.php
studio.json

这里有一个问题,用 composer create-project 创建的项目脚手架,把所有的 .gitignore 都干掉了,导致 git init 以后 storage 内的文件都加入了 Git 的控制范围,所以我们需要把 storage 内各个目录该有的 .gitignore 补齐(参考 官方仓库的结构)。

一键操作:

for i in storage/*
do
  echo '*\n!.gitignore' > $i/.gitignore;
done

脚本自动创建各个 .gitignore 文件:

storage/cache/.gitignore
storage/formatter/.gitignore
storage/less/.gitignore
storage/locale/.gitignore
storage/logs/.gitignore
storage/sessions/.gitignore
storage/tmp/.gitignore
storage/views/.gitignore

public/assets 内也需补充 .gitignore

echo '*\n!avatars\n!.gitignore' > public/assets/.gitignore

这时候我们的脚手架已经准备好了。在继续之前,若拷贝了上一节提到的 packages/core,需要删除之前手动拷贝的 packages/core

rm -r packages/core

提交一个版本到本地分支:

git add .
git commit -m "Initial Commit"

到这里,网站最基础的脚手架就好了,关于远程仓库的安排,后文会提到。

添加待修改的模块

接下来我们以 submodule 的形式加入待修改的模块,同样以 flarum/core 为例。

首先用 submodule 添加 core 到 packages/core 目录:

git submodule add https://github.com/flarum/core.git ./packages/core

等待代码克隆,代码较多,速度可能较慢,可以考虑使用代理或镜像。

克隆完成后,运行 git status,可以发现产生了两个文件,packages/core 与 .gitmodules

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

        new file:   .gitmodules
        new file:   packages/core

由于目前的代码处于社区最新的提交。我们得先把 core 当下的代码 checkout 到我们使用的版本(截至本文写成还是 v0.1.0-beta.13,可以用 git tag 看到所有打了标签的版本号):

cd core
git checkout v0.1.0-beta.13

此时当下 core 的代码已经在 v0.1.0-beta.13 版本的状态下,会提示你目前在 detached HEAD 的状态,此时若直接提交,则会覆盖 master 分支。

$ git checkout v0.1.0-beta.13
Note: checking out 'v0.1.0-beta.13'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at fd371c12 Release v0.1.0-beta.13

所以这里最重要的一步,就是新建一个分支,让你接下来的修改都在这个分支进行。建议改一个与版本有关的分支名,方便后期追溯,比如说 beta.13 。如果是插件的话,也可以改成 dev (或者和你喜欢的名字)。

git checkout -b beta.13

这时候本地的 flarum/core 已经准备好了,在本地开发环境,单独针对 packages/core 这个仓库提交修改即可,提交的修改均位于 beta.13 分支。

这时候我们回到网站主体仓库,需要把 submodule 相关的信息提交一个版本(包括子模块信息的变化,子模块的 commit hash 等,可以用 git status 查看本地的变化)。

cd ../.. # 回到网站根目录
git add . # 添加到暂存区
git commit -m "add flarum/core"

对于插件的任何修改,我们都需要先在子模块提交版本更新,然后在外层的主体仓库再提交版本更新。

用 VSCode 的 Git 面板,可以很直观地看到主体仓库与子模块仓库的分支和提交情况。

1930641262.jpg1930641262.jpg

vscode-git-submodule.jpg

远程仓库配置

我们需要有个远程仓库跟踪本地的更改,submodule 也需要远程仓库的 URL,本小节主要介绍远程仓库的配置问题。涉及到定制的每一个模块,都需要在一个独立的仓库下跟踪版本的变化。

针对这样的情况,比较推荐创建一个独立的 GitHub 帐户(个人帐户或组织帐户都可以。相比于注册新的帐户来说,创建组织是最方便的),然后在这个帐户下统一托管所有模块(包括网站主体)的远程仓库。

创建了组织帐户以后,我们可以创建一批可用的 Git 仓库。private 或 public 均可(当然,这里还是鼓励大家开源的,所以尽可能选 public 吧)。我这里为了演示,创建以下仓库:

创建时不需要额外初始化这个仓库。

2448891704.jpg2448891704.jpg

Git 创建仓库

flarum-site

准备好仓库以后,我们先把网站主体的代码同步上来,首先 cd 到项目根目录,然后添加远程仓库,把本地分支推送到远端:

git remote add origin git@github.com:gq-flarum/flarum-site.git
git push -u origin master

这时候 远端 可以看到你推送的代码了,git push 的 -u 参数代表将当前的分支与正在推送的远端(origin)分支(master)绑定,以后只需要简单 git push 就可以推送更新。

flarum-core

接下来我们处理 flarum/core 的更新。首先进入 packages/core,同样的方式,将本地分支的代码推送至远程,由于在克隆时候已经用掉了 origin 这个代号,所以这个远程仓库我们取名为 orig2

cd packages/core
git remote add orig2 git@github.com:gq-flarum/flarum-core.git
git push -u orig2 beta.13

处理网站主体的仓库,把它的 submodule 的远程仓库改到新的仓库上,首先编辑项目根目录的 .gitmodules :

cd ../..
vim .gitmodules

把 .gitmodules 里面的 url 参数修改为新的地址,保存退出:

[submodule "packages/core"]
        path = packages/core
        url = git@github.com:gq-flarum/flarum-core.git

然后同步子模块的 URL 修改,再将修改提交至远程仓库:

git submodule sync --recursive
git add .
git commit -m "change submodule url"
git push

flarum-likes

继续按类似的方式添加需要修改的插件,这里我们以 flarum/likes 为例,这里放系列的命令:

# 克隆原项目
git submodule add https://github.com/flarum/likes.git packages/likes
cd packages/likes

# 切换分支
git checkout -b dev
git remote add orig2 git@github.com:gq-flarum/flarum-likes.git
git push -u orig2 dev

# 修改地址
vim .gitmodules
[submodule "packages/core"]
        path = packages/core
        url = git@github.com:gq-flarum/flarum-core.git
[submodule "packages/likes"]
        path = packages/likes
        url = git@github.com:gq-flarum/flarum-likes.git
# 修改 composer.json,将 flarum/likes 版本设置为 dev-master
vim composer.json
composer update flarum/likes

# 提交网站主体仓库所有修改
git add .
git commit -m "add flarum/likes"
git push

到这里也完成了 flarum/likes 模块的本地开发的 Git 配置。

有一点需要注意的是:所有的模块提交后要记得 push 到各自的远程仓库,保证本地的分支与远程同步,不然会因为代码缺失,影响其他人的协作开发与部署流程。

与上游同步

在这一系列机制下,我们定制的所有模块,都是在官方的分支提交下所分叉出的新的分支。因此,若上游有新的代码更新,我们只需要在本地同步上游的分支,然后再合并修改,再push 到我们的仓库中。

比如说我们定制了 flarum/core,假设官方 release 了 beta.14,需要做的事情也是以下简单几步:

git checkout master # 切到 master 分支
git pull # 拉取最新更新
git checkout v0.1.0-beta.14 # 切到 beta.14 的提交
git checkout -b beta.14 # 新建一个 beta.14 的分支
git merge beta.13 # 合并我以前在 beta.13 做过的魔改

最后一步有可能会产生冲突,解决冲突,保证代码工作正常,再提交即可。

同理,假设 flarum/like 上游有更新,我需要做的事情:

git checkout master
git pull
git checkout dev
git merge master

是的,就是这么简单。

部署

部署方面,简单地说,其实也就是 git pullcomposer install 一条龙操作,但由于涉及到多个子模块,所以和一般的 git pull 又不一样。

当然了,首先还是得让部署的主机有访问代码库的权限,可以创建一个专门用于部署的 GitHub 用户,然后服务器创建一对 ssh 密钥,把公钥绑定到这个用户的身份上。接下来就是一波简单操作。

第一次部署(可与 上一篇 介绍的 Docker 环境配合):

git clone git@github.com:gq-flarum/flarum-site.git
cd flarum-site/
git submodule update --init --recursive

# 安装依赖
composer install --ignore-platform-reqs
# 优化自动加载
composer dump-autoload -o

拉取最新提交:

git pull --recurse-submodules
git submodule update --init --recursive

composer i --ignore-platform-reqs
# 优化自动加载
composer dump-autoload -o

最后

本文演示用的代码仓库:https://github.com/gq-flarum/

Flarum 一切皆模块的设计,为版本控制也增加了许多复杂度,带来了不稳定性和较高的定制开发成本。借助 Git Submodule 可以很好地把控住这个局面,但 Git Submodule 本身也不是很容易上手。繁多的细节难以一篇文章充分传达。

折腾中有任何问题,欢迎留言讨论。也欢迎加入 University@FLARUM 讨论群组,将 Flarum 推广到更多的高校同学之中,让交流有更多的沉淀、专注与温度。