Neste artigo
O Padrão de Projeto Interpreter representa a gramática de uma linguagem simples com classes e usa essas classes para interpretar sentenças dessa linguagem. Cada regra da gramática vira uma classe, e cada sentença vira uma árvore de objetos. Para saber o resultado, basta chamar interpret() no objeto do topo da árvore.
A linguagem aqui não é PHP nem SQL. É uma linguagem pequena, do seu domínio, como as regras de uma promoção ou de um filtro de busca.

Observe no diagrama os participantes do padrão:
- AbstractExpression (Expression): a interface com o método
interpret(). - TerminalExpression (EqualsExpression e GreaterThanExpression): as folhas da árvore. Consultam o contexto e devolvem o resultado.
- NonterminalExpression (AndExpression, OrExpression e NotExpression): combinam outras expressões.
- Context (Context): os dados sobre os quais a sentença é avaliada.
- Client: quem monta a árvore e chama
interpret(). No exemplo, é o teste.
Qual problema o Interpreter resolve?
Pense em uma loja virtual em que a regra de frete grátis muda toda semana. Hoje ela é: pedido acima de R$ 200 para São Paulo, ou qualquer pedido de cliente VIP.
A solução direta é escrever a regra no código:
<?php
function hasFreeShipping(array $order): bool
{
return ($order['total'] > 200 && $order['state'] === 'SP')
|| $order['customer'] === 'vip';
}
Funciona, mas cada mudança de regra exige alterar o código e publicar de novo. Com o Interpreter, a regra deixa de ser código e passa a ser um dado: uma árvore de objetos, que pode ser montada a partir de uma configuração, de um banco de dados ou de uma tela de cadastro.
A nossa linguagem de regras é pequena. Ela tem cinco construções:
| Regra da linguagem | Significado | Classe |
|---|---|---|
| campo = valor | O campo é igual ao valor | EqualsExpression |
| campo > número | O campo é maior que o número | GreaterThanExpression |
| A AND B | As duas expressões são verdadeiras | AndExpression |
| A OR B | Pelo menos uma é verdadeira | OrExpression |
| NOT A | A expressão é falsa | NotExpression |
Exemplo do padrão Interpreter em PHP
Vamos para o código. A interface Expression é o que toda expressão sabe fazer:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
interface Expression
{
public function interpret(Context $context): bool;
}
O Context guarda os dados do pedido. Se a regra pedir um campo que não existe, ele avisa com uma exceção:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
use InvalidArgumentException;
final class Context
{
/** @var array<string, string|int|float> */
private array $values;
/** @param array<string, string|int|float> $values */
public function __construct(array $values)
{
$this->values = $values;
}
public function get(string $field): string|int|float
{
if (!array_key_exists($field, $this->values)) {
throw new InvalidArgumentException(sprintf('Unknown field: %s', $field));
}
return $this->values[$field];
}
}
Agora as expressões terminais. A EqualsExpression compara um campo com um valor:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
final class EqualsExpression implements Expression
{
private string $field;
private string $value;
public function __construct(string $field, string $value)
{
$this->field = $field;
$this->value = $value;
}
public function interpret(Context $context): bool
{
return $context->get($this->field) === $this->value;
}
}
E a GreaterThanExpression verifica se um campo é maior que um número:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
final class GreaterThanExpression implements Expression
{
private string $field;
private float $value;
public function __construct(string $field, float $value)
{
$this->field = $field;
$this->value = $value;
}
public function interpret(Context $context): bool
{
return $context->get($this->field) > $this->value;
}
}
As expressões não terminais recebem outras expressões e combinam os resultados. Os nomes têm o sufixo Expression porque and e or são palavras reservadas do PHP e não podem ser nome de classe:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
final class AndExpression implements Expression
{
private Expression $left;
private Expression $right;
public function __construct(Expression $left, Expression $right)
{
$this->left = $left;
$this->right = $right;
}
public function interpret(Context $context): bool
{
return $this->left->interpret($context) && $this->right->interpret($context);
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
final class OrExpression implements Expression
{
private Expression $left;
private Expression $right;
public function __construct(Expression $left, Expression $right)
{
$this->left = $left;
$this->right = $right;
}
public function interpret(Context $context): bool
{
return $this->left->interpret($context) || $this->right->interpret($context);
}
}
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Behavioral\Interpreter;
final class NotExpression implements Expression
{
private Expression $expression;
public function __construct(Expression $expression)
{
$this->expression = $expression;
}
public function interpret(Context $context): bool
{
return !$this->expression->interpret($context);
}
}
Repare que uma AndExpression não sabe se os filhos são terminais ou outras combinações. Ela só chama interpret() em cada um. É isso que permite montar regras de qualquer tamanho.
Testando o exemplo
O terceiro teste monta a regra de frete grátis do começo do artigo e avalia três pedidos diferentes com ela:
<?php
declare(strict_types=1);
namespace Growthdev\DesignPatterns\Tests\Behavioral\Interpreter;
use Growthdev\DesignPatterns\Behavioral\Interpreter\AndExpression;
use Growthdev\DesignPatterns\Behavioral\Interpreter\Context;
use Growthdev\DesignPatterns\Behavioral\Interpreter\EqualsExpression;
use Growthdev\DesignPatterns\Behavioral\Interpreter\GreaterThanExpression;
use Growthdev\DesignPatterns\Behavioral\Interpreter\NotExpression;
use Growthdev\DesignPatterns\Behavioral\Interpreter\OrExpression;
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
final class InterpreterTest extends TestCase
{
public function testExpectTerminalExpressionsToReadTheContext(): void
{
$context = new Context(['total' => 250.0, 'state' => 'SP']);
$this->assertTrue((new GreaterThanExpression('total', 200.0))->interpret($context));
$this->assertFalse((new GreaterThanExpression('total', 300.0))->interpret($context));
$this->assertTrue((new EqualsExpression('state', 'SP'))->interpret($context));
$this->assertFalse((new EqualsExpression('state', 'RJ'))->interpret($context));
}
public function testExpectNotToInvertAnExpression(): void
{
$context = new Context(['state' => 'SP']);
$notFromRio = new NotExpression(new EqualsExpression('state', 'RJ'));
$this->assertTrue($notFromRio->interpret($context));
}
public function testExpectFreeShippingRuleToBeInterpreted(): void
{
// (total > 200 AND state = SP) OR customer = vip
$freeShipping = new OrExpression(
new AndExpression(
new GreaterThanExpression('total', 200.0),
new EqualsExpression('state', 'SP')
),
new EqualsExpression('customer', 'vip')
);
$bigOrderFromSaoPaulo = new Context(['total' => 250.0, 'state' => 'SP', 'customer' => 'regular']);
$bigOrderFromRio = new Context(['total' => 250.0, 'state' => 'RJ', 'customer' => 'regular']);
$smallOrderFromVip = new Context(['total' => 50.0, 'state' => 'RJ', 'customer' => 'vip']);
$this->assertTrue($freeShipping->interpret($bigOrderFromSaoPaulo));
$this->assertFalse($freeShipping->interpret($bigOrderFromRio));
$this->assertTrue($freeShipping->interpret($smallOrderFromVip));
}
public function testExpectUnknownFieldToFail(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage('Unknown field: coupon');
(new EqualsExpression('coupon', 'FRETE10'))->interpret(new Context(['total' => 250.0]));
}
}
A regra é montada uma vez e reaproveitada com vários contextos. Para mudar a regra, basta montar outra árvore, sem tocar em nenhuma classe.
Quando usar o padrão Interpreter?
- Quando o domínio tem uma linguagem simples que se repete: regras de promoção, filtros de busca, permissões, validações configuráveis.
- Quando as regras precisam mudar sem alterar o código da aplicação.
- Quando a gramática é pequena e o desempenho não é crítico.
Cuidados ao usar o Interpreter
- Cada regra da gramática é uma classe. Em uma gramática grande, isso vira dezenas de classes difíceis de manter.
- O padrão não cuida de transformar texto em árvore. No exemplo a árvore é montada à mão; ler uma regra escrita como texto exige um analisador (parser), que é outro trabalho.
- Antes de escrever a sua linguagem, veja se uma biblioteca pronta resolve. O componente ExpressionLanguage do Symfony, por exemplo, avalia expressões escritas como texto e é indicado pela própria documentação como base para um motor de regras de negócio.
- Nunca use
eval()para avaliar regras vindas de fora. O manual do PHP desaconselha o uso porque ele permite executar qualquer código PHP.
Interpreter e Composite: qual a diferença?
A árvore de expressões do Interpreter tem a mesma estrutura do Composite: as expressões terminais são as folhas, e as não terminais são os grupos. A diferença está no objetivo de cada padrão:
| Padrão | Tipo | Foco |
|---|---|---|
| Interpreter | Comportamental | Dar significado a uma sentença: cada nó da árvore sabe se avaliar |
| Composite | Estrutural | Montar a árvore e tratar folha e grupo do mesmo jeito |
Esse é mais um padrão comportamental do livro GoF. Com este artigo, os 23 padrões do livro têm um exemplo em PHP aqui no blog. A lista completa está no artigo Resumo dos Padrões de Projetos.
O código completo deste exemplo, com os testes, está no meu GitHub, na pasta src/Behavioral/Interpreter do repositório growthdev-repo/design-patterns.
Veja também: o resumo dos 23 padrões de projeto GoF e os outros padrões comportamentais já publicados: Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor.
Confiança Sempre!!!
Fontes:
- [1] GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos.
- [2] FREEMAN, Eric; ROBSON, Elisabeth; BATES, Bert; SIERRA, Kathy. Head First Design Patterns.
- [3] Symfony Docs. Expression Language.
- [4] PHP Manual. eval.

Continue a conversa