Code Review Stack Exchange is a question and answer site for peer programmer code reviews. Join them; it only takes a minute:

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Using the table data gateway (TDG) pattern, I created a class QuestionGateway for retrieving, adding, updating, deleting, and sorting multiple-choice questions.

class QuestionGateway
{
    private $dbh;

    public function __construct(PDO $dbh)
    {
        $this->dbh = $dbh;
    }

    /**
     * Find all test questions with all their choices.
     * @param $testId int
     * @return array 
     */
    public function findAll($testId) 
    {
        $questions = array();
        // get all questions
        $sth = $this->dbh->prepare("
            SELECT id, `order`, question  
            FROM questions WHERE test_id = ? ORDER BY `order`
        ");
        $sth->execute(array($testId));
        $rows = $sth->fetchAll();
        $sth->closeCursor();

        if (!empty($rows)) {
            foreach ($rows as $row) {
                $row->choices = array();
                $questions[$row->id] = $row;
            }
        }
        // merge choices with their respective question
        if (!empty($questions)) {
            $choices = $this->findAllChoices($testId);
            foreach ($choices as $choice) {
                if (array_key_exists($choice->question_id, $questions)) {
                    $questions[$choice->question_id]->choices[] = $choice;
                }
            }
        }

        return $questions;
    }

    /**
     * Find all test choices.
     * @param $testId int
     * @return bool
     */
    private function findAllChoices($testId) 
    {
        try {
            $sth = $this->dbh->prepare("
                SELECT id, choice, correct, question_id 
                FROM choices 
                WHERE question_id IN (SELECT id FROM questions WHERE test_id = ?) 
                ORDER BY question_id
            ");
            $sth->execute(array($testId));
            $rows = $sth->fetchAll();
            $sth->closeCursor();
        } catch (Exception $e) {
            $this->log($e->getMessage());
        }
        return $rows;   
    }

    /**
     * Add question.
     * @param $testId int
     * @param $question string
     * @param $choices array
     * @param $correct int the i-th choice that is the correct answer
     * @return bool
     */
    public function add($testId, $question, $choices, $correct) 
    {
        try {
            $this->dbh->beginTransaction();

            $sth = $this->dbh->prepare("
                INSERT INTO questions SET question = ?, test_id = ?
            ");
            $sth->execute(array($question, $testId));

            $questionId = $this->dbh->lastInsertId();

            $sth = $this->dbh->prepare("
                INSERT INTO choices SET choice = ?, correct = ?, question_id = ?
            ");
            foreach ($choices as $i => $choice) {
                $sth->execute(array($choice, $i == $correct ? 'Y' : 'N', $questionId));
            }
            return $this->dbh->commit();
        } catch (Exception $e) {
            $this->dbh->rollBack();
            $this->log($e->getMessage());
        }
        return false;
    }

    /**
     * Update question.
     * @param $questionId int
     * @param $question string
     * @param $choices array
     * @param $correct int the i-th choice that is the correct choice
     * @return bool
     */
    public function update($questionId, $question, $choices, $correct) 
    {
        try {
            $this->dbh->beginTransaction();

            $sth = $this->dbh->prepare("
                UPDATE questions SET question = ? WHERE id = ?
            ");
            $sth->execute(array($question, $questionId));

            $sth = $this->dbh->prepare("
                DELETE FROM choices WHERE question_id = ?
            ");
            $sth->execute(array($questionId));

            $sth = $this->dbh->prepare("
                INSERT INTO choices SET question_id = ?, choice = ?, correct = ?
            ");
            foreach ($choices as $order => $choice) {
                $sth->execute(array($questionId, $choice, $order == $correct ? 'Y' : 'N'));
            }
            return $this->dbh->commit();
        } catch (Exception $e) {
            $this->dbh->rollBack();
        }
        return false;
    }

    /**
     * Delete question.
     * @param $questionId int
     * @return bool
     */
    public function delete($questionId) 
    {
        try {
            $sth = $this->dbh->prepare("DELETE FROM questions WHERE id = ?");
            $sth->execute(array($questionId));
            return ($sth->rowCount());  
        } catch (Exception $e) {
            $this->log($e->getMessage());
        }
        return false;
    }

    /**
     * Sort all questions.
     * @param $questions associative array [questionId] => [order]
     * @return bool
     */
    public function sortAll($questions) 
    {
        try {
            $this->dbh->beginTransaction();
            $sth = $this->dbh->prepare("
                UPDATE questions SET `order` = ? WHERE id = ?
            ");
            foreach ($questions as $questionId => $order) {
                $sth->execute(array($order, $questionId));
            }
            return $this->dbh->commit();
        } catch (Exception $e) {
            $this->dbh->rollBack();
        }
        return false;
    }

    private function log($message) 
    {
        error_log($message, 3, 'logs.txt');
    }
}

The code works with the following tables and columns:

Questions(id: int, question: text, order: int, test_id: int) 
Choices(id: int, choice: text, correct: enum('Y','N']), question_id: int)

From what I understood, with TDG, all the SQL for accessing a single table will be in the class, but what happens when the table relies on another table? For example, Questions table has a relationship with the Choices table.

If I want to retrieve all the questions with their choices, should all the logic be placed in this single class or should I have another class like ChoiceGateway?

share|improve this question
    
I'm not familiar with the pattern itself but from martinfowler.com/eaaCatalog/tableDataGateway.html I can deduce that Choice should have separate TDG class and question should be buld by some kind of quiestion builder which would use both Question and Choice gateways. – ghispi Apr 27 '15 at 10:43

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Browse other questions tagged or ask your own question.