User Tools

Site Tools


2019-07-23

Meeting Notes: Tue 23rd July 2019

Welcome! This is the notes page for our fourth learn-to-code-from-scratch meeting in The Avalon, Kent Road. The plan is to run something like this every two weeks to get people up and running with basic web development and system admin.

Scratch Notes

Adding this to the top of your programs will force errors to be displayed (as opposed to hiding them from general users). It might help find the source of your problem if you get an 'Error 500'.

error_reporting(E_ALL);
ini_set("display_errors", 1);

Learn to Code: What we'll do

Just like last month, I'll spend some time taking anyone new through the basic set up steps. For those that are all set up and ready to go - we'll work on the following:

  1. Create a simple quiz page. When you visit it, it chooses a random question from a list you've supplied earlier and asks for the answer in a form then checks if it's correct or not.
  2. We'll build this using a simple array to begin with, then switch to getting the information from a database.

Getting Started: The basic steps

Skip through these if you're ready to start directly on the above, otherwise, these are the things we'll do, in order:

  1. Setting up an Amazon EC2 instance - once that is complete you now have your own server, and you can securely connect to it using Putty (or directly via Linux/macOS)
  2. Setting up your server with Apache and MySQL - the server doesn't have much software installed, just a basic terminal, these steps install Apache (the webserver) and MySQL (the database). At this step we also downloaded Adminer (a GUI to manage the database, similar to PHPMyAdmin). Then we went through the Amazon AWS interface, and set up Security Groups for the webserver (HTTP) - this is basically Amazon's equivalent of a firewall, we had to open a port to allow access to our IP in a web browser
  3. Writing your first hello world program - Now we started with our very first simple program.
  4. Editing files remotely on the server - Then we looked at ways to connect directly to the server and edit files using Notepad++ (or by mounting a remote directory in Linux/macOS)
  5. An example of a basic HTML form - We created a basic HTML form and showed how to access it's elements in PHP once it had been submitted
  6. Some example code in PHP to connect to your MySQL server - Some others went ahead and got the PHP code working that connects to a database and pulls data out, though we stopped there as we hadn't actually put data into the database yet - maybe next time!

Basic Quiz

This uses elements from the basic form that we've already seen. We'll start just using an array of questions, then move to populating an array from a database.

First, the questions. Create a file called questions.php and enter something like this:

<?php
 
$questions[0]['question'] = "What was originally considered to be the ninth planet from the Sun but after 1992 had its status as a large planet changed?";
$questions[0]['answer'] = "Pluto";
 
$questions[1]['question'] = "How many days are there in February in a leap year?";
$questions[1]['answer'] = "29";
 
$questions[2]['question'] = "Give the four initials of the organisation responsible for maintaining a database of drivers in Great Britain?";
$questions[2]['answer'] = "DVLA";
 
$questions[3]['question'] = "Which dance move, in which the dancer moves backwards, was made popular by Michael Jackson?";
$questions[3]['answer'] = "Moonwalk";
 
?>

Now, a basic page to choose a question at random, and wait for the answer, let's call it quiz.php:

<?php
 
require("questions.php");
 
$size_of_question_list = sizeof($questions);
 
// check if we are in the middle of answering a question (if we are, then
// the random_number will already be set), otherwise, choose a number
 
if(empty($_REQUEST['random_number'])) {
   $random_number = rand(0, $size_of_question_list);  // there is a bug here
} else {
   $random_number = $_REQUEST['random_number'];
}
 
 
?>
 
<h1>Quiz</h1>
 
<?php
 
if(!empty($_REQUEST['user_answer'])) {
   $user_answer = $_REQUEST['user_answer'];
   $question_id = $_REQUEST['random_number'];
   echo "<p>You answered '$user_answer' for Question #$question_id</p>";
 
   if($user_answer == $questions[$question_id]['answer']) {
      echo "Correct :)";
   } else {
      echo "Wrong :(";
   }
 
}
 
?>
 
<p><strong>Question: </strong><?= $questions[$random_number]['question']; ?></p>
<form method='post' action='quiz.php'>
<input type='hidden' name='random_number' value='<?= $random_number; ?>' />
<input type='text' name='user_answer' />
<input type='submit' name='answer_button' value='Answer'/>
</form>
 
<p><a href='quiz.php'>Another random question</a></p>

Getting the questions from a database

First, lets design a very simple database that can hold both the questions and answers. We'd normally have a table for questions and a table for answers (especially if some questions had multiple answers) but for now we'll stick to one table.

We need:

  1. A question ID (so we can match questions to answers) this'll be a number so we can use numbered arrays
  2. A question (a text field to record the question in) lets say 2000 characters long at most
  3. An answer (so we know if the user got it right) lets say 500 characters long at most
  4. A time/date of when the question was last asked

