<?php
// Classe Template - Define o fluxo padrão de processamento
abstract class ProcessadorNotaFiscal {
// TEMPLATE METHOD - Define a sequência de passos
public final function processarNotaFiscal(DadosNota $dados): string {
echo "=== Iniciando processamento da Nota Fiscal ===\n";
// 1. Validações básicas (método concreto - comum a todos)
if (!$this->validarDadosBasicos($dados)) {
return "Erro: Dados básicos inválidos";
}
// 2. Validações específicas (método abstrato - cada tipo implementa)
if (!$this->validarDadosEspecificos($dados)) {
return "Erro: Validação específica falhou";
}
// 3. Calcular impostos (método abstrato - cada tipo tem sua regra)
$impostos = $this->calcularImpostos($dados);
$dados->setImpostos($impostos);
// 4. Gerar XML (método abstrato - formato específico por tipo)
$xml = $this->gerarXML($dados);
// 5. Assinar digitalmente (método concreto - mesmo para todos)
$xmlAssinado = $this->assinarDigitalmente($xml);
// 6. Enviar para SEFAZ (hook method - pode ser sobrescrito)
$this->enviarParaSEFAZ($xmlAssinado);
// 7. Salvar no banco (método concreto - mesmo processo)
$this->salvarNoBanco($dados, $xmlAssinado);
echo "=== Processamento concluído ===\n";
return "Nota Fiscal processada com sucesso";
}
// Método concreto - implementação padrão
protected function validarDadosBasicos(DadosNota $dados): bool {
return !empty($dados->getCnpjEmitente()) &&
$dados->getValorTotal() > 0 &&
!empty($dados->getItens());
}
// Métodos abstratos - devem ser implementados pelas subclasses
abstract protected function validarDadosEspecificos(DadosNota $dados): bool;
abstract protected function calcularImpostos(DadosNota $dados): float;
abstract protected function gerarXML(DadosNota $dados): string;
// Método concreto - implementação padrão
protected function assinarDigitalmente(string $xml): string {
return "<assinatura>" . $xml . "</assinatura>";
}
// Hook method - pode ser sobrescrito se necessário
protected function enviarParaSEFAZ(string $xml): void {
echo "Enviando para SEFAZ: " . substr($xml, 0, 50) . "...\n";
}
// Método concreto - implementação padrão
protected function salvarNoBanco(DadosNota $dados, string $xml): void {
echo "Salvando nota no banco de dados...\n";
}
}
// Implementação para Nota Fiscal de Produto
class ProcessadorNFeProduto extends ProcessadorNotaFiscal {
protected function validarDadosEspecificos(DadosNota $dados): bool {
// Validações específicas para produtos
foreach ($dados->getItens() as $item) {
if (empty($item->getNcm()) || strlen($item->getNcm()) !== 8) {
echo "Erro: NCM inválido para produto\n";
return false;
}
if (empty($item->getCfop())) {
echo "Erro: CFOP obrigatório para produtos\n";
return false;
}
}
return true;
}
protected function calcularImpostos(DadosNota $dados): float {
$totalImpostos = 0;
foreach ($dados->getItens() as $item) {
// ICMS para produtos
$icms = $item->getValor() * 0.18; // 18%
// IPI para produtos industrializados
$ipi = $item->getValor() * 0.10; // 10%
// PIS/COFINS
$pisCofins = $item->getValor() * 0.0925; // 9,25%
$totalImpostos += $icms + $ipi + $pisCofins;
}
echo "Impostos calculados para NFe Produto: R$ " . number_format($totalImpostos, 2, ',', '.') . "\n";
return $totalImpostos;
}
protected function gerarXML(DadosNota $dados): string {
$xml = "<?xml version='1.0' encoding='UTF-8'?>";
$xml .= "<NFe xmlns='http://www.portalfiscal.inf.br/nfe'>";
$xml .= "<infNFe>";
$xml .= "<emit><CNPJ>" . $dados->getCnpjEmitente() . "</CNPJ></emit>";
foreach ($dados->getItens() as $item) {
$xml .= "<det>";
$xml .= "<prod>";
$xml .= "<cProd>" . $item->getCodigo() . "</cProd>";
$xml .= "<xProd>" . $item->getDescricao() . "</xProd>";
$xml .= "<NCM>" . $item->getNcm() . "</NCM>";
$xml .= "<CFOP>" . $item->getCfop() . "</CFOP>";
$xml .= "<vProd>" . $item->getValor() . "</vProd>";
$xml .= "</prod>";
$xml .= "</det>";
}
$xml .= "<total><vNF>" . $dados->getValorTotal() . "</vNF></total>";
$xml .= "</infNFe>";
$xml .= "</NFe>";
return $xml;
}
}
// Implementação para Nota Fiscal de Serviço
class ProcessadorNFSeServico extends ProcessadorNotaFiscal {
protected function validarDadosEspecificos(DadosNota $dados): bool {
// Validações específicas para serviços
foreach ($dados->getItens() as $item) {
if (empty($item->getCodigoServico())) {
echo "Erro: Código do serviço obrigatório\n";
return false;
}
}
if (empty($dados->getLocalPrestacao())) {
echo "Erro: Local de prestação obrigatório\n";
return false;
}
return true;
}
protected function calcularImpostos(DadosNota $dados): float {
$totalImpostos = 0;
foreach ($dados->getItens() as $item) {
// ISS para serviços
$iss = $item->getValor() * 0.05; // 5%
// PIS/COFINS
$pisCofins = $item->getValor() * 0.0925; // 9,25%
// IR e CSLL para alguns serviços
$irCsll = $item->getValor() * 0.0465; // 4,65%
$totalImpostos += $iss + $pisCofins + $irCsll;
}
echo "Impostos calculados para NFSe Serviço: R$ " . number_format($totalImpostos, 2, ',', '.') . "\n";
return $totalImpostos;
}
protected function gerarXML(DadosNota $dados): string {
$xml = "<?xml version='1.0' encoding='UTF-8'?>";
$xml .= "<NFSe xmlns='http://www.abrasf.org.br/nfse.xsd'>";
$xml .= "<InfNfse>";
$xml .= "<PrestadorServico><Cnpj>" . $dados->getCnpjEmitente() . "</Cnpj></PrestadorServico>";
$xml .= "<TomadorServico><Cnpj>" . $dados->getCnpjDestinatario() . "</Cnpj></TomadorServico>";
foreach ($dados->getItens() as $item) {
$xml .= "<Servico>";
$xml .= "<CodigoTributacaoMunicipio>" . $item->getCodigoServico() . "</CodigoTributacaoMunicipio>";
$xml .= "<Discriminacao>" . $item->getDescricao() . "</Discriminacao>";
$xml .= "<ValorServicos>" . $item->getValor() . "</ValorServicos>";
$xml .= "</Servico>";
}
$xml .= "<ValorTotalServicos>" . $dados->getValorTotal() . "</ValorTotalServicos>";
$xml .= "</InfNfse>";
$xml .= "</NFSe>";
return $xml;
}
// Sobrescreve o hook method para NFSe
protected function enviarParaSEFAZ(string $xml): void {
echo "Enviando NFSe para Prefeitura Municipal...\n";
// Lógica específica para envio de NFSe
}
}
// Implementação para Nota Fiscal Rural
class ProcessadorNFProdutoRural extends ProcessadorNotaFiscal {
protected function validarDadosEspecificos(DadosNota $dados): bool {
// Validações específicas para produtos rurais
if (empty($dados->getInscricaoEstadual())) {
echo "Erro: Inscrição Estadual obrigatória para produtor rural\n";
return false;
}
foreach ($dados->getItens() as $item) {
$descricao = strtolower($item->getDescricao());
if (strpos($descricao, 'rural') === false && strpos($descricao, 'agrícola') === false) {
echo "Aviso: Item pode não ser produto rural\n";
}
}
return true;
}
protected function calcularImpostos(DadosNota $dados): float {
$totalImpostos = 0;
foreach ($dados->getItens() as $item) {
// ICMS reduzido para produtos rurais
$icms = $item->getValor() * 0.07; // 7% (reduzido)
// Produtos rurais podem ter isenção de IPI
$ipi = 0; // Isento
// PIS/COFINS com alíquota diferenciada
$pisCofins = $item->getValor() * 0.0365; // 3,65%
$totalImpostos += $icms + $ipi + $pisCofins;
}
echo "Impostos calculados para NF Rural: R$ " . number_format($totalImpostos, 2, ',', '.') . "\n";
return $totalImpostos;
}
protected function gerarXML(DadosNota $dados): string {
$xml = "<?xml version='1.0' encoding='UTF-8'?>";
$xml .= "<NFe xmlns='http://www.portalfiscal.inf.br/nfe'>";
$xml .= "<infNFe>";
$xml .= "<emit>";
$xml .= "<CNPJ>" . $dados->getCnpjEmitente() . "</CNPJ>";
$xml .= "<IE>" . $dados->getInscricaoEstadual() . "</IE>";
$xml .= "<CRT>1</CRT>"; // Simples Nacional
$xml .= "</emit>";
foreach ($dados->getItens() as $item) {
$xml .= "<det>";
$xml .= "<prod>";
$xml .= "<cProd>" . $item->getCodigo() . "</cProd>";
$xml .= "<xProd>" . $item->getDescricao() . " - PRODUTO RURAL</xProd>";
$xml .= "<NCM>" . $item->getNcm() . "</NCM>";
$xml .= "<CFOP>5101</CFOP>"; // CFOP típico para venda rural
$xml .= "<vProd>" . $item->getValor() . "</vProd>";
$xml .= "</prod>";
$xml .= "</det>";
}
$xml .= "<total><vNF>" . $dados->getValorTotal() . "</vNF></total>";
$xml .= "</infNFe>";
$xml .= "</NFe>";
return $xml;
}
}
// Classes de apoio
class DadosNota {
private string $cnpjEmitente;
private string $cnpjDestinatario;
private ?string $inscricaoEstadual = null;
private ?string $localPrestacao = null;
private float $valorTotal;
private float $impostos = 0;
private array $itens;
public function __construct(string $cnpjEmitente, string $cnpjDestinatario, float $valorTotal, array $itens) {
$this->cnpjEmitente = $cnpjEmitente;
$this->cnpjDestinatario = $cnpjDestinatario;
$this->valorTotal = $valorTotal;
$this->itens = $itens;
}
// Getters e Setters
public function getCnpjEmitente(): string { return $this->cnpjEmitente; }
public function getCnpjDestinatario(): string { return $this->cnpjDestinatario; }
public function getInscricaoEstadual(): ?string { return $this->inscricaoEstadual; }
public function setInscricaoEstadual(string $ie): void { $this->inscricaoEstadual = $ie; }
public function getLocalPrestacao(): ?string { return $this->localPrestacao; }
public function setLocalPrestacao(string $local): void { $this->localPrestacao = $local; }
public function getValorTotal(): float { return $this->valorTotal; }
public function getImpostos(): float { return $this->impostos; }
public function setImpostos(float $impostos): void { $this->impostos = $impostos; }
public function getItens(): array { return $this->itens; }
}
class ItemNota {
private string $codigo;
private string $descricao;
private float $valor;
private ?string $ncm = null;
private ?string $cfop = null;
private ?string $codigoServico = null;
public function __construct(string $codigo, string $descricao, float $valor) {
$this->codigo = $codigo;
$this->descricao = $descricao;
$this->valor = $valor;
}
// Getters e Setters
public function getCodigo(): string { return $this->codigo; }
public function getDescricao(): string { return $this->descricao; }
public function getValor(): float { return $this->valor; }
public function getNcm(): ?string { return $this->ncm; }
public function setNcm(string $ncm): void { $this->ncm = $ncm; }
public function getCfop(): ?string { return $this->cfop; }
public function setCfop(string $cfop): void { $this->cfop = $cfop; }
public function getCodigoServico(): ?string { return $this->codigoServico; }
public function setCodigoServico(string $codigoServico): void { $this->codigoServico = $codigoServico; }
}
// Factory para seleção do processador
class ProcessadorFactory {
public static function criar(string $tipo): ProcessadorNotaFiscal {
switch ($tipo) {
case 'NFE_PRODUTO':
return new ProcessadorNFeProduto();
case 'NFSE_SERVICO':
return new ProcessadorNFSeServico();
case 'NF_RURAL':
return new ProcessadorNFProdutoRural();
default:
throw new InvalidArgumentException("Tipo não suportado: $tipo");
}
}
}
// Controller para API REST
class NotaFiscalController {
public function processarNota(array $request): array {
try {
$processador = ProcessadorFactory::criar($request['tipo']);
$resultado = $processador->processarNotaFiscal($request['dados']);
return [
'status' => 'success',
'message' => $resultado,
'timestamp' => date('Y-m-d H:i:s')
];
} catch (Exception $e) {
return [
'status' => 'error',
'message' => $e->getMessage(),
'timestamp' => date('Y-m-d H:i:s')
];
}
}
}
// Exemplo de uso
echo "=== DEMONSTRAÇÃO DO TEMPLATE METHOD EM PHP ===\n\n";
try {
// Preparar dados para NFe de Produto
$itensProduto = [
new ItemNota("001", "Notebook Dell", 2500.00),
new ItemNota("002", "Mouse Logitech", 50.00)
];
// Configurar dados específicos para produtos
$itensProduto[0]->setNcm("84713012");
$itensProduto[0]->setCfop("5102");
$itensProduto[1]->setNcm("84716060");
$itensProduto[1]->setCfop("5102");
$dadosNFe = new DadosNota("12345678000123", "98765432000187", 2550.00, $itensProduto);
// Processar NFe de Produto
echo "1. PROCESSANDO NFe DE PRODUTO:\n";
echo str_repeat("-", 50) . "\n";
$processadorNFe = new ProcessadorNFeProduto();
$resultadoNFe = $processadorNFe->processarNotaFiscal($dadosNFe);
echo "Resultado NFe: $resultadoNFe\n\n";
// Preparar dados para NFSe de Serviço
$itensServico = [
new ItemNota("SERV001", "Consultoria em TI", 1500.00),
new ItemNota("SERV002", "Desenvolvimento de Sistema", 3000.00)
];
// Configurar dados específicos para serviços
$itensServico[0]->setCodigoServico("01.01");
$itensServico[1]->setCodigoServico("01.02");
$dadosNFSe = new DadosNota("12345678000123", "98765432000187", 4500.00, $itensServico);
$dadosNFSe->setLocalPrestacao("São Paulo/SP");
// Processar NFSe de Serviço
echo "2. PROCESSANDO NFSe DE SERVIÇO:\n";
echo str_repeat("-", 50) . "\n";
$processadorNFSe = new ProcessadorNFSeServico();
$resultadoNFSe = $processadorNFSe->processarNotaFiscal($dadosNFSe);
echo "Resultado NFSe: $resultadoNFSe\n\n";
// Preparar dados para NF Rural
$itensRural = [
new ItemNota("RURAL001", "Soja em grão rural", 5000.00),
new ItemNota("RURAL002", "Milho agrícola", 2000.00)
];
// Configurar dados específicos para produtos rurais
$itensRural[0]->setNcm("12019000");
$itensRural[1]->setNcm("10051000");
$dadosRural = new DadosNota("12345678000123", "98765432000187", 7000.00, $itensRural);
$dadosRural->setInscricaoEstadual("123456789");
// Processar NF Rural
echo "3. PROCESSANDO NF RURAL:\n";
echo str_repeat("-", 50) . "\n";
$processadorRural = new ProcessadorNFProdutoRural();
$resultadoRural = $processadorRural->processarNotaFiscal($dadosRural);
echo "Resultado NF Rural: $resultadoRural\n\n";
// Exemplo usando Factory e Controller
echo "4. EXEMPLO COM FACTORY E CONTROLLER:\n";
echo str_repeat("-", 50) . "\n";
$controller = new NotaFiscalController();
$request = [
'tipo' => 'NFE_PRODUTO',
'dados' => $dadosNFe
];
$response = $controller->processarNota($request);
echo "Response do Controller:\n";
echo json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
} catch (Exception $e) {
echo "Erro durante execução: " . $e->getMessage() . "\n";
}
?>
<!--
=== COMO INTEGRAR COM FRAMEWORK WEB ===
// Laravel Route
Route::post('/api/notas-fiscais/processar', [NotaFiscalController::class, 'processarNota']);
// Symfony Controller
#[Route('/api/notas-fiscais/processar', methods: ['POST'])]
public function processarNota(Request $request): JsonResponse
{
$dados = json_decode($request->getContent(), true);
$controller = new NotaFiscalController();
$resultado = $controller->processarNota($dados);
return new JsonResponse($resultado);
}
// CodeIgniter 4
public function processar()
{
$dados = $this->request->getJSON(true);
$controller = new NotaFiscalController();
$resultado = $controller->processarNota($dados);
return $this->response->setJSON($resultado);
}
-->