Este guia mostra como construir um backend escalável de bilhética de eventos baseado em NFT em PHP usando o Symfony Messenger para gerir a latência da blockchain de forma segura e fiável.Este guia mostra como construir um backend escalável de bilhética de eventos baseado em NFT em PHP usando o Symfony Messenger para gerir a latência da blockchain de forma segura e fiável.

Construir um sistema descentralizado de bilheteira de eventos Web3 com Symfony 7.4

2025/12/22 01:43

A interseção da Web3 com frameworks web tradicionais é onde a utilidade do mundo real começa. Embora os ciclos de hype venham e vão, a utilidade dos Non-fungible tokens (NFT) para verificar a propriedade — especificamente na emissão de bilhetes para eventos — permanece um caso de uso sólido.

Neste artigo, vamos construir a espinha dorsal de um Sistema de Bilhetes para Eventos Descentralizado usando Symfony 7.4 e PHP 8.3. Vamos além dos tutoriais básicos e implementar uma arquitetura de nível de produção que lida com a natureza assíncrona das transações blockchain usando o componente Symfony Messenger.

A Arquitetura

Uma abordagem "Sénior" reconhece que o PHP não é um processo de longa duração como o Node.js. Portanto, não escutamos eventos blockchain em tempo real dentro de um controlador. Em vez disso, usamos uma abordagem híbrida:

  1. Interação Direta (Escrita): Usamos o Symfony Messenger para transferir transações de "Minting" para um worker, evitando timeouts HTTP.
  2. RPC Polling (Leitura): Usamos comandos agendados para verificar o estado on-chain.
  3. Contrato inteligente / Smart contract: Assumimos um contrato ERC-721 padrão implementado numa chain compatível com EVM (Ethereum, Polygon, Base).

Pré-requisitos & Stack

  • PHP: 8.3+
  • Symfony: 7.4 (LTS)
  • Blockchain Node: Infura, Alchemy, ou um nó Hardhat local.

Muitas bibliotecas PHP Web3 estão abandonadas ou mal tipadas. Embora web3p/web3.php seja a mais famosa, depender estritamente dela pode ser arriscado devido a lacunas de manutenção.

Para este guia, usaremos web3p/web3.php (versão ^0.3) para codificação ABI, mas aproveitar o HttpClient nativo do Symfony para o transporte JSON-RPC real. Isso dá-nos controlo total sobre timeouts, tentativas e logging — crítico para aplicações de produção.

Configuração do Projeto

Primeiro, vamos instalar as dependências. Precisamos do runtime Symfony, do cliente HTTP e da biblioteca 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

Certifique-se de que o seu composer.json reflete a estabilidade:

{ "require": { "php": ">=8.3", "symfony/http-client": "7.4.*", "symfony/messenger": "7.4.*", "symfony/uid": "7.4.*", "web3p/web3.php": "^0.3.0" } }

O Serviço Blockchain

Precisamos de um serviço robusto para comunicar com a blockchain. Vamos criar um EthereumService que envolve as chamadas 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; } }

Execute um teste local acedendo getTicketOwner com um ID mintado conhecido. Se obtiver um endereço 0x, a sua ligação RPC está a funcionar.

Minting Assíncrono com Messenger

As transações blockchain são lentas (15s a minutos). Nunca faça um utilizador esperar por uma confirmação de bloco num pedido de navegador. Usaremos o Symfony Messenger para lidar com isto em segundo plano.

A Mensagem

//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 ) {} }

O Handler

É aqui que a magia acontece. Usaremos o helper da biblioteca web3p/web3.php para assinar uma transação localmente.

Nota: Num ambiente de alta segurança, usaria um Key Management Service (KMS) ou um enclave de assinatura separado. Para este artigo, assinamos localmente.

//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; } } }

O Controlador

O controlador permanece simples. Aceita o pedido, valida a entrada, cria uma entidade de bilhete "Pendente" na sua base de dados (omitido por brevidade) e envia a mensagem.

//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); } }

Configuração & Guia de Estilo

Seguindo o estilo do Symfony 7.4, usamos tipagem estrita e atributos. Certifique-se de que o seu messenger.yaml está configurado para transporte assíncrono.

#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

Verificação

Para verificar se esta implementação funciona sem implementar na Mainnet:

Nó Local: Execute uma blockchain local usando Hardhat ou Anvil (Foundry).

npx hardhat node

Ambiente: Configure o seu .env.local para apontar para 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"

Consumir: Inicie o worker.

php bin/console messenger:consume async -vv

Pedido:

curl -X POST https://localhost:8000/api/v1/tickets/mint \ -H "Content-Type: application/json" \ -d '{"wallet_address": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"}'

Deve ver o worker processar a mensagem e, se implementou a lógica de assinatura de transação bruta completamente, um hash de transação aparecer na sua consola Hardhat.

Conclusão

Construir aplicações Web3 em PHP requer uma mudança de mentalidade. Não está apenas a construir uma aplicação CRUD; está a construir um orquestrador para estado descentralizado.

Ao usar o Symfony 7.4, aproveitamos:

  • HttpClient para comunicação RPC confiável e controlável.
  • Messenger para lidar com a realidade assíncrona das blockchains.
  • PHP 8.3 Attributes para código limpo e legível.

Esta arquitetura escala. Quer esteja a vender 10 bilhetes ou 10 000, a fila de mensagens atua como um buffer, garantindo que os nonces das suas transações não colidem e o seu servidor não trava.

Pronto para escalar a sua infraestrutura Web3?

Integrar blockchain requer precisão. Se precisar de ajuda a auditar as suas interações de contrato inteligente ou a escalar os seus consumidores de mensagens Symfony, vamos entrar em contacto.

\

Oportunidade de mercado
Logo de 4
Cotação 4 (4)
$0.02228
$0.02228$0.02228
+6.60%
USD
Gráfico de preço em tempo real de 4 (4)
Isenção de responsabilidade: Os artigos republicados neste site são provenientes de plataformas públicas e são fornecidos apenas para fins informativos. Eles não refletem necessariamente a opinião da MEXC. Todos os direitos permanecem com os autores originais. Se você acredita que algum conteúdo infringe direitos de terceiros, entre em contato pelo e-mail service@support.mexc.com para solicitar a remoção. A MEXC não oferece garantias quanto à precisão, integridade ou atualidade das informações e não se responsabiliza por quaisquer ações tomadas com base no conteúdo fornecido. O conteúdo não constitui aconselhamento financeiro, jurídico ou profissional, nem deve ser considerado uma recomendação ou endosso por parte da MEXC.