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
?