Interpreter Design Pattern in PHP

In the interpreter pattern you define a language, parse requests in that language, and assign the appropriate class(es), method(s), etc to handle each request.

In this example, the Interpreter class can handle strings in the following formats: "book author #", "book title #", or "book author title #". The # must be a numeric which must correlate to a book in the list of books we have.

<?php

class Interpreter {  
    private $bookList;
    public function __construct($bookListIn) {
      $this->bookList = $bookListIn;
    }
    public function interpret($stringIn) {      
      $arrayIn = explode(" ",$stringIn);      
      $returnString = NULL;      
      // go through the array validating 
      // and if possible calling a book method
      // could use refactoring, some duplicate logic 
      if ('book' == $arrayIn[0]) {
        if ('author' == $arrayIn[1]) {
          if (is_numeric($arrayIn[2])) {
            $book = $this->bookList->getBook($arrayIn[2]);
            if (NULL == $book) {
              $returnString = 'Can not process, there is no book # '.$arrayIn[2]; 
            } else {
              $returnString = $book->getAuthor(); 
            }
          } elseif ('title' == $arrayIn[2]) {
            if (is_numeric($arrayIn[3])) {
              $book = $this->bookList->getBook($arrayIn[3]);
              if (NULL == $book) {
                $returnString = 'Can not process, there is no book # '.
                  $arrayIn[3]; 
              } else {
                $returnString = $book->getAuthorAndTitle(); 
              }
            } else {
              $returnString = 'Can not process, book # must be numeric.'; 
            }            
          } else {
            $returnString = 'Can not process, book # must be numeric.'; 
          }
        }
        if ('title' == $arrayIn[1]) {
          if (is_numeric($arrayIn[2])) {
            $book = $this->bookList->getBook($arrayIn[2]);
            if (NULL == $book) {
              $returnString = 'Can not process, there is no book # '.
                $arrayIn[2]; 
            } else {
              $returnString = $book->getTitle(); 
            }
          } else {
            $returnString = 'Can not process, book # must be numeric.'; 
          }
        }
      } else {
        $returnString = 'Can not process, can only process book author #,  book title #, or book author title #'; 
      }      
      return $returnString;  
    }
}

class BookList {
    private $books = array();
    private $bookCount = 0;
    public function __construct() {
    }
    public function getBookCount() {
        return $this->bookCount;
    }
    private function setBookCount($newCount) {
        $this->bookCount = $newCount;
    }
    public function getBook($bookNumberToGet) {
        if ( (is_numeric($bookNumberToGet)) && 
           ($bookNumberToGet <= $this->getBookCount())) {
           return $this->books[$bookNumberToGet];
        } else {
           return NULL;
        }
    }
    public function addBook(Book $book_in) {
        $this->setBookCount($this->getBookCount() + 1);
        $this->books[$this->getBookCount()] = $book_in;
        return $this->getBookCount();
    }
    public function removeBook(Book $book_in) {
      $counter = 0;
      while (++$counter <= $this->getBookCount()) {
        if ($book_in->getAuthorAndTitle() == 
          $this->books[$counter]->getAuthorAndTitle())
		  {
            for ($x = $counter; $x < $this->getBookCount(); $x++) {
              $this->books[$x] = $this->books[$x + 1];
          }
          $this->setBookCount($this->getBookCount() - 1);
        }
      }
      return $this->getBookCount();
    }
}

class Book {
    private $author;
    private $title;
    function __construct($title_in, $author_in) {
        $this->author = $author_in;
        $this->title  = $title_in;
    }
    function getAuthor() {
        return $this->author;
    }
    function getTitle() {
        return $this->title;
    }
    function getAuthorAndTitle() {
        return $this->getTitle().' by '.$this->getAuthor();
    }
}

  writeln('BEGIN TESTING INTERPRETER PATTERN');
  writeln('');

  //load BookList for test data
  $bookList = new BookList();
  $inBook1 = new Book('PHP for Cats','Larry Truett');
  $inBook2 = new Book('MySQL for Cats','Larry Truett');
  $bookList->addBook($inBook1);
  $bookList->addBook($inBook2);
 
  $interpreter = new Interpreter($bookList);
 
  writeln('test 1 - invalid request missing "book"');
  writeln($interpreter->interpret('author 1'));
  writeln('');
 
  writeln('test 2 - valid book author request');
  writeln($interpreter->interpret('book author 1'));
  writeln('');

  writeln('test 3 - valid book title request');
  writeln($interpreter->interpret('book title 2'));
  writeln('');

  writeln('test 4 - valid book author title request');
  writeln($interpreter->interpret('book author title 1'));
  writeln('');

  writeln('test 5 - invalid request with invalid book number');
  writeln($interpreter->interpret('book title 3'));
  writeln('');

  writeln('test 6 - invalid request with nuo numeric book number');
  writeln($interpreter->interpret('book title one'));
  writeln('');

  writeln('END TESTING INTERPRETER PATTERN');
 
  function writeln($line_in) {
    echo $line_in."<br/>";
  }

?>

Output

BEGIN TESTING INTERPRETER PATTERN

test 1 - invalid request missing "book"
Can not process, can only process book author #, 
book title #, or book author title #

test 2 - valid book author request
Larry Truett

test 3 - valid book title request
MySQL for Cats

test 4 - valid book author title request
PHP for Cats by Larry Truett

test 5 - invalid request with invalid book number
Can not process, there is no book # 3

test 6 - invalid request with nuo numeric book number
Can not process, book # must be numeric.

END TESTING INTERPRETER PATTERN

Code examples