Web3 dan rangka kerja web tradisional bersilang di mana utiliti dunia sebenar bermula. Walaupun kitaran gembar-gembur datang dan pergi, utiliti Token Tidak Boleh Fungible (NFT) untuk mengesahkan pemilikan — khususnya dalam tiket acara — kekal sebagai kes penggunaan yang kukuh.
Dalam artikel ini, kita akan membina tulang belakang Sistem Tiket Acara Terdesentralisasi menggunakan Symfony 7.4 dan PHP 8.3. Kita akan melangkaui tutorial asas dan melaksanakan seni bina gred pengeluaran yang mengendalikan sifat asinkronus transaksi blockchain menggunakan komponen Symfony Messenger.
Pendekatan "Senior" mengakui bahawa PHP bukanlah proses berjalan lama seperti Node.js. Oleh itu, kita tidak mendengar peristiwa blockchain dalam masa nyata dalam pengawal. Sebaliknya, kita menggunakan pendekatan hibrid:
Banyak perpustakaan PHP Web3 ditinggalkan atau ditaip dengan buruk. Walaupun web3p/web3.php adalah yang paling terkenal, bergantung sepenuhnya padanya boleh berisiko kerana jurang penyelenggaraan.
Untuk panduan ini, kita akan menggunakan web3p/web3.php (versi ^0.3) untuk pengekodan ABI tetapi akan memanfaatkan HttpClient asli Symfony untuk pengangkutan JSON-RPC sebenar. Ini memberi kita kawalan penuh ke atas tamat masa, percubaan semula dan pembalakan — kritikal untuk aplikasi pengeluaran.
Pertama, mari kita pasang kebergantungan. Kita memerlukan runtime Symfony, klien HTTP dan perpustakaan Web3.
composer create-project symfony/skeleton:"7.4.*" decentralized-ticketing cd decentralized-ticketing composer require symfony/http-client symfony/messenger symfony/uid web3p/web3.php
Pastikan composer.json anda mencerminkan kestabilan:
{ "require": { "php": ">=8.3", "symfony/http-client": "7.4.*", "symfony/messenger": "7.4.*", "symfony/uid": "7.4.*", "web3p/web3.php": "^0.3.0" } }
Kita memerlukan perkhidmatan yang kukuh untuk bercakap dengan blockchain. Kita akan mencipta EthereumService yang membungkus panggilan JSON-RPC.
//src/Service/Web3/EthereumService.php namespace App\Service\Web3; use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; use Web3\Utils; class EthereumService { private const JSON_RPC_VERSION = '2.0'; public function __construct( private HttpClientInterface $client, #[Autowire(env: 'BLOCKCHAIN_RPC_URL')] private string $rpcUrl, #[Autowire(env: 'SMART_CONTRACT_ADDRESS')] private string $contractAddress, #[Autowire(env: 'WALLET_PRIVATE_KEY')] private string $privateKey ) {} /** * Reads the owner of a specific Ticket ID (ERC-721 ownerOf). */ public function getTicketOwner(int $tokenId): ?string { // Function signature for ownerOf(uint256) is 0x6352211e // We pad the tokenId to 64 chars (32 bytes) $data = '0x6352211e' . str_pad(Utils::toHex($tokenId, true), 64, '0', STR_PAD_LEFT); $response = $this->callRpc('eth_call', [ [ 'to' => $this->contractAddress, 'data' => $data ], 'latest' ]); if (empty($response['result']) || $response['result'] === '0x') { return null; } // Decode the address (last 40 chars of the 64-char result) return '0x' . substr($response['result'], -40); } /** * Sends a raw JSON-RPC request using Symfony HttpClient. * This offers better observability than standard libraries. */ private function callRpc(string $method, array $params): array { $response = $this->client->request('POST', $this->rpcUrl, [ 'json' => [ 'jsonrpc' => self::JSON_RPC_VERSION, 'method' => $method, 'params' => $params, 'id' => random_int(1, 9999) ] ]); $data = $response->toArray(); if (isset($data['error'])) { throw new \RuntimeException('RPC Error: ' . $data['error']['message']); } return $data; } }
Jalankan ujian tempatan mengakses getTicketOwner dengan ID yang diketahui telah dicetak. Jika anda mendapat alamat 0x, sambungan RPC anda berfungsi.
Transaksi blockchain adalah perlahan (15 saat hingga minit). Jangan sekali-kali membuat pengguna menunggu pengesahan blok dalam permintaan pelayar. Kita akan menggunakan Symfony Messenger untuk mengendalikan ini di latar belakang.
//src/Message/MintTicketMessage.php: namespace App\Message; use Symfony\Component\Uid\Uuid; readonly class MintTicketMessage { public function __construct( public Uuid $ticketId, public string $userWalletAddress, public string $metadataUri ) {} }
Di sinilah keajaiban berlaku. Kita akan menggunakan pembantu perpustakaan web3p/web3.php untuk menandatangani transaksi secara tempatan.
Nota: Dalam persekitaran keselamatan tinggi, anda akan menggunakan Perkhidmatan Pengurusan Kunci (KMS) atau enklaf tandatangan berasingan. Untuk artikel ini, kita menandatangani secara tempatan.
//src/MessageHandler/MintTicketHandler.php namespace App\MessageHandler; use App\Message\MintTicketMessage; use App\Service\Web3\EthereumService; use Psr\Log\LoggerInterface; use Symfony\Component\Messenger\Attribute\AsMessageHandler; use Web3\Contract; use Web3\Providers\HttpProvider; use Web3\RequestManagers\HttpRequestManager; use Web3p\EthereumTx\Transaction; #[AsMessageHandler] class MintTicketHandler { public function __construct( private EthereumService $ethereumService, // Our custom service private LoggerInterface $logger, #[Autowire(env: 'BLOCKCHAIN_RPC_URL')] private string $rpcUrl, #[Autowire(env: 'WALLET_PRIVATE_KEY')] private string $privateKey, #[Autowire(env: 'SMART_CONTRACT_ADDRESS')] private string $contractAddress ) {} public function __invoke(MintTicketMessage $message): void { $this->logger->info("Starting mint process for Ticket {$message->ticketId}"); // 1. Prepare Transaction Data (mintTo function) // detailed implementation of raw transaction signing usually goes here. // For brevity, we simulate the logic flow: try { // Logic to get current nonce and gas price via EthereumService // $nonce = ... // $gasPrice = ... // Sign transaction offline to prevent key exposure over network // $tx = new Transaction([...]); // $signedTx = '0x' . $tx->sign($this->privateKey); // Broadcast // $txHash = $this->ethereumService->sendRawTransaction($signedTx); // In a real app, you would save $txHash to the database entity here $this->logger->info("Mint transaction broadcast successfully."); } catch (\Throwable $e) { $this->logger->error("Minting failed: " . $e->getMessage()); // Symfony Messenger will automatically retry based on config throw $e; } } }
Pengawal kekal nipis. Ia menerima permintaan, mengesahkan input, mencipta entiti tiket "Pending" dalam pangkalan data anda (ditinggalkan untuk ringkas) dan menghantar mesej.
//src/Controller/TicketController.php: namespace App\Controller; use App\Message\MintTicketMessage; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Messenger\MessageBusInterface; use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Uid\Uuid; #[Route('/api/v1/tickets')] class TicketController extends AbstractController { #[Route('/mint', methods: ['POST'])] public function mint(Request $request, MessageBusInterface $bus): JsonResponse { $payload = $request->getPayload(); $walletAddress = $payload->get('wallet_address'); // 1. Basic Validation if (!$walletAddress || !str_starts_with($walletAddress, '0x')) { return $this->json(['error' => 'Invalid wallet address'], 400); } // 2. Generate Internal ID $ticketId = Uuid::v7(); // 3. Dispatch Message (Fire and Forget) $bus->dispatch(new MintTicketMessage( $ticketId, $walletAddress, 'https://api.myapp.com/metadata/' . $ticketId->toRfc4122() )); // 4. Respond immediately return $this->json([ 'status' => 'processing', 'ticket_id' => $ticketId->toRfc4122(), 'message' => 'Minting request queued. Check status later.' ], 202); } }
Mengikuti gaya Symfony 7.4, kita menggunakan taip ketat dan atribut. Pastikan messenger.yaml anda dikonfigurasikan untuk pengangkutan async.
#config/packages/messenger.yaml: framework: messenger: transports: async: dsn: '%env(MESSENGER_TRANSPORT_DSN)%' retry_strategy: max_retries: 3 delay: 1000 multiplier: 2 routing: 'App\Message\MintTicketMessage': async
Untuk mengesahkan pelaksanaan ini berfungsi tanpa menggunakan ke Mainnet:
Nod Tempatan: Jalankan blockchain tempatan menggunakan Hardhat atau Anvil (Foundry).
npx hardhat node
Persekitaran: Tetapkan .env.local anda untuk menunjuk ke localhost.
BLOCKCHAIN_RPC_URL="http://127.0.0.1:8545" WALLET_PRIVATE_KEY="<one of the test keys provided by hardhat>" SMART_CONTRACT_ADDRESS="<deployed contract address>" MESSENGER_TRANSPORT_DSN="doctrine://default"
Gunakan: Mulakan pekerja.
php bin/console messenger:consume async -vv
Permintaan:
curl -X POST https://localhost:8000/api/v1/tickets/mint \ -H "Content-Type: application/json" \ -d '{"wallet_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}'
Anda sepatutnya melihat pekerja memproses mesej dan, jika anda melaksanakan logik tandatangan transaksi mentah sepenuhnya, hash transaksi muncul dalam konsol Hardhat anda.
Membina aplikasi Web3 dalam PHP memerlukan perubahan pemikiran. Anda bukan hanya membina aplikasi CRUD; anda membina orkestrator untuk keadaan terdesentralisasi.
Dengan menggunakan Symfony 7.4, kita memanfaatkan:
Seni bina ini berskala. Sama ada anda menjual 10 tiket atau 10,000, baris gilir mesej bertindak sebagai penampan, memastikan nonce transaksi anda tidak berlanggar dan pelayan anda tidak tergantung.
Mengintegrasikan blockchain memerlukan ketepatan. Jika anda memerlukan bantuan mengaudit interaksi kontrak pintar anda atau meningkatkan penggunaan mesej Symfony anda, mari kita berhubung.
\


