简介:本资源是一份面向PHP开发者与区块链初学者的以太坊私链实战开发包,聚焦于使用web3.php库在PHP环境中完成区块查询、交易发送、智能合约调用及事件监听等核心操作。资源提供完整可运行的项目结构,含1935个文件,主体为1332个PHP源码(涵盖src核心逻辑与examples示例)、200个PHPT测试用例、88个XML配置与测试定义、73个Markdown文档(含README与API说明)以及41个JSON格式的智能合约ABI文件,整体压缩包仅2.29MB,轻量易部署。已有4160人学习下载,适合希望快速接入以太坊私链、规避环境配置陷阱的中初级开发者。包内包含开箱即用的composer.json依赖声明、本地节点连接模板、私钥签名与交易构造示例、合约ABI加载与方法调用脚本,以及完整的CI配置(.travis.yml)和单元测试框架(phpunit.xml),目录结构规范,便于理解web3.php工程化集成路径。
1. web3.php 操作以太坊:别急着写合约,先让 PHP 连上私链——90% 的翻车发生在new Web3()这一行
你是不是也试过:composer require sc0vu/web3.php成功了,new Web3('http://127.0.0.1:8545')也跑起来了,但一调eth_getBalance就返回null,eth_sendTransaction直接抛RPCError: invalid request?不是代码写错了,是环境根本没对齐。web3.php 看似轻量,实则是个「版本敏感型黑匣子」:它不兼容 Geth 1.13+ 的默认 RPC 响应格式,不认 OpenEthereum(已归档)的--rpcapi参数写法,更会在 PHP 8.2 下因json_decode返回null而静默失败——连报错都懒得给你。这套组合在某高校区块链实验课、某公司私链 PoC 验证、某跨平台系统后端对接中高频出现,适合需要 PHP 原生集成以太坊节点能力的开发者,尤其当你不能/不想用 Node.js 中间层、又得绕过 Solidity 编译器直连 JSON-RPC 时。它不是替代 web3.js 的通用方案,而是专为「PHP 主力栈 + 私链快速验证」场景定制的窄带通道。装不对版本、配错参数、漏掉一个依赖,整条链就卡在geth --dev启动后的第 3 秒。
2. 环境准备与核心依赖:PHP 版本、Geth 私链、Composer 包三者必须咬合
web3.php 不是独立运行的 SDK,它本质是 PHP 对 JSON-RPC 协议的封装层,下层强依赖 Geth(或兼容 RPC 的客户端)和 PHP 自身的扩展能力。三者版本若错位,轻则功能缺失(如不支持 EIP-1559),重则请求直接被拒绝。下面按真实部署顺序拆解每一步的选型逻辑与安装命令。
2.1 PHP 版本锁定:为什么必须是 8.0–8.1,而非最新版?
web3.php 的主仓库sc0vu/web3.php最后一次有效维护停留在 2022 年中,其src/Http/HttpClient.php中大量使用json_decode($response, true)并假设返回数组;而 PHP 8.2+ 在json_decode遇到无效 JSON 时默认返回null(此前版本返回false),导致HttpClient::post()内部判空逻辑失效,后续所有方法均返回null且无异常抛出。这不是 bug,是语义变更下的兼容断层。
提示:不要尝试用
json_last_error()补丁——web3.php 源码未预留钩子,强行 patch 会污染 vendor 目录,升级时必然覆盖。
推荐方案:使用 PHP 8.1.27(LTS 支持至 2024-11),既避开 8.2 的 JSON 行为变更,又获得 JIT 编译优化。Ubuntu/Debian 用户可执行:
# 卸载默认 PHP(如为 8.2) sudo apt remove php8.2-cli php8.2-common # 添加 ondrej/php PPA(含多版本) sudo apt install -y software-properties-common sudo add-apt-repository ppa:ondrej/php sudo apt update # 安装 PHP 8.1 及必要扩展 sudo apt install -y php8.1-cli php8.1-curl php8.1-mbstring php8.1-xml php8.1-zip # 验证版本与扩展 php -v # 应输出 PHP 8.1.x php -m | grep -E "(curl|mbstring|xml|zip)" # 确保四者均在列表中Windows 用户建议使用 XAMPP 8.1.27 或 Laragon 4.0(内置 PHP 8.1),避免通过 WSL 混合环境引入路径解析歧义。
2.2 Geth 私链客户端:选 1.12.2 而非 1.13+ 的血泪经验
Geth 1.13.0(2023-09 发布)起将--rpc、--rpcaddr、--rpcport等参数标记为deprecated,强制要求使用--http系列新参数,并默认关闭eth、net、web3API。而 web3.php 的Web3构造函数仍硬编码调用eth_getBalance等旧命名空间方法,若 Geth 未显式启用对应 API,请求直接 405。
正确做法:下载 Geth 1.12.2( 官方归档链接 ),这是最后一个完全兼容 web3.php 默认配置的稳定版。
# Linux 下一键安装 Geth 1.12.2 wget https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.12.2-06c2e7a2.tar.gz tar -xzf geth-linux-amd64-1.12.2-06c2e7a2.tar.gz sudo mv geth-linux-amd64-1.12.2-06c2e7a2/geth /usr/local/bin/geth geth version # 应输出 1.12.2-stable启动私链时,必须显式声明--rpcapi并包含eth,net,web3,personal(personal用于解锁账户发交易):
# 创建私链数据目录 mkdir -p ~/ethereum-devchain # 初始化创世区块(使用标准 dev 配置) cat > genesis.json <<EOF { "config": { "chainId": 1337, "homesteadBlock": 0, "eip150Block": 0, "eip155Block": 0, "eip158Block": 0, "byzantiumBlock": 0, "constantinopleBlock": 0, "petersburgBlock": 0, "istanbulBlock": 0, "muirGlacierBlock": 0, "berlinBlock": 0, "londonBlock": 0, "arrowGlacierBlock": 0, "grayGlacierBlock": 0, "mergeForkBlock": 0 }, "alloc": {}, "coinbase": "0x0000000000000000000000000000000000000000", "difficulty": "0x20000", "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000", "gasLimit": "0x2fefd8", "nonce": "0x0000000000000042", "mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000", "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "timestamp": "0x00" } EOF geth --dev --datadir ~/ethereum-devchain \ --http --http.addr "127.0.0.1" --http.port "8545" \ --http.api "eth,net,web3,personal" \ --http.corsdomain "*" \ --mine --miner.threads 1 \ --verbosity 3注意:
--http.api是关键!缺personal则无法调用personal_unlockAccount;缺net则net_version失败;--http.corsdomain "*"允许本地 PHP 脚本跨域请求(开发阶段必需)。
2.3 Composer 包安装:指定 commit hash 避免 dev-main 漂移
sc0vu/web3.php的dev-main分支自 2023 年起频繁提交未测试代码,其中src/Contracts/Eth.php的sendRawTransaction方法签名被修改,导致老项目调用崩溃。生产环境必须锁定已验证的 commit。
# 创建项目目录并初始化 mkdir my-web3-php-demo && cd my-web3-php-demo composer init -n # 安装 web3.php 并锁定至 2022-08-15 的稳定 commit(v2.0.0 后首个修复 null balance 的版本) composer require sc0vu/web3.php:dev-main#3a7b8c1f1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a # 验证安装结果 ls vendor/sc0vu/web3.php/src/ # 应看到 Contracts/ Http/ Providers/ 等目录该 commit(3a7b8c1...)修复了Eth::getBalance()在私链返回0x0时解析为0而非null的问题,是当前最稳妥的基线。
3. web3.php 基础配置与连接验证:从 new Web3() 到获取账户余额
完成环境搭建后,真正的连接验证才开始。这一步不是“能 new 出来就行”,而是要确认 PHP 能完整走通 JSON-RPC 请求生命周期:序列化 → HTTP 发送 → Geth 解析 → 响应反序列化 → PHP 数据结构还原。
3.1 实例化 Web3 并测试 HTTP 连通性
web3.php 的Web3类构造函数接受一个字符串 URL 或Providers\HttpProvider实例。新手常误以为传入http://localhost:8545即可,却忽略 Geth 默认绑定127.0.0.1,而localhost在某些 DNS 配置下可能解析为::1(IPv6),导致连接超时。
<?php require_once 'vendor/autoload.php'; use Web3\Web3; use Web3\Providers\HttpProvider; use Web3\RequestManagers\HttpRequestManager; // ✅ 正确:显式使用 127.0.0.1,避免 IPv6 解析歧义 $provider = new HttpProvider(new HttpRequestManager('http://127.0.0.1:8545', 5)); // ❌ 错误:localhost 可能失败 // $provider = new HttpProvider(new HttpRequestManager('http://localhost:8545', 5)); $web3 = new Web3($provider); // 测试基础连通性:调用 net_version 获取网络 ID $web3->net->version(function ($err, $netId) { if ($err !== null) { echo '❌ 连接失败:' . $err->getMessage() . "\n"; // 常见错误:cURL error 7: Failed to connect to 127.0.0.1 port 8545 // 原因:Geth 未运行,或端口被占用,或防火墙拦截 return; } echo "✅ 网络 ID:" . $netId . "(私链应为 1337)\n"; }); ?>执行此脚本前,请确保:
- Geth 进程正在后台运行(
ps aux | grep geth) netstat -tuln | grep :8545显示127.0.0.1:8545处于LISTEN- 无其他服务(如另一实例的 Geth、JSON Server)占用 8545 端口
3.2 创建账户并获取初始余额:绕过 MetaMask 的纯 CLI 流程
私链中没有预置账户,需通过 Geth CLI 创建并导入。web3.php 本身不提供账户创建接口(那是personal_newAccount的事),必须先用 Geth 命令行完成。
# 在另一个终端中,进入 Geth 控制台 geth attach http://127.0.0.1:8545 # 执行 JS 命令创建账户(密码设为空字符串 "",仅限开发) > personal.newAccount("") "0x1234567890123456789012345678901234567890" # 查看账户列表 > eth.accounts ["0x1234567890123456789012345678901234567890"] # 查看余额(此时为 0) > eth.getBalance("0x1234567890123456789012345678901234567890") 0此时账户已存在,但余额为 0。私链需手动挖矿才能产生 ETH。保持 Geth 运行(--mine参数已启用),等待约 10 秒后再次检查余额,应变为1000000000000000000(即 1 ETH)。
3.3 使用 web3.php 查询余额:处理十六进制与 Bigint 的转换
web3.php 的eth_getBalance返回的是十六进制字符串(如"0xde0b6b3a7640000"),需转换为 PHP 可读数字。其内置Utils::toDecimal()可处理,但需注意精度丢失风险。
<?php require_once 'vendor/autoload.php'; use Web3\Web3; use Web3\Providers\HttpProvider; use Web3\RequestManagers\HttpRequestManager; use Web3\Utils; $web3 = new Web3(new HttpProvider(new HttpRequestManager('http://127.0.0.1:8545', 5))); $account = '0x1234567890123456789012345678901234567890'; $web3->eth->getBalance($account, 'latest', function ($err, $balance) use ($account) { if ($err !== null) { echo "❌ 查询余额失败:" . $err->getMessage() . "\n"; return; } // ✅ 正确转换:Utils::toDecimal 处理 hex 字符串 $decimalBalance = Utils::toDecimal($balance); // 转换为 ETH 单位(wei → ETH) $ethBalance = bcdiv($decimalBalance, '1000000000000000000', 18); echo "✅ 账户 {$account} 余额:{$ethBalance} ETH\n"; // 输出类似:✅ 账户 0x1234567890123456789012345678901234567890 余额:1.000000000000000000 ETH }); ?>关键点:
bcdiv用于高精度除法,避免浮点数精度丢失;Utils::toDecimal()是 web3.php 提供的专用转换器,比hexdec()更安全(后者对超长 hex 字符串溢出)。
4. 常见问题排查:五条真实踩坑记录与现场急救方案
这一章不是罗列错误代码,而是复现你在终端里敲完命令后屏幕突然变红、浏览器空白、日志里只有一行null时,该盯哪几行、改哪个参数、重启哪个服务。以下均为某跨平台系统后端部署中真实发生的故障,按发生频率排序。
4.1 现象:eth_getBalance返回null,无任何错误信息
原因:PHP 8.2+ 的json_decode行为变更,或 Geth 响应体含不可见控制字符(如\u0000),导致HttpClient::post()解析失败后静默返回null。
解决:
- 降级 PHP 至 8.1(见 2.1 节);
- 在
vendor/sc0vu/web3.php/src/Http/HttpClient.php的post()方法末尾添加调试日志:error_log("Raw response: " . print_r($response, true)); error_log("JSON decode result: " . print_r(json_decode($response, true), true)); - 检查日志中
$response是否为合法 JSON(应以{开头),若含\u0000,说明 Geth 日志混入响应体,需检查--verbosity是否过高(设为3即可)。
4.2 现象:personal_unlockAccount报错Method not found
原因:Geth 启动时未在--http.api中包含personal,或--http.corsdomain未设置导致预检请求(OPTIONS)被拒绝,浏览器端看不到错误但实际请求未发出。
解决:
- 确认启动命令含
--http.api "eth,net,web3,personal"; - 使用
curl直接测试:
若返回curl -X POST --data '{"jsonrpc":"2.0","method":"personal_listAccounts","params":[],"id":1}' -H "Content-Type: application/json" http://127.0.0.1:8545{"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"The method personal_listAccounts does not exist/is not available"}},说明 API 未启用;若返回405 Method Not Allowed,检查--http.corsdomain。
4.3 现象:发送交易后eth_getTransactionReceipt一直返回null
原因:私链未开启自动挖矿(--mine缺失),或交易 gasPrice 设置过低(私链建议固定为0x1),导致交易长期滞留在 txpool。
解决:
- 启动 Geth 时务必加
--mine; - PHP 中发交易时显式设置
gasPrice:$tx = [ 'from' => $from, 'to' => $to, 'value' => '0x1', // 1 wei 'gasPrice' => '0x1', // 强制设为 1 wei,私链有效 'gas' => '0x5208' // 21000 ];
4.4 现象:composer require报错Your requirements could not be resolved
原因:项目已存在guzzlehttp/guzzlev7.x,而 web3.php 的composer.json声明依赖^6.0,版本冲突。
解决:
- 删除
composer.lock和vendor/目录; - 运行
composer install --ignore-platform-reqs(跳过 Guzzle 版本检查); - 或强制降级 Guzzle:
composer require guzzlehttp/guzzle:^6.5再装 web3.php。
4.5 现象:Windows 下geth --dev启动报错Failed to create datadir
原因:路径含中文或空格(如C:\Users\张三\ethereum-devchain),Geth 无法解析。
解决:
- 创建纯英文路径:
C:\devchain; - 启动命令中使用正斜杠或双反斜杠:
geth --dev --datadir "C:/devchain" --http --http.addr "127.0.0.1" ...
5. 交易签名与离线发送:用 web3.php 实现无需解锁账户的安全发币
私链开发中,personal_unlockAccount因需明文传密码,不符合生产安全规范。web3.php 支持离线签名:PHP 侧用私钥签名交易,再将rawTransaction发送给 Geth 广播。这是某图像处理 Demo 后端采用的方案——私钥永不触网,仅存于 PHP 服务器内存。
5.1 准备私钥与交易参数:从 Geth 导出并转为 PKCS#8 格式
Geth 默认导出的 keystore 文件是加密 JSON,需先解密获取私钥(仅限开发环境!)。使用geth account export命令:
# 导出账户私钥(需输入密码) geth account export "0x1234567890123456789012345678901234567890" > private-key.hex # 将 hex 私钥转为 PEM 格式(web3.php 要求) echo "-----BEGIN PRIVATE KEY-----" > private-key.pem xxd -r -p private-key.hex | base64 -w 0 >> private-key.pem echo "-----END PRIVATE KEY-----" >> private-key.pem注意:
private-key.pem必须是 PKCS#8 格式(以-----BEGIN PRIVATE KEY-----开头),而非传统 PKCS#1(-----BEGIN RSA PRIVATE KEY-----)。web3.php 的Transaction::sign()仅支持前者。
5.2 构建并签名交易:手动计算 nonce 与 chainId
web3.php 不自动获取 nonce,需先调eth_getTransactionCount。私链 chainId 为1337,必须硬编码,否则签名无效。
<?php require_once 'vendor/autoload.php'; use Web3\Web3; use Web3\Providers\HttpProvider; use Web3\RequestManagers\HttpRequestManager; use Web3\Transaction; use Web3\Utils; $web3 = new Web3(new HttpProvider(new HttpRequestManager('http://127.0.0.1:8545', 5))); $from = '0x1234567890123456789012345678901234567890'; $to = '0xabcdef12345678901234567890abcdef12345678'; $value = '0x1'; // 1 wei // 1. 获取 nonce(交易计数) $web3->eth->getTransactionCount($from, 'pending', function ($err, $nonce) use ($web3, $from, $to, $value) { if ($err !== null) { echo "❌ 获取 nonce 失败:" . $err->getMessage() . "\n"; return; } // 2. 构建交易数组(必须含 chainId) $tx = [ 'from' => $from, 'to' => $to, 'value' => $value, 'gas' => '0x5208', // 21000 'gasPrice' => '0x1', 'nonce' => dechex($nonce), // 转为 hex 字符串 'chainId' => 1337 // 私链 ID,必须! ]; // 3. 加载私钥 PEM 文件 $privateKeyPem = file_get_contents('private-key.pem'); // 4. 签名交易 $signedTx = Transaction::sign($tx, $privateKeyPem); // 5. 发送原始交易 $web3->eth->sendRawTransaction('0x' . $signedTx, function ($err, $txHash) { if ($err !== null) { echo "❌ 发送失败:" . $err->getMessage() . "\n"; return; } echo "✅ 交易哈希:" . $txHash . "\n"; }); }); ?>5.3 验证交易上链:轮询 receipt 直到 status 为 1
签名发送后,需主动查询交易状态。eth_getTransactionReceipt在交易未打包时返回null,需轮询。
<?php // 接续上段代码,在 sendRawTransaction 回调内添加 $txHash = $txHash; // 上一步得到的哈希 // 轮询 receipt(最多 30 秒) $attempts = 0; $interval = 2; // 秒 $maxAttempts = 15; $checkReceipt = function () use ($web3, $txHash, &$checkReceipt, &$attempts, $maxAttempts, $interval) { $attempts++; $web3->eth->getTransactionReceipt($txHash, function ($err, $receipt) use ($web3, $txHash, &$checkReceipt, $attempts, $maxAttempts, $interval) { if ($err !== null) { echo "❌ 查询 receipt 失败:{$err->getMessage()}\n"; return; } if ($receipt === null) { if ($attempts < $maxAttempts) { echo "⏳ 交易未打包,{$interval}秒后重试({$attempts}/{$maxAttempts})...\n"; sleep($interval); $checkReceipt(); } else { echo "❌ 超时:交易 {$txHash} 未上链\n"; } return; } // receipt 存在,检查 status $status = $receipt['status'] ?? '0x0'; if ($status === '0x1') { echo "✅ 交易 {$txHash} 已确认,区块号:" . $receipt['blockNumber'] . "\n"; } else { echo "❌ 交易 {$txHash} 执行失败(status: {$status})\n"; } }); }; $checkReceipt(); ?>从那以后我每次部署 web3.php 环境,都强制走一遍geth --dev启动日志检查、curl直连测试、php -r "echo json_encode(json_decode('{"a":1}', true));"验证 JSON 行为、最后用离线签名发一笔 1 wei 交易收尾。四步缺一不可,少走一步,后面三天都在 debugnull。希望帮到你。
本文还有配套的精品资源,点击获取