DEV Community

Cover image for Design Patterns in PHP 8: Interpreter
Max Zhuk
Max Zhuk

Posted on • Edited on

Design Patterns in PHP 8: Interpreter

Hello, fellow developers!🧑🏼‍💻

In the realm of software development, design patterns play a pivotal role in crafting efficient and scalable applications. One such pattern, the Interpreter pattern, offers a unique approach to evaluating language grammar or expressions for specific languages. Essentially, it's akin to building a mini language processor. In this article, we will delve into the intricacies of the Interpreter pattern and demonstrate its implementation in PHP 8 through the creation of a simple mathematical expression evaluator.

Understanding the Interpreter Pattern

The Interpreter pattern is designed to interpret sentences in a language. It involves defining a representation for the language's grammar alongside an interpreter to process this grammar. This pattern is particularly useful when you need to design a tool that interprets or compiles languages, offering a structured approach to translating one form of data into another.

Before we dive into the coding part, let's understand the components involved in this pattern:

  1. Expression Interface: This is a blueprint for our expressions, defining a method that will interpret different kinds of expressions.
  2. Terminal Expressions: These are the primary expressions that return a value.
  3. Non-Terminal Expressions: These expressions represent operations and use terminal expressions to complete their tasks.

With this understanding, let's proceed to build our mathematical expression evaluator.

Building a Simple Mathematical Expression Evaluator

Step 1: Expression Interface

First, we define an interface for our expressions, which declares a method to interpret the expressions:

interface Expression
{
    public function interpret(array $context): int;
}
Enter fullscreen mode Exit fullscreen mode

Step 2: Terminal Expressions

Next, we create the main expressions that return a value. These expressions represent the numbers in our mathematical expressions:

class Number implements Expression
{
    public function __construct(private int $number) {}

    public function interpret(array $context): int
    {
        return $this->number;
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Non-Terminal Expressions

Now, we develop expressions that represent operations, utilizing terminal expressions to return a result:

class Add implements Expression
{
    public function __construct(private Expression $left, private Expression $right) {}

    public function interpret(array $context): int
    {
        return $this->left->interpret($context) + $this->right->interpret($context);
    }
}

class Subtract implements Expression
{
    public function __construct(private Expression $left, private Expression $right) {}

    public function interpret(array $context): int
    {
        return $this->left->interpret($context) - $this->right->interpret($context);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Client Code

Finally, we create a client code to interpret and evaluate mathematical expressions:

class Client
{
    public static function main()
    {
        $context = [];

        // Creating number expressions
        $five = new Number(5);
        $three = new Number(3);

        // Creating and interpreting add expression
        $addExpression = new Add($five, $three);
        echo "Result of addition: " . $addExpression->interpret($context) . "\n"; // Output: 8

        // Creating and interpreting subtract expression
        $subtractExpression = new Subtract($five, $three);
        echo "Result of subtraction: " . $subtractExpression->interpret($context) . "\n"; // Output: 2
    }
}

// Running the client code
Client::main();
Enter fullscreen mode Exit fullscreen mode

In this client code, we create Number objects representing the numbers 5 and 3, and then create Add and Subtract expressions using these numbers. We interpret these expressions to get the results, which are then printed.

Through this article, we have explored the Interpreter pattern and its practical implementation in PHP 8 by building a simple mathematical expression evaluator. This pattern offers a structured approach to designing language compilers or interpreters, showcasing the power and flexibility of object-oriented programming.

As you venture further into the world of design patterns, you'll find that the Interpreter pattern can be a valuable tool in your developer toolkit, especially when dealing with language processing tasks. Happy coding!

References

  1. Design Patterns: Elements of Reusable Object-Oriented Software - Book by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  2. PHP 8 Documentation - Official PHP 8 Documentation

P.S. Fellow developers, if you've found value in this article and are eager to deepen your understanding of design patterns in PHP and TypeScript, I have thrilling news for you! I am in the midst of crafting a comprehensive book that delves extensively into these topics, filled with practical examples, lucid explanations, and real-world applications of these patterns.

This book is being designed to cater to both novices and seasoned developers, aiming to bolster your understanding and implementation of design patterns in PHP and TypeScript. Whether you are aiming to refresh your existing knowledge or venture into new learning territories, this book is your perfect companion.

Moreover, your subscription will play a pivotal role in supporting the completion of this book, enabling me to continue providing you with quality content that can elevate your coding prowess to unprecedented heights.

I invite you to subscribe to my blog on dev.to for regular updates. I am eager to embark on this journey with you, helping you to escalate your coding skills to the next level!


Photo by Ilya Podshivalov

Top comments (1)

Collapse
 
xwero profile image
david duymelinck • Edited

The example doesn't add much value to the code just write echo 3+5; echo 3-5; and you got the same result.
The pattern as you show it also adds limitations, like only two arguments for the non-terminal classes.

I created an example that makes the grammar building more front and center.

<?php

interface BooleanGrammar {
    public function result(string $context): bool;
}
// Terminal classes
abstract class Node implements BooleanGrammar {
    public function __construct(private string $data) {}

    public function result(string $context): bool {
        return str_contains($context, $this->data);
    }
}

class Person extends Node {}

class Relationship extends Node {}
// Non-terminal classes
class HomogeneousGroup implements BooleanGrammar {
    private $persons = [];

    public function __construct(Person ...$persons) {
        $this->persons = $persons;
    }

    public function result(string $context): bool {
        foreach($this->persons as $person) {
            if($person->result($context)) {
                return true;
            }
        }

        return false;
    }
}

class PersonHasRelationship implements BooleanGrammar {
    public function __construct(private Person $person, private Relationship $relationship) {}

    public function result(string $context): bool {
        return $this->person->result($context) && $this->relationship->result($context);
    }
}
// tests
$areSingle = new HomogeneousGroup(new Person('me'), new Person('myself'));

var_dump($areSingle->result('me')); // true
var_dump($areSingle->result('i')); // false

$meRelationship = new PersonHasRelationship(new Person('me'), new Relationship('single'));

var_dump($meRelationship->result('me, single')); // true
var_dump($meRelationship->result('me, married')); // false
Enter fullscreen mode Exit fullscreen mode

I hope that this gives people a better idea on how to use the design pattern.