随着区块链技术的飞速发展,以太坊作为全球最大的智能合约平台,其应用场景日益广泛,将传统 Web 应用(基于 PHP 框架 ThinkPHP 开发)与以太坊区块链进行对接,已成为许多开发者探索的方向,本文将详细介绍如何使用 ThinkPHP 框架对接以太坊,实现与区块链网络的交互,如读取链上数据、发送交易、调用智能合约等功能。
为什么选择 ThinkPHP 对接以太坊
ThinkPHP 是一款国产的开源 PHP 框架,以其简洁、快速、灵活的特点深受国内开发者喜爱,它拥有完善的文档、活跃的社区和丰富的扩展,将 ThinkPHP 与以太坊对接,可以利用 ThinkPHP 强大的 MVC 架构、便捷的数据库操作和快速的开发能力,构建出功能完善的区块链应用前端或后端服务,可以开发一个基于以太坊的去中心化应用(DApp)的 Web 管理界面、实现用户资产查询、代币转账、NFT 展示等功能。
对接前的准备工作
在开始对接之前,我们需要准备以下环境和工具:
- PHP 环境:确保你的 PHP 版本满足以太坊库的要求(PHP 7.2 或更高版本)。
- ThinkPHP 框架:已安装并可以正常运行 ThinkPHP 项目。
- 以太坊节点:
- 本地节点:搭建自己的以太坊节点(如 Geth 或 Parity),提供完整的区块链数据,优点是数据私有、可控,缺点是同步节点耗时且消耗资源。
- 第三方 Infura 或 Alchemy 服务:这是更推荐的方式,尤其是对于开发和测试阶段,它们提供稳定的远程节点连接,无需自行维护节点,你需要注册获取一个项目 ID (Project ID)。
- Web3.php 库:这是 PHP 与以太坊交互的核心库,它封装了与以太坊节点通信的 JSON-RPC 接口,我们可以通过 Composer 来安装它。
- MetaMask 钱包(可选):用于测试交易和与 DApp 交互,但后端对接通常不直接依赖它,而是通过节点或服务。
核心步骤:ThinkPHP 对接以太坊
安装 Web3.php 库
在 ThinkPHP 项目的根目录下,通过 Composer 安装 web3.php 库:
composer require sc0vu/web3.php
安装完成后,vendor 目录下会包含该库的文件。
配置以太坊节点连接信息
为了方便管理和复用,我们可以在 ThinkPHP 的配置文件中(如 config/web3.php,如果不存在则新建)配置以太坊节点的 RPC 地址。
// config/web3.php
return [
'mainnet_rpc' => 'https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID', // 以太坊主网 RPC
'testnet_rpc' => 'https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID', // Sepolia 测试网 RPC
// 可以配置其他网络或私有链的 RPC
];
将 YOUR_INFURA_PROJECT_ID 替换为你从 Infura 或类似服务获取的实际 ID。
创建以太坊服务类(推荐)
为了遵循 ThinkPHP 的架构思想,我们可以创建一个服务类来封装所有与以太坊相关的交互逻辑,在 app/service 目录下创建 EthService.php。
// app/service/EthService.php
namespace app\service;
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
class EthService
{
protected $web3;
protected $eth;
public function __construct($rpcUrl)
{
$this->web3 = n
ew Web3(new HttpProvider(new HttpRequestManager($rpcUrl, 10)));
$this->eth = $this->web3->eth;
}
/**
* 获取最新区块号
*/
public function getLatestBlockNumber()
{
$this->eth->blockNumber(function ($err, $blockNumber) {
if ($err !== null) {
return 'Error: ' . $err->getMessage();
}
return $blockNumber->toString();
});
// 注意:这里需要处理异步回调,ThinkPHP 可以结合 Swoole 或使用 Promise 模式
// 为了简化示例,这里仅展示调用方式,实际项目中需妥善处理异步
// 更简单的方式是使用同步调用(web3.php 支持)或封装成 Promise
}
// 其他方法:获取账户余额、发送交易、调用智能合约等
}
注意:web3.php 默认使用异步回调,在 ThinkPHP 这种同步框架中直接使用可能会有些不便,可以考虑:
- 使用支持同步调用的 PHP Web3 库(如果存在)。
- 结合 Swoole 扩展的协程来处理异步回调。
- 封装返回 Promise 对象,在控制器中等待处理。
- 对于简单的读取操作,可以尝试同步方式(具体查看 web3.php 文档)。
在控制器中使用服务类
在控制器中,我们可以实例化 EthService 并调用其方法来与以太坊交互。
// app/controller/Index.php
namespace app\controller;
use app\BaseController;
use app\service\EthService;
class Index extends BaseController
{
public function index()
{
// 从配置中获取 RPC URL
$rpcUrl = config('web3.testnet_rpc'); // 使用测试网
// 实例化以太坊服务
$ethService = new EthService($rpcUrl);
// 示例:获取最新区块号(简化处理,实际需处理回调)
// 这里仅展示如何调用,实际项目中需要更完善的异步处理
$blockNumber = $ethService->getLatestBlockNumber();
// 假设 getLatestBlockNumber 已经修改为同步返回或通过 Promise 处理
// 如果是异步,这里可能需要特殊处理逻辑
return json(['block_number' => $blockNumber ?? '获取失败']);
}
/**
* 获取指定地址的 ETH 余额
*/
public function getBalance($address)
{
$rpcUrl = config('web3.testnet_rpc');
$ethService = new EthService($rpcUrl);
// 调用服务类中的获取余额方法(需在 EthService 中实现)
// $balance = $ethService->getBalance($address);
// return json(['address' => $address, 'balance' => $balance]);
// 简化示例,直接在控制器演示(不推荐,应放在服务类)
$web3 = new \Web3\Web3(new \Web3\Providers\HttpProvider(new \Web3\RequestManagers\HttpRequestManager($rpcUrl)));
$eth = $web3->eth;
$eth->getBalance($address, function ($err, $balance) {
if ($err !== null) {
return json(['error' => $err->getMessage()]);
}
$balanceInEth = $balance->toString() / 1e18; // 转换为 ETH 单位
return json(['address' => $address, 'balance' => $balanceInEth . ' ETH']);
});
// 注意:此处的异步回调在控制器中直接处理可能会使代码结构混乱
// 更推荐将回调逻辑封装在服务类中,并返回处理后的结果
}
}
智能合约交互
对接以太坊的重要场景之一是与智能合约交互,这通常需要:
- 智能合约 ABI (Application Binary Interface):合约的接口描述,是一段 JSON 格式的数据。
- 智能合约地址:部署到以太坊网络后的合约地址。
在 EthService 中添加调用合约的方法:
// app/service/EthService.php 中添加
use Web3\Contracts\Ethabi;
use Web3\Utils;
public function callContract($contractAddress, $abi, $functionName, $params = [])
{
$this->web3->getContract($abi, $contractAddress)->at($functionName, $params, function ($err, $result) {
if ($err !== null) {
return 'Error: ' . $err->getMessage();
}
// 处理 $result,根据函数返回类型解析
return $result->toString(); // 简化示例
});
}
在控制器中调用时,需要传入 ABI 和合约地址,并指定要调用的函数名和参数。
发送交易(如转账、调用合约写入函数)
发送交易需要使用账户的私钥进行签名。请务必妥善保管私钥,切勿硬编码在代码中或提交到版本库! 可以考虑使用环境变量或加密存储。
// app/service/EthService.php 中添加(简化示例)
public function sendTransaction($privateKey, $toAddress, $value)
{
$