At a certain point of my development as a PHP programmer, I was building MVC applications by-the-book, without understanding the ins-and-outs. I did what I was told: fat model, thin controller. Don’t put logic in your views. What I didn’t understand was how to create a cohesive application structure that allowed me to express my business ideas as maintainable code, nor did I understand how to really separate my concerns into tight layers without leaking low-level logic into higher layers. I’d heard about SOLID principles, but applying them to a web app was a mystery.
In this series, we’ll build a quiz application using these concepts. We’ll separate the application into layers, allowing us to substitute components: for example, it’ll be a breeze to switch from MongoDB to MySQL, or from a web interface to a command-line interface.
Why MVC Isn’t Enough:
MVC, which stands for Model-View-Controller, is a powerful design pattern for web applications. Unfortunately, with its rise to buzzword status, it has been taken out of context and used as a miracle cure. It’s become standard practice to use MVC frameworks, and many developers have succeeded in using them to seperate display logic and domain logic. The trouble is that developers stop there, building quasi-object-oriented systems at best and procedural code wrapped in classes–often controllers–at worst.
In building our quiz app, we’ll be using the Domain Model pattern described in Martin Fowler’s Patterns of Enterprise Application Architecture. Domain Model is just a fancy way of saying that we’ll be using an object-oriented approach to designing the system: a web of objects with different responsibilities that as a whole, will comprise our application.
The Domain Model approach uses “entity” objects to represent information in the database; but instead of having our object-oriented code mimic the database, we’ll have the database mimic our object-oriented design. Why? Because it allows us to build good object-oriented code. This mapping, called Object-Relational Mapping, is a large subject, and outside of the scope of this article. Luckily, there are several mature libraries available in PHP that solve this problem. The most mature of these, and my personal favorite, is Doctrine. We’ll be side-stepping the issue entirely by manually writing the specific mapping code we need for this article.
Even when using the Domain Model pattern, there is still the problem of performing operations that require multiple classes to work together. We’ll be solving this with the Service Layer pattern.
The Service Layer Pattern:
Correct object-oriented design dictates that you should write decoupled code. Each class should have a single responsiblity. How, then, do we combine these independent classes to perform our business logic?
The Service Layer pattern addresses this problem. We group all our system’s operations (signing up, purchasing a product, solving a quiz) into service classes, one service per operation or group of closely-related operations. We decouple these service classes from the classes to which they delegate. This allows us to reuse the services between different use-cases, say the web interface and the CLI interface, the front- and back-end interfaces, and so on.
Getting Started:
We’ll be using Slim as our MVC framework. Slim is a light-weight framework that’s easy to learn, and perfect for our simple app. As you’ll see in the next article, when we write controller code, it’ll be easy for you to replace Slim with any framework you prefer. We’ll install Slim with Composer. Create a directory for the project with the following composer.json
file:
{
"require": {
"slim/slim": "2.*"
}
"autoload": {
"psr-O": {"QuizApp\\": "./lib/"}
}
}
Coding the Service Class:
We’ll need a service for handling the quiz flow: choosing a quiz, checking the user’s answers, and so on. This service will contain the bulk of the business logic of the application. The rest of the code will solve more technical, specific problems, such as accessing the database.
Let’s define an interface for the service. Create a file lib/QuizApp/service/QuizInterface.php
with the following contents:
<?php
namespace QuizApp\Service;
interface QuizInterface
{
/** @return Quiz[] */
public function showAllQuizes();
public function startQuiz($quizOrId);
/** @return Question */
public function getQuestion();
/** @return bool */
public function checkSolution($id);
/** @return bool */
public function isOver();
/** @return Result */
public function getResult();
}
Most of the operations should speak for themselves, but getQuestion()
and getResult()
might not be so clear. getQuestion()
returns the next question for the user to answer. getResult()
returns an object with information about the number of correct and incorrect answers, and whether the user passed the quiz.
Before we implement this service, we should define the mapper interface, as the service will need to use it. The service needs two operations: find()
which returns a single quiz by ID, and findAll()
.
<?php
namespace QuizApp\Mapper;
interface QuizInterface
{
/** @return \QuizApp\Entity\Quiz[] */
public function findAll();
/**
* @param int $i
* @return \QuizApp\Entity\Quiz
*/
public function find($i);
}
These operations return objects of the class \QuizApp\Entity\Quiz
, which represents a single quiz. The Quiz
class, in turn, contains \QuizApp\Entity\Question
objects, which represent quiz questions. Let’s implement these before returning to the service.
<?php
namespace QuizApp\Entity;
class Question
{
private $id;
private $questions;
private $solutions;
private $correctIndex;
/**
* @param string $question
* @param string[] $solutions
* @param int $correctSolutionIndex
*/
public function __construct ($question, array $solutions, $correctSolutionIndex)
{
$this->question = $question;
$this->solutions = $solutions;
$this->correctIndex = $correctSolutionIndex;
if (!isset($this->solutions[$this->correctIndex])) {
throw new \InvalidArgumentException('Invalid index');
}
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function getQuestion()
{
return $this->question;
}
public function getSolutions()
{
return $this->solutions;
}
public function getCorrectSolution()
{
return $this->solutions[$this->correctIndex];
}
public function isCorrect($solutionId)
{
return $this->correctIndex == $solutionId;
}
}
Notice that, in addition to its getters and setters, \QuizApp\Entity\Question
has a method isCorrect()
for checking if a certain answer to the question is correct.
And the \QuizApp\Entity\Quiz
class:
<?php
namespace QuizApp\Entity;
class Quiz
{
private $id;
private $title;
private $questions;
/**
* @param string $title
* @param Question[] $questions
*/
public function __construct($title, array $questions)
{
$this->title = $title;
$this->questions = $questions;
}
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
public function getTitle()
{
return $this->title;
}
public function getQuestions()
{
return $this->questions;
}
}
And the \QuizApp\Service\Quiz\Result
class:
<?php
namespace QuizApp\Service\Quiz;
class Result
{
private $correct;
private $incorrect;
private $passScore;
public function __construct($correct, $incorrect, $passScore)
{
$this->correct = $correct;
$this->incorrect = $incorrect;
$this->passScore = $passScore;
}
public function getCorrect()
{
return $this->correct;
}
public function getIncorrect()
{
return $this->incorrect;
}
public function getTotal()
{
return $this->correct + $this->incorrect;
}
public function getPassScore()
{
return $this->passScore;
}
public function hasPassed()
{
return $this->correct >= $this->passScore;
}
}
Writing a Placeholder Mapper:
We need a concrete \QuizApp\MapperMapper\MapperInterface
class for the service to use. Let’s define a dummy implementation for the time being, so that we can test the code, before writing a real mapper that’ll access the MongoDB database. The dummy uses hard-coded ‘\QuizApp\Entity\Question’ objects to return from the find($id)
and findAll()
methods.
<?php
namespace QuizApp\Mapper;
class Hardcoded implements QuizInterface
{
private static $MAP = array();
/** @return \QuizApp\Entity\Quiz[] */
public function findAll()
{
return [ $this->find(0), $this->find(1) ];
}
/**
* @param int $id
* @return \QuizApp\Entity\Quiz
*/
public function find($id)
{
if (isset (self::$MAP[$id])) {
return self::$MAP[$id];
}
$result = new \QuizApp\Entity\Quiz(
'Quiz' . $id, [
new \QuizApp\Entity\Question(
'What colour was George Washington\'s white horse?',
[ 'White', 'Gray', 'Yellow', 'All of the above' ],
0
),
new \QuizApp\Entity\Question(
'Who\'s buried in Grant\'s tomb?',
[ 'Grant', 'George Washington', 'George Washingtion\'s horse', 'All of the above' ],
0
),
]
);
$result->setId($id);
self::$MAP[$id] = $result;
return $result;
}
}
The class implements the interface by returning a couple of hard-coded Quiz objects. It uses the $MAP
static property as an Identity Map to ensure the class returns the same objects each time it’s called.
Conclusion:
In this first part of our Pratical OOP series we began developing our quiz application. We discussed MVC and why it is not a silver bullet; we covered the Domain Model and Service Layer design patterns; we sketched out the interface for our quiz service, which will contain the logic behind a user solving a quiz; we modeled the quizes and questions as entities; and we created a dummy mapper for looking up quizzes from the “database,” which will come in handy in part two.
Stay tuned! Next time we’ll be be fleshing out our application, writing the service class, and a real database mapper that will connect to MongoDB. Once we have that in place, you’ll see how easy it is to write our controllers and views, and how our elegant design keeps the “M,” the “V,” and the “C” separate, maintainable, and extensible. You can find the full source code for this part here.
Frequently Asked Questions (FAQs) about Building a Quiz App
What are the key features to consider when building a quiz app?
When building a quiz app, there are several key features to consider. First, the app should have a user-friendly interface that is easy to navigate. This includes clear instructions, intuitive controls, and a clean, uncluttered design. Second, the app should offer a variety of question types, such as multiple choice, true or false, and fill in the blank. This will keep the quizzes interesting and challenging for users. Third, the app should include a scoring system that provides immediate feedback to users. This can help users track their progress and identify areas where they need to improve. Lastly, the app should have a robust database of questions. This will ensure that users have a wide range of topics to choose from and that the quizzes remain fresh and engaging.
How can I ensure that my quiz app is engaging and fun for users?
To ensure that your quiz app is engaging and fun for users, consider incorporating elements of gamification. This could include things like leaderboards, badges, or rewards for achieving certain scores. You could also include a time limit for each question to add a sense of urgency and excitement. Additionally, consider including a variety of multimedia elements in your quizzes, such as images, audio clips, or videos. This can help to make the quizzes more interactive and visually appealing. Lastly, consider including social sharing features so that users can share their scores and challenge their friends to beat them.
What programming languages are commonly used to build quiz apps?
The choice of programming language for building a quiz app largely depends on the platform you’re targeting. For Android apps, Java and Kotlin are commonly used. For iOS apps, Swift and Objective-C are popular choices. If you’re looking to build a web-based quiz app, you might use JavaScript along with HTML and CSS. For cross-platform apps, frameworks like React Native or Flutter, which use JavaScript and Dart respectively, are often used. It’s important to choose a language and framework that you’re comfortable with and that suits the requirements of your app.
How can I monetize my quiz app?
There are several ways to monetize a quiz app. One common method is through in-app purchases. This could include things like premium quizzes, additional question packs, or ad-free versions of the app. Another method is through advertising. This could include banner ads, interstitial ads, or video ads. You could also consider a freemium model, where users can download and use the app for free, but must pay to unlock additional features or content. Lastly, you could consider partnering with educational institutions or companies to offer branded quizzes or sponsored content.
How can I ensure that my quiz app is accessible to all users?
To ensure that your quiz app is accessible to all users, it’s important to follow best practices for accessibility in app design. This includes things like using large, easy-to-read text, providing alternative text for images, and ensuring that the app is fully navigable using a keyboard or voice commands. You should also consider including features like a high-contrast mode for visually impaired users, or closed captions for audio content. It’s also important to test your app with a variety of users to identify and fix any accessibility issues.
How can I protect the data of my quiz app users?
Protecting user data is a critical aspect of any app, including quiz apps. This can be achieved by implementing strong security measures such as encryption for data in transit and at rest, using secure coding practices to prevent common vulnerabilities, and regularly updating and patching your app to protect against new threats. It’s also important to be transparent with users about how their data is used and stored, and to comply with all relevant data protection laws and regulations.
How can I promote my quiz app to reach a wider audience?
There are several strategies you can use to promote your quiz app and reach a wider audience. These include app store optimization (ASO), which involves optimizing your app’s title, description, and keywords to improve its visibility in app store search results. You can also use social media marketing, content marketing, and influencer marketing to raise awareness of your app. Paid advertising, such as Google AdWords or Facebook Ads, can also be effective. Finally, don’t underestimate the power of word-of-mouth marketing – encourage your users to share your app with their friends and family.
How can I keep my quiz app content fresh and engaging?
Keeping your quiz app content fresh and engaging is crucial for retaining users. One way to do this is by regularly updating your question database with new and interesting questions. You could also consider adding new features or game modes to keep users interested. Another strategy is to use user-generated content – for example, you could allow users to create and share their own quizzes. Finally, consider using data analytics to understand what types of questions and topics are most popular with your users, and focus on creating more of this type of content.
How can I test the usability of my quiz app?
Testing the usability of your quiz app is crucial for ensuring a positive user experience. This can be done through a variety of methods, including user testing, where you observe real users interacting with your app and note any difficulties or frustrations they encounter. You can also use A/B testing to compare different versions of your app and see which one performs better. Tools like heatmaps and session recordings can also provide valuable insights into how users interact with your app. Finally, don’t forget to regularly gather and act on user feedback.
How can I measure the success of my quiz app?
Measuring the success of your quiz app can be done through a variety of metrics. These might include the number of downloads, the number of active users, user retention rates, and user engagement rates (such as the number of quizzes completed or the average time spent in the app). You might also consider user feedback and reviews, as well as any revenue generated through in-app purchases or advertising. It’s important to regularly monitor these metrics and use them to inform your ongoing app development and marketing strategies.
Moshe Teutsch is a freelance web developer and entrepreneur. He specializes in PHP programming and is currently available for hire. In his spare time he enjoys playing chess, singing, writing, reading, philosophizing, and coding in esoteric programming languages.