Source Code for /public/05-accepting-input/index.php
<?php
require('../inc/functions.php');
add_header('Lesson 5: Accepting Input');
?>
<div class="objectives">
<h2>Objectives</h2>
<ul>
<li>Understand the difference between <code>GET</code> and <code>POST</code> requests</li>
<li>Know to use <code>htmlspecialchars()</code> before outputing anything that came from a user</li>
<li>Accept input from a user and display it back to them</li>
<li>Act upon user input: do different things based on what they submitted</li>
<li>Validate user input to ensure it's among the expected values</li>
<li>Think about how JavaScript <code>fetch()</code> requests might be integrated with PHP backend processing</li>
<li>Make a fun quiz!</li>
</ul>
</div>
<p>
One area where PHP really shines is when you want to accept input from your visitors. Feedback forms, commenting,
guestbooks, search boxes, user-generated content and more all work best using server-side code.
</p>
<p class="warning">
There are security implications when working with user input. As you go through the lesson, keep an eye out for
warnings like this one with information about the relevant dangers; or if you're familiar them skim the
<a href="/appendix-security/">appendix on security</a> for a summary of the risks.
</p>
<h2 id="get-and-post"><code>GET</code> and <code>POST</code> requests</h2>
<p>
User input comes in many forms - the web address they've typed in, the contents of forms, even that's in their
cookies or their browser's name and version number. But for the time being we're going to focus on the data they
supply via <code>GET</code> and <code>POST</code> requests.
</p>
<h3><code>GET</code> parameters</h3>
<p>
If a web address contains a question mark (<code>?</code>), then everything after it (up until any <code>#</code> symbols)
are called the "query string". If it has any ampersands (<code>&</code>) or semicolons (<code>;</code>), PHP will
split the query string into variables and values around these. Those variables all look like
<code>$_GET['variable_name']</code>.
</p>
<p class="tip">
If you've spotted that <code>$_GET</code> must be an array, well done!
</p>
<p>
Consider the following partial web address:
</p>
<p>
<code>/05-accepting-input/whats-your-name.php?<strong>name=Dan</strong>&<strong>pronoun=him</strong></code>
</p>
<p>
When the user visits that page, <code>whats-your-name.php</code> will have access to the following variables:
</p>
<ul>
<li><code>$_GET['name']</code> will be <code>'Dan'</code></li>
<li><code>$_GET['pronoun']</code> will be <code>'him'</code></li>
</ul>
<h3><code>POST</code> parameters</h3>
<p>
<code>POST</code> parameters are sent in the body of the request, rather than in the URL. They're associated with
forms, but they can be sent in other ways too.
</p>
<p class="warning">
Just because the user can't easily see their <code>POST</code> parameters doesn't mean the y can't tamper with them.
They can, so you have to be just as careful with them as with any other user input. We'll look at how to stay safe
in a little bit.
</p>
<p>
The parameters sent in a <code>POST</code> request are stored in the <code>$_POST</code> array.
</p>
<p class="tip">
If you don't care what kind of input you're getting, the <code>$_REQUEST</code> array holds both the
<code>GET</code> <em>and</em> <code>POST</code> arrays (sometimes among other data).
</p>
<h2 id="whats-your-name">Asking the user's name</h2>
<p>
Let's start by asking the user's name and repeating it back to them. Create a new file called
<code>whats-your-name.php</code> with the following code:
</p>
<?php code_block_from_file( '05-accepting-input/whats-your-name.php', 'php' ); ?>
<p>
Here's how it works:
</p>
<ol>
<li>
On the first visit to the page, neither <code>$_GET['name']</code> nor <code>$_GET['pronoun']</code>
are set, so we skip over the initial block of code and start rending the HTML.
</li>
<li>
This means that <code>$greeting</code> is not set, so skip ahead to rendering "Hi! Who are you?" and the form.
</li>
<li>
The HTML <code><form></code> has two attributes:
<ul>
<li>
<code>method="get"</code> means that the form will be submitted using a <code>GET</code> request; that is -
the user's input will appear in the address bar as part of the URL.
</li>
<li>
<code>action="whats-your-name.php"</code> means that the form will be submitted to the <code>whats-your-name.php</code>
page - i.e. the user will come back to the same page, but now with the GET parameters added to the URL.
</li>
</ul>
</li>
<li>
The form has two fields with a <code>name="..."</code> attribute. These will become the names of the variables once they're
passed to the PHP script.
</li>
<li>
When they submit the form they'll come back to the same page again, but this time <code>$_GET['name']</code> and
<code>$_GET['pronoun']</code> will be set. The code will make a variable called <code>$greeting</code> containing a
string that acknowledges the user's name and the subject pronoun associated with the value they picked from the dropdown.
</li>
<li>
The page will then render the greeting, and a link to the same page again so the user can have another go!
</li>
</ol>
<p class="warning">
We use <code>htmlspecialchars()</code> to escape any HTML that the user entered, making it safe to display in the browser.
This prevents Cross-Site Scripting (XSS) attacks. If we didn't do this, a malicious user could enter HTML code that could
run <em>their</em> JavaScript on <em>our</em> page. If they could trick another user into visiting a page which they'd
tampered with, they could potentially steal their cookies or other sensitive data.<br>
Try going to <a href="whats-your-name.php?name=%3Cem+style%3D%22color%3Ared%3B%22%3Ea+wannabe+hacker%3C%2Fem%3E&pronoun=them"><code>?name=%3Cem style%3D%22color%3Ared%3B%22%3Ea wannabe hacker%3C%2Fem%3E&pronoun=them</code></a>
and see that the page is protected. If you want to, try removing the <code>htmlspecialchars()</code> function and see what happens!
<a href="/appendix-security/#xss">More info in the appendix</a>.
</p>
<p>
It's probably easier to experience than explain, so <a href="whats-your-name.php">go introduce yourself</a> and see
what happens!
</p>
<p class="warning">
In this instance, we didn't perform any validation of the user's input. You can see the effect of this by editin the URL
in the address bar to change your pronoun to one that isn't in the list! Try e.g.
<a href="whats-your-name.php?name=Dan+Q&pronoun=that+sloppy+coder"><code>?name=Dan+Q&pronoun=that+sloppy+coder</code></a>!
If that's something you care about, you'll need to make sure your code checks that the user's input is valid before using it.
<a href="/appendix-security/#xss">More info in the appendix</a>.
</p>
<p class="tip">
Could you add a "(no pronouns)" option to the dropdown? A user who selects "(no pronouns)" should see their own name used a second
time, rather than "him"/"her"/"them" etc.
<small>(<a href="/appendix-samples/whats-your-name-2.php">🏁 view solution</a>)</small>
</p>
<h2 id="fruits-and-filters">Fruits and filters revisited</h2>
<p>
In lesson 3 we came up with the idea for
<a href="https://php.danq.dev/demo-harness.php?file=public/appendix-samples/random-image-with-effect.php">a web page that would randomly select a fruit and a CSS effect and combine them</a>.
Let's revisit that, but this time we'll let the user choose which fruit and which CSS effect they want to use. This time,
we'll use a form with a <code>POST</code> request:
</p>
<p class="tip">
In general, the rule is that <em>idempotent</em> requests (ones that don't change anything, where if you did the same thing
twice it would work the same way) should use a <code>GET</code> request, <code>POST</code> requests should be used for
requests that change things. So a search form should use a <code>GET</code> request because searching for the same thing
twice should yield the same results, but a login form should use a <code>POST</code> request because it's likely to result
in a cookie being set or changed (plus: users don't like their passwords appearing in the URL bar in case somebody's standing
behind them!). We're ignoring that "best practice" guideline for now, but you should be aware of it.
<a href="/appendix-security/#xsrf">More info in the appendix</a>.
</p>
<?php code_block_from_file( '05-accepting-input/fruits-and-filters.php', 'php' ); ?>
<p>
Here's how it works:
</p>
<ol>
<li>
When the page loads, the <code>user_requested_a_fruit_and_filter()</code> is used to check if a request has been made.
It looks for the <code>$_POST</code> variables that are set by submitting the form.
</li>
<li>
If the user <em>has</em> requested a fruit and a filter, the <code>show_a_filtered_fruit()</code> function is used to display
the fruit image with the filter applied. It uses <code>get_fruits()</code> and <code>get_filters()</code> to get the list of
fruits and filters, then finds the correct-numbered fruit and filter from the list, based on the user's request. Then it
displays that image.
</li>
<li>
Either way, the page then renders a form with two <code><select></code> (dropdown) fields. Each lists the relevant
fruits/filters, but the <code>value="..."</code> of each <code><option></code> is not the fruit/filter name but its
index (position) in the list. This means a malicious user can't tamper with the form by changing the value into a fruit or
filter of their choice: it has to be one that's in the list.
</li>
</ol>
<p class="warning">
Users who are allowed to select from a list of <em>files on your server</em> are a special case for security.
If we accepted the file name from the user, <em>and</em> we allowed PHP to read and send the file (e.g. with
<code><a href="https://www.php.net/manual/en/function.readfile.php">readfile()</a></code>), the user could tweak
the request to ask for <em>any file on your system</em>, e.g. <code>/etc/passwd</code> or <code>/home/user/my-secrets.txt</code>.
Our program is safe because we're only passing ID numbers around and then using those to look up fruits from the approved list,
but <a href="/appendix-security/#path-traversal">there's a relevant section in the appendix</a> if you want to let your users
specify paths to files on your server.
</p>
<p class="tip">
Wouldn't it be nice if the <code><select></code> boxes retained the values that were last selected, when the page reloads?
To do that, you'll need to add a <code>selected</code> attribute of the <code><option></code> tags. Tip: everything you get
from the user is a string, so you'll need to convert it to an integer before comparing it to the ID number.
<small>(<a href="/appendix-samples/fruits-and-filters-retaining-selection.php">🏁 view solution</a>)</small>
</p>
<p class="tip">
If you enjoy JavaScript, you'll be pleased to know that you can integrate JavaScript and PHP together. Can you make it so that,
if JavaScript is enabled, the form is submitted using a <code>fetch()</code> request, and if PHP sees this come in then it
returns some JSON to tell JavaScript what to update on the page? It should still work even with JavaScript disabled.
<small>(<a href="/appendix-samples/fruits-and-filters-js-enhanced.php">🏁 view solution</a>)</small>
</p>
<h2 id="quiz">Let's make a quiz!</h2>
<p>
Quizzes are a great example of something for which using PHP would be better than using JavaScript, because it means that the
user can't cheat my looking at the source code. Let's make a quiz that tests the user's PHP knowledge with three questions,
and then tells them their score at the end!
</p>
<p>
Create a new file called <code>quiz.php</code>:
</p>
<?php code_block_from_file( '05-accepting-input/quiz.php', 'php', true, true, true ); ?>
<p>
Here's how it works:
</p>
<ol>
<li>
If the user isn't submitting the form, we show the quiz. We loop through an array and show each question and the candidate
answers associated with it. The array also contains the <em>correct</em> answer, but that's never sent to the user's
browser.
</li>
<li>
The <code>name="..."</code> of an <code><input></code> is used by PHP to choose where it appears in the <code>$_POST</code>
array; we use a special syntax with square brackets to tell PHP to store the answers given by the user <em>in an array</em> with
each answer being numbered after the number of the question it belongs to.
</li>
<li>
Once submitted, we score the quiz by looping through the questions again and comparing the <em>correct</em> answer to the
one the user gave. If they match, we increment the score.
</li>
<li>
Throughout, we use the <code><?php echo $_SERVER['REQUEST_URI']; ?></code> as a convenient shorthand for "the URL of
the current page".
</li>
</ol>
<p class="tip">
Could you give the user a <em>ranking</em> based on their score, using a series of <code>if</code> statements? Or even a
<code><a href="https://www.php.net/manual/en/control-structures.switch.php">switch</a></code> statement? Or perhaps you
could show their score as a percentage?
</p>
<p class="tip">
Maybe you would like to tell the user which questions they got wrong, so they can learn from their mistakes?
</p>
<?php
add_footer();
?>