{ Entity Classes Part II }

This is a continuation of the entity classes post using the domain model pattern. We created the Base class with the entity hydrators, but haven’t made use of these methods yet.

Suppose we have a Faculty entity. We can split this into Faculty.php and FacultyRepository.php. If you have a lot of fields in your table, the method list in Faculty can get quite lengthy. So here’s another plug for Doctrine and its ORM or Object Relational Mapper, which I’ll get to in a later post.

In one of my recent projects, I inherited a lot of legacy code and data, brought over from Microsoft Access. I’ll keep this part of the code brief.

Faculty.php

class Faculty extends Base {

   protected $last;
   protected $first;
   .. and on and on..

   protected $mapping = [
      'id' => 'id',
      'last' => 'last',
      'first' => 'first',
      ....
   }
}

Then FacultyRepository, which uses a custom Finder class with static methods that acts as a query builder. To draw from a legacy reference, the methods can be chained together in a jQuery-like manner as each method returns a reference to the singleton instance.

class Finder {

   public static $sql = '';
   public static $instance = NULL;
   public static $prefix = '';
   public static $join = '';
   public static $where = array();
   public static $group = '';
   public static $control = ['', ''];

   /**
    * @param $table
    * @param null $cols
    *
    * @return Finder
    *
    * Returns singleton instance of class
    */
   public static function select($table, $cols = NULL) {
      self::$instance = new Finder();

      if ($cols) {
         self::$prefix = 'SELECT ' . $cols . ' FROM ' . $table;
      } else {
         self::$prefix = 'SELECT * FROM ' . $table;
      }

      return self::$instance;
   }
}

The select method is the starting point in lieu of a constructor, as we don’t want to instantiate the class. It can only do that itself. Then we have a myriad of static methods to choose from:

public static function where($filter = NULL) {
   self::$where[0] = ' WHERE ' . $filter;
   return self::$instance;
}

public static function innerJoin($join1, $join2) {
   self::$join = ' INNER JOIN ' . $join1 . ' ON ' . $join2;
   return self::$instance;
}

public static function like($a, $b) {
   self::$where[] = trim($a . ' LIKE ' . $b);
   return self::$instance;
}

public static function and($a = NULL) {
   self::$where[] = trim('AND ' . $a);
   return self::$instance;
}

public static function or($a = NULL) {
   self::$where[] = trim('OR' . $a);
   return self::$instance;
}

public static function in(array $a) {
   self::$where[] = 'IN ( ' . implode(',', $a) . ' )';
   return self::$instance;
}

public static function not($a = NULL) {
   self::$where[] = trim('NOT ' . $a);
   return self::$instance;
}

public static function limit($limit) {
   self::$control[0] = 'LIMIT ' . $limit;
   return self::$instance;
}

public static function offset($offset) {
   self::$control[1] = 'OFFSET ' . $offset;
   return self::$instance;
}

public static function group(array $fields) {
   self::$group = 'GROUP BY ' . implode(',', $fields);
   return self::$instance;
}

Then finally our helper method to get the final SQL:

public static function getSql() {
   self::$sql = self::$prefix . self::$join . implode(' ', self::$where)
      . ' '
       . self::$group
                . ' '
      . self::$control[0]
      . ' '
      . self::$control[1];

   preg_replace('/ /', ' ', self::$sql);
   return trim(self::$sql);
}

Then we can do something like this in FacultyRepository.php

class FacultyRepository {

   const TABLE = 'faculty_table_name';

   protected $connection;

   public function __construct(Connection $connection) {
      $this->connection = $connection;
   }
   public function fetchByLastName($search) {
      $prepare = Finder::select(self::TABLE)->where()->like('last', ':search')->getSql();

      $stmt = $this->connection->pdo->prepare($prepare);
      $stmt->bindValue(':search', "{$search}%");
      $stmt->execute();

      $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

      foreach ($results as $result) {
         yield Faculty::arrayToEntity($result, new Faculty());
      }
   }
}

Here we have a method that does a search by last name, and hydrates the Faculty class with each result. This also introduces the yield statement, which returns a generator instance to the caller. If we want to grab all of the results:

public function fetchAll() {
   $prepare = Finder::select(self::TABLE)->getSql();

   $stmt = $this->connection->pdo->prepare($prepare);
   $stmt->execute();

   $results = $stmt->fetchAll(PDO::FETCH_ASSOC);

   foreach ($results as $result) {
      yield Faculty::arrayToEntity($result, new Faculty());
   }
}

Example of usage:

define('DB_CONFIG_FILE', 'config.php');

$connection = new Connection(include __DIR__ . '../' .  DB_CONFIG_FILE);

$repo = new FacultyRepository($connection);
$resultGenerator = $repo->fetchAll();

$nameIterator = new ArrayIterator();
foreach ($resultGenerator as $result) {
   
   $nameIterator->append([
      'id' => $result->getId(),
      'name' => $result->getFirst() . ' ' . $result->getLast()
   ]);
}
foreach ($nameIterator as $result): ?>
   <div><a class="does-trigger-popup" href="#" data-model-id="<?= $result['id']; ?>"><?= $result['name']; ?></a></div>
   <div><?= $result['name']; ?></div>
endforeach;