Why the time and date? This'll let the 'random experience' be a bit less repetitive. More on that later.

You can create this manually using the mysql client, or in Adminer (instructions on installing it are here).

A quick way to create the table in Adminer is to copy and paste this SQL into the SQL Command section:

CREATE TABLE `quiz_questions` (
  `question_id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `question` VARCHAR(2000) NOT NULL,
  `answer` VARCHAR(500) NOT NULL,
  `last_asked` datetime NOT NULL
);

That'll create the table for you.

And now, this SQL will add some test questions to it (or you can enter some yourself by clicking on the newly created “quiz_questions” table and clicking 'New Item'):

INSERT INTO `quiz_questions` (`question`, `answer`, `last_asked`)
VALUES ('What was originally considered to be the ninth planet from the Sun but after 1992 had its status as a large planet changed?', 'Pluto', now());
INSERT INTO `quiz_questions` (`question`, `answer`, `last_asked`)
VALUES ('How many days are there in February in a leap year?', '29', now());
INSERT INTO `quiz_questions` (`question`, `answer`, `last_asked`)
VALUES ('Give the four initials of the organisation responsible for maintaining a database of drivers in Great Britain?', 'DVLA', now());
INSERT INTO `quiz_questions` (`question`, `answer`, `last_asked`)
VALUES ('Which dance move, in which the dancer moves backwards, was made popular by Michael Jackson?', 'Moonwalk', now());

Let's create a new file, this one called questions_from_db.php - we'll be using some code from the connecting to your DB sample:

<?php 
 
$host = "localhost";
$username = "coding_user";
$password = "cheese";
$db_name = "coding"; 
 
$mysql_string = "mysql:host=$host;dbname=$db_name";
 
$db_connection = new PDO($mysql_string, $username, $password);
 
$sql = "select * from quiz_questions";
$rs = $db_connection->query($sql);
$rows = $rs->fetchAll(PDO::FETCH_ASSOC);
 
foreach($rows as $row) {
  $questions[$row['question_id']]['question'] = $row['question'];
  $questions[$row['question_id']]['answer'] = $row['answer'];
 
}
 
?>

Putting it all together

Now, if everything has went to plan, you can replace the:

require("questions.php")

line from your quiz.php file with:

require("questions_from_db.php")

and now, when you enter new questions to the database, they'll be available in your quiz.

We finished?

This might be too much for one session, but the great thing about the web is how easy it is for us to connect with each other.

You can make your questions, in your database, available to anyone else's quiz program with this bit of code, call it export.php:

<?php
header("Content-Type: application/json");
require("questions_from_db.php");
echo json_encode($questions);
?>

Now, you can share your quiz URL with others. The full URL will be http://YOUR_IP_ADDRESS/export.php

So, back to the quiz.php file (actually anyone else's quiz.php) instead of the line that reads

require("questions.php")

This code will connect to a fellow coder's IP address, download their quiz questions and format them in a way your program can understand them:

$json_data = file_get_contents("http://YOUR_IP_ADDRESS/export.php");
$questions = json_decode($json_data, true);

Extra tasks

I've decided to try and separate out the complete beginners from the ones who've already got their server set up etc. Going forward I'm going to try and put 'Level 0' or 'Level 1' on the titles of events. You can read up on this on the Lesson Levels page.

So until that is done, for those of you who are waiting while I'm setting others up, try the following:

  • Ask someone else for their IP and see if you can load their questions into your quiz
  • Checkout The Moonwalk problem below
  • Working with RSS - I made some brief notes on RSS and how to access it with PHP

The Moonwalk

Prep

Before we begin, let's get your quiz to focus just on this question while we test it (so it does't choose them at random).

Add the question if you don't have it already, then find the ID of the question by looking at your database. Then add a new line after the if statement that assigns the random number (around line 14 depending if you've made changes, just make sure it's not inside the if statement) that reads (replace 4 with your Micheal Jackson question ID):

$random_number = 4;

Remember to take this line out when you want to go back to random selection.

Now your quiz will continually ask this question while we test some changes.

Case sensitive

One of my example question/answers is “Which dance move, in which the dancer moves backwards, was made popular by Michael Jackson?”, if someone enters all lower case 'moonwalk' it'll by wrong. Why? Because the “==” comparison is exact (i.e. case sensitive).

We could fix that by changing the

if($user_answer == $questions[$question_id]['answer']) {

line to use the strcasecmp() function. You can read about that function here:

https://www.php.net/manual/en/function.strcasecmp.php

It compares one string to another, ignoring case and returns 0 if they match. So, this would work:

if(strcasecmp($user_answer,$questions[$question_id]['answer']) == 0) {

This 'equals 0' thing might be a little confusing, especially if we go back to this code after a couple weeks. So maybe we could try an alternative approach that is a little easier to read. We could also use the strtolower() function and convert the user's answer (and our answer) to lower case, then use the old comparision, something like:

if(strtolower($user_answer) == strtolower($questions[$question_id]['answer'])) {

That should also work. Whichever you use is up to what you prefer (or how you might use the answers later).

The 'the' problem

For example, if I give “The Moonwalk” as an answer, it'll be wrong. Why?

Now, let's look at the line that checks if it matches an answer:

if($user_answer == $questions[$question_id]['answer']) {

Using “==” we asked it “Does the user's answer exactly match the answer in the database?”, because “The Moonwalk” isn't “Moonwalk” it fails (incorrect).

Instead we could ask “Does the user's answer contain the same word we have as the answer in the database?”, in which case “The Moonwalk” would contain it.

So how do we do that? PHP doesn't have a function that does exactly that, but it does have a function that tells us which character a certain string starts at. strpos(). You can read it's manual entry here:

https://www.php.net/manual/en/function.strpos.php

So, if we use that function, we should flip the question to be: “Does the questions's answer appear in the text the user has submitted?”

Can you get it working? We'd have to do something like this:

if(strpos($user_answer, $questions[$question_id]['answer']) !== false) {

This is a bit more complex, but it's quite a common use of strpos. It's saying “Does the database answer appear in the user's answer?” and because of the way strpos works (it returns false if not found) we check to make sure it's not false.

We use a special operator “!==” here rather than the “!=” operator because it's possible strpos will return “0” i.e. “Yes, I found the string you are searching for and it's at the 0th character” - so we want to compare the type of variable that is return (i.e. true/false) rather than assume 0 is false and 1 is true in this particular case. Don't worry about this too much, it's not too common outside of this particular use case.

Lastly, we'll also make it not case-sensitive, so the final line you should use is:

if(strpos(strotolower($user_answer), strtolower($questions[$question_id]['answer'])) !== false) {

You might want to break that into a few lines to make it easier to read/understand later:

$AnswerUser = strtolower($user_answer);
$AnswerDb = strtolower($questions[$question_id]['answer']);
 
if(strpos($AnswerUser, $AnswerDb) !== false) {

Functions

How do I know which function to use? It's basically a case of reading the manual. I know we are working with strings of characters (the answers), so I look at the index of string functions:

https://www.php.net/manual/en/ref.strings.php

We're doing a comparison, so I just searched (CTRL + F) the list for comparison functions. There are a few choices, but most are overkill, so I picked the simplest one. Same for case sensitive functions, I searched for 'case' and picked something simple and appropriate.

If we were working with numbers (and doing something more complex than equal to or less than etc), we'd want to look at the math functions:

https://www.php.net/manual/en/ref.math.php

Troubleshooting


I get this error on the page sometimes: Notice: Undefined offset: 4 in /var/www/starflyer/html/tmp/coding/quiz.php on line 49

You've encountered the bug on line 14. The size of the question array is 4, but it contains elements indexed 0 to 3. So if we use the rand() function it needs to be picking a number from 0 to the size of the array minus 1 (so it returns at most 3, not 4). Change the random number line (14) to:

$random_number = rand(0, $size_of_question_list-1);

I can't log into Adminer (or I'm getting access denied errors). Make sure the were set properly (there was a mistake on the instructions on the wiki). Log into your server and run this to reset your password:

  • Connect to the database as the root (admin) user:
sudo mysql -u root
  • Then, inside the mysql client, we created the database and created a user called ‘coding_username’, with the password ‘cheese’ and gave it access to the newly created ‘coding’ database:
create database coding;
grant all on coding.* to coding_username@localhost identified by 'cheese';
quit

Then use these details:

username coding_username
database coding
password cheese
host localhost

My page is blank.

Make sure you have error reporting turned on. Add these lines to the very top of your page (after the <?):

error_reporting(E_ALL);
ini_set("display_errors", 1);

Attendees

  1. Abdullah Khan
  2. Abhimanya Koushik Vemuri
  3. Abhishek Singh
  4. Alan Kennedy
  5. Araceli Pérez
  6. Ayse Busra Parnell
  7. Casian Florin Dănilă
  8. Chris Cavani
  9. Connor Stewart
  10. Donald
  11. Erika Anderson
  12. Ewan Miller
  13. Gordon Mckerrell
  14. JJ
  15. Jonathan Minton
  16. Karola Jehodek
  17. Kathryn M
  18. MaryF
  19. Neil McKillop (Event Organiser)
  20. Revathi Lalam
  21. Tapan Parida
2019-07-23.txt · Last modified: 2019/07/23 16:30 by nmckillop