本文目录导读:

- 方案一:使用 Web3.php (最推荐)
- 方案二:使用 ethrpc 轻量库
- 方案三:使用 Node.js 中间层(混合架构)
- 关键注意事项
- 完整代码示例(Web3.php 转账 ERC20)
- 总结:如何选择?
在PHP项目中实现智能合约交互,核心思路是通过JSON-RPC协议与区块链节点(如以太坊节点)通信,PHP本身没有原生的合约调用能力,通常需要借助第三方库。
以下是几种主流实现方案及完整步骤:
使用 Web3.php (最推荐)
Web3.php 是最流行的 PHP 以太坊交互库,功能完善、社区活跃。
安装
composer require web3/web3
连接以太坊节点
use Web3\Web3;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
// 连接到本地或远程节点(如 Infura、Alchemy)
$web3 = new Web3(new HttpProvider(
new HttpRequestManager('https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID', 30)
));
// 测试连接
$web3->clientVersion(function ($err, $version) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Connected to: ' . $version;
});
调用合约方法(只读 - call)
use Web3\Contract;
// 合约地址和 ABI
$contractAddress = '0x1234...';
$contractABI = '[{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"type":"function"}]';
// 实例化合约
$contract = new Contract($web3->provider, $contractABI);
$contract->at($contractAddress);
// 调用 totalSupply() 函数
$contract->call('totalSupply', function ($err, $result) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Total Supply: ' . $result[0]->toString(); // 转为可读数字
});
调用合约方法(写入 - send)
use Web3\Utils;
use Web3\Personal;
// 解锁账户(或使用外部签名)
$personal = new Personal($web3->provider);
$personal->unlockAccount('0xYOUR_ADDRESS', 'your_password', 300, function ($err, $result) {
// ...
});
// 发送交易(转移代币示例)
$contract->send('transfer', [
'from' => '0xYOUR_ADDRESS',
'gas' => '0x200000',
'value' => '0x0'
], '0xRECEIVER', 1000 * 1e18, function ($err, $txHash) {
if ($err !== null) {
echo 'Error: ' . $err->getMessage();
return;
}
echo 'Transaction sent, hash: ' . $txHash;
});
使用 ethrpc 轻量库
如果你只需要基础的 RPC 调用(不想引入大库),可以用 ethrpc-php。
安装
composer require sabre-io/ethrpc-php
直接调用
use Ethrpc\EthRPC;
$eth = new EthRPC('https://mainnet.infura.io/v3/YOUR_PROJECT_ID');
// 获取余额
$balance = $eth->eth_getBalance('0x...', 'latest');
echo hexdec($balance); // 返回 wei 单位余额
// 调用合约(需要手动构造 ABI 编码的 data)
$data = '0x18160ddd'; // 对应 totalSupply() 函数的 keccak256 前4字节
$result = $eth->eth_call([
'to' => '0xCONTRACT_ADDRESS',
'data' => $data
], 'latest');
echo hexdec($result);
缺点:需要自己处理 ABI 编码(推荐使用 keccak256 和 abi-encode 库)。
使用 Node.js 中间层(混合架构)
PHP 直接签名交易涉及私钥管理,安全性较低。更推荐的架构是 PHP 只负责业务逻辑,合约交互委托给 Node.js 服务。
架构流程
PHP (业务逻辑) → 发送HTTP请求 → Node.js (使用 ethers.js/web3.js) → 区块链
Node.js 中间服务示例(使用 Express + ethers.js)
const express = require('express');
const { ethers } = require('ethers');
const app = express();
app.use(express.json());
const provider = new ethers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_PROJECT_ID');
const wallet = new ethers.Wallet('PRIVATE_KEY', provider);
const contract = new ethers.Contract('0x...', ABI, wallet);
app.post('/call', async (req, res) => {
const { method, params } = req.body;
try {
const result = await contract[method](...params);
res.json({ success: true, data: result.toString() });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000);
PHP 调用
$response = Http::post('http://node-service:3000/call', [
'method' => 'totalSupply',
'params' => []
]);
$data = $response->json();
关键注意事项
-
私钥管理
- 绝对不要在 PHP 代码中硬编码私钥
- 建议使用环境变量、HSM(硬件安全模块)或外部签名服务
- 生产环境应使用 Node.js/Go 中间层 存储密钥
-
Gas 估算
- 写入操作前一定要用
eth_estimateGas估算 Gas - 避免 Gas 不足导致交易失败或浪费
- 写入操作前一定要用
-
ABI 编码
- 合约函数调用需要将参数按 ABI 规范编码
- Web3.php 自动处理,其他库可能需要手动编码
-
错误处理
- 区块链交易是异步的,写入操作会立即返回哈希
- 需要额外查询交易收据确认是否成功
-
单位转换
- 以太坊最小单位是 wei (10^18)
- 使用
Utils::toWei()/Utils::toEther()转换
完整代码示例(Web3.php 转账 ERC20)
require 'vendor/autoload.php';
use Web3\Web3;
use Web3\Contract;
use Web3\Utils;
use Web3\Providers\HttpProvider;
use Web3\RequestManagers\HttpRequestManager;
$web3 = new Web3(new HttpProvider(
new HttpRequestManager('https://rinkeby.infura.io/v3/YOUR_PROJECT_ID')
));
$abi = '[{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"}]';
$contract = new Contract($web3->provider, $abi);
$contract->at('0xTOKEN_CONTRACT_ADDRESS');
// 查询余额
$address = '0xYOUR_WALLET_ADDRESS';
$contract->call('balanceOf', $address, function ($err, $result) use ($web3) {
if ($err) exit($err->getMessage());
$balance = $result[0]->toString();
echo "Balance: " . Utils::fromWei($balance, 'ether') . " TOKEN\n";
});
// 转账(使用预解锁账户)
$personal = new \Web3\Personal($web3->provider);
$personal->unlockAccount('0xYOUR_ADDRESS', 'password', 300);
$contract->send('transfer', [
'from' => '0xYOUR_ADDRESS',
'gas' => '0x200000'
], '0xRECEIVER_ADDRESS', 100, function ($err, $txHash) {
if ($err) exit($err->getMessage());
echo "TX Hash: $txHash\n";
});
如何选择?
| 方案 | 适用场景 | 复杂度 | 安全性 |
|---|---|---|---|
| Web3.php | 独立 PHP 项目,快速开发 | 中等 | 需自己管私钥 |
| ethrpc-php | 轻量级、简单查询 | 低 | 同左 |
| Node.js 中间层 | 生产环境、高频交易 | 较高 | 高(密钥隔离) |
推荐路径:
- 学习/原型开发 → Web3.php
- 生产项目 → Node.js 中间层 + PHP 前端业务
对于生产级应用,建议使用 Node.js/Go 作为签名层,PHP 只负责业务编排和前端展示。