Build a PHP Study Planner
Build a local PHP form that creates safe, personalized study plans.
Introduction
30 Second Summary
A study session can go off track when the task list ignores how much time you have. A useful plan should fit the topic you want to practise.
In this project, you will build a local Study Planner with PHP. You will use a short form to generate a personalized task list in Safari.
What You'll Build
Your finished page turns three form choices into a personalized study plan that appears immediately beneath the form.
By the end of this project, you'll have:
- A personalized plan that displays your name with the topic you selected.
- An adaptive task list that adds another activity when you choose a longer session.
- Safe form feedback that guides you when a name is blank or the time budget is too short. Typed angle brackets stay visible as text.
- Secret Mission: Add Beginner and Challenge modes that produce different task lists.
Are there any prerequisites?
You need a Mac with internet access and permission to install software. The guide walks you through installing PHP and Visual Studio Code before you build the planner.
Before We Start
Before the hands-on work begins, define what your PHP Study Planner generates and why learning these basics matters to you. This gives every tool and code change ahead a clear purpose.
Set Up PHP and Visual Studio Code
Your study planner needs PHP to execute its code. It also needs Visual Studio Code so you can create each part of the application.
A local development server sends the result to your browser. This step builds that complete local workflow without adding a production web stack.
In this step, get ready to:
- Choose Homebrew or MacPorts based on your Mac.
- Install PHP 8.5. Confirm that the runtime is available.
- Install Visual Studio Code. Launch your first PHP page in Safari.
Choose your package manager
A package manager installs developer tools while handling the files those tools depend on. Your processor type determines the supported path for this project.
- Click the Apple menu in the top-left corner of your screen.
- Select About This Mac.
- Record the listed processor here: your Mac processor.
- Record the listed macOS release here: your macOS version.
Use the Homebrew path for a supported Apple Silicon Mac on a current Apple-supported macOS release. Use the MacPorts path for an Intel Mac or an unsupported macOS release.
You run the installer commands in macOS Terminal. Choose the tab that matches the processor information you recorded.
✔️ Supported Apple Silicon
Homebrew is the primary package-manager path for supported Apple Silicon releases. The installer may request your Mac login password.
- Press Cmd+Space to open Spotlight.
- Type Terminal and press Return to open it.
Expect the installer to take several minutes while it downloads the package manager. Hidden password characters are normal if Terminal requests your password.
- Install Homebrew by running this official installer command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
What does this installer do?
The command downloads Homebrew's official installer. The installer configures the package manager for your Mac.
- Complete any shell setup step printed at the end of the installer.
- Confirm that Homebrew is available by running:
brew --version
What does this command check?
The command asks Homebrew to print its version information. A version response confirms that Terminal can find the package manager.
You should see Homebrew version information in Terminal. That output confirms your primary installation path is ready.
Homebrew command not found?
Review the final lines from the installer for a shell setup step. Complete that step before opening a new Terminal window.
If the command still fails, help me fix a Homebrew installation that is not available in Terminal.
ⓧ Intel or unsupported macOS
MacPorts provides the compatibility path for an Intel Mac or a macOS release outside Homebrew's supported Apple Silicon path. It requires Apple's Command Line Developer Tools before the package installer can run.
- Press Cmd+Space to open Spotlight.
- Type Terminal and press Return to open it.
- Start the Command Line Developer Tools installer by running:
xcode-select --install
What does this command install?
The command asks macOS to install Apple's developer command-line tools. MacPorts uses those tools to prepare software packages.
- Complete the installer that macOS opens.
The developer-tools installer can take several minutes. A progress screen during this wait means macOS is still working.
- Visit the official MacPorts installation page.
- Download the MacPorts 2.12.7 package that matches your macOS release.
- Open the downloaded package installer.
- Complete the package installation.
- Close the current Terminal window.
- Press Cmd+Space to open Spotlight again.
- Type Terminal and press Return to open a fresh window.
- Confirm the MacPorts installation by running:
port version
What does this command check?
The command prints the installed MacPorts version. Opening a fresh Terminal window lets the shell use the path configured by the package installer.
You should see Version: 2.12.7. Your compatibility package-manager path is now ready.
MacPorts command not found?
Confirm that you downloaded the installer matching your exact macOS release. Open a new Terminal window after the installer finishes.
If MacPorts remains unavailable, help me diagnose a MacPorts installation that Terminal cannot find.
Install and verify PHP
The PHP runtime reads .php files. It executes the instructions between PHP tags before sending the result to the browser.
Return to the Terminal window from your package-manager setup. Use the matching tab to install the runtime.
Homebrew
Homebrew installs the current stable PHP formula. The download can take several minutes because the formula includes supporting packages.
- Install PHP through Homebrew by running:
brew install php
What does this command install?
Homebrew downloads the stable PHP formula. It also installs the supporting packages required by the runtime.
Wait until Terminal returns to its normal prompt. That prompt confirms the installation process has finished.
PHP installation stopped?
Check that the Homebrew version command worked in the previous substep. A network interruption can also stop a package download.
For help with the visible message, help me diagnose my Homebrew PHP installation error.
MacPorts
MacPorts installs its PHP meta-port. The command requests your Mac login password because the package manager writes system-managed files.
Your password remains hidden while you type. The installation may take several minutes while MacPorts prepares dependencies.
- Install PHP through MacPorts by running:
sudo port install php
What does this command install?
MacPorts installs the PHP meta-port with administrator permission. The meta-port selects the PHP 8.5 runtime.
Wait until Terminal returns to its normal prompt. That prompt confirms MacPorts has finished processing the installation.
MacPorts installation stopped?
Confirm that port version worked before retrying the PHP installation. Also check that Terminal still has internet access.
For help with the visible output, help me diagnose my MacPorts PHP installation error.
Before you check the runtime, what PHP version do you expect Terminal to report?
- Check the installed PHP version by running:
php -v
What does this command check?
The command prints the active PHP runtime version. The first line reveals which PHP release Terminal finds.
✔️ I see the required PHP version
The first line reports PHP 8.5. That's the runtime ready for your study planner.
ⓧ I see an older PHP version
Terminal is finding an older runtime. Repeat the PHP installation command in the package-manager tab you used above.
- Close the current Terminal window after the installation finishes.
- Open a fresh Terminal window through Spotlight.
- Run the PHP version check again.
- Confirm that the first line now reports PHP 8.5.
ⓧ Command not found
The shell cannot find PHP yet. Return to the package-manager tab above that matches your setup.
- Repeat the PHP installation command in that tab.
- Complete any shell setup instruction printed by the package manager.
- Close the current Terminal window.
- Open a fresh Terminal window through Spotlight.
- Run the PHP version check again.
Still cannot find PHP?
The package manager may be installed in a location missing from your shell path. Use the output from your package-manager installation to identify its setup instruction.
If you need a hand, help me make PHP available in my Terminal path.
Install the editor and launch PHP
Visual Studio Code includes PHP syntax highlighting without requiring an extension. You will add its terminal command before creating the project folder.
- Visit the Visual Studio Code download page.
- Download Visual Studio Code for macOS.
- Open the downloaded .dmg file from your Downloads folder in Finder.
- Drag Visual Studio Code.app into the Applications folder.
- Press Cmd+Space to open Spotlight.
- Type Visual Studio Code and press Return to launch it.
- Press Cmd+Shift+P to open the Command Palette.
- Type shell command into the Command Palette.
- Select Shell Command: Install 'code' and optionally 'copilot' commands in PATH.
- Select No if Visual Studio Code asks whether to install GitHub Copilot CLI.
- Close the Terminal window from your package setup.
Creating the project on your Desktop gives it a predictable location. Finder can open Terminal directly inside that folder.
- Click an empty area of your Desktop.
- Choose File from the Finder menu bar.
- Choose New Folder.
- Enter php-study-planner and press Return.
You should now see the php-study-planner folder on your Desktop.
- Double-click the php-study-planner folder.
- Choose View from the Finder menu bar.
- Select Show Path Bar.
- Control-click php-study-planner in the path bar at the bottom of the Finder window.
- Select Open in Terminal.
A fresh Terminal window opens inside php-study-planner. The folder is now the location used by your next command.
- Open the current folder in Visual Studio Code by running:
code .
What does this command do?
The code command launches Visual Studio Code. The dot tells it to open the folder currently selected in Terminal.
Visual Studio Code should open with php-study-planner shown in the Explorer sidebar.
Code command not found?
Return to Visual Studio Code's Command Palette. Run the shell command that installs code in your path again.
Open a fresh Terminal window before retrying. If it still fails, help me make the Visual Studio Code command available in Terminal.
- Click the file-shaped plus icon at the top of the Explorer sidebar.
- Enter index.php as the file name.
You should see index.php in the Explorer sidebar. The empty file is ready for your first PHP instruction.
- Add the readiness message to index.php by pasting the code below:
<?php echo "PHP is ready"; ?>
What does this code do?
- The PHP opening tag tells the runtime to execute the code that follows.
- The echo statement sends the readiness message to the browser.
- Save index.php by pressing Cmd+S.
- Confirm that the unsaved indicator disappears from the index.php tab.
File missing from Explorer?
Confirm that the Explorer sidebar shows the php-study-planner folder. Check that the file name ends with .php.
If the file was saved elsewhere, help me place index.php inside my php-study-planner folder.
✔️ Awesome, I've got everything!
Your saved index.php contains the complete readiness message.
ⓧ I'd like to double check the full code
<?php echo "PHP is ready"; ?>
Before you start the server, what do you expect Safari to show when PHP executes index.php?
- Switch back to the Terminal window inside php-study-planner.
- Start the PHP development server by running:
php -S localhost:8000
What does this command do?
PHP starts its built-in development server from the current folder. Requests to the local address use index.php automatically.
This server is designed for local development. Keep it bound to localhost.
- Leave the server running in its Terminal window.
- Press Cmd+Space to open Spotlight.
- Type Safari and press Return to open it.
- Enter http://localhost:8000 in Safari's address bar.
- Press Return to load the local page.
Safari shows PHP is ready. Terminal shows a development-server message for the local address.
You've got the full local loop working. PHP executes your file while Safari displays the result.
Local page not loading?
Confirm that the server Terminal remains open. Check that it was started from the php-study-planner folder.
Confirm that Safari uses http://localhost:8000. If the page still fails, help me diagnose my local PHP development server.
Your runtime and editor are ready. Next, you will turn this one-line response into a visible study plan.
Build a Visible Study Plan
Your local PHP page already proves that the development server can execute index.php. That first success gives you a working loop from Visual Studio Code to Safari.
The one-line message cannot show how PHP data becomes a useful page. This step builds one fixed study session so every language feature has a visible result.
In this step, get ready to:
- Build a styled page that can display PHP values.
- Define fixed values for Alex's study session.
- Render two study tasks with a foreach loop.
Build the page shell
HTML gives the planner its visible structure. CSS gives that structure a clear layout.
- Select the existing <?php echo "PHP is ready"; ?> line in the Visual Studio Code editor.
- Replace the selected line by pasting the page shell below:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Study Planner</title>
</head>
<body>
<main>
<h1>PHP Study Planner</h1>
</main>
</body>
</html>
What does this page shell do?
- The head section gives the browser information about the page.
- The title element names the browser tab.
- The main element holds the study planner content.
- Save index.php.
- Reload http://localhost:8000 in Safari.
You'll see the PHP Study Planner heading on a plain white page.
Still seeing the setup message?
Confirm that you replaced the original line in index.php. Make sure the file is saved.
Check that Safari is still using http://localhost:8000.
Help me find why Safari still shows the old PHP message.
The page structure is working. A centered background gives the planner a focused canvas.
- Place your cursor on the line after <title>PHP Study Planner</title> in index.php.
- Add the page background by pasting this style block:
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 16px;
font-family: Arial, sans-serif;
background: #f4f1ff;
color: #201a32;
}
</style>
How does the background work?
- The universal selector makes element sizes include their padding.
- The body selector fills the browser height.
- The grid layout places the planner in the center.
- The background color separates the page from the planner card added next.
- Save index.php.
- Reload the page in Safari.
You'll see the heading centered against a light purple background.
Background still white?
Confirm that the entire style block sits inside the head section. Check that every opening brace has a closing brace.
Help me fix the missing background styles.
A card separates the planner from the page background. Spacing between list items keeps the final tasks easy to scan.
- Place your cursor above the closing </style> tag.
- Add the planner card styles by pasting this CSS:
main {
width: min(680px, 100%);
background: #ffffff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 18px 45px rgba(49, 35, 91, 0.14);
}
li + li {
margin-top: 10px;
}
What styles the planner card?
- The main selector limits the card width on large screens.
- The white background makes the planner stand out.
- The rounded corners soften the card edges.
- The shadow lifts the card from the purple page.
- Save index.php.
- Reload the page in Safari.
You'll see the heading inside a centered white card with rounded corners.
Card missing its styles?
Confirm that the main selector sits before the closing </style> tag. Check the closing brace after the shadow value.
Help me fix the planner card styling.
Add fixed study details
A PHP variable stores one detail for reuse. The page can echo each stored value wherever it belongs.
- Place your cursor above <!DOCTYPE html> in index.php.
- Define the three study details by pasting this PHP block:
<?php
$name = "Alex";
$topic = "Variables";
$minutes = 30;
?>
What do these variables hold?
- The $name variable holds the learner name.
- The $topic variable holds the selected PHP topic.
- The $minutes variable holds the available time as an integer.
- Place your cursor on the line after <h1>PHP Study Planner</h1>.
- Display the stored details by pasting these elements:
<h2><?php echo $name; ?>'s plan</h2>
<p><?php echo $minutes; ?> minutes on <?php echo $topic; ?></p>
How does PHP reach the page?
- Each PHP tag switches from HTML into PHP processing.
- Each echo statement inserts one stored value into the page.
- The surrounding HTML controls where that value appears.
- Save index.php.
- Reload the page in Safari.
You should see Alex's plan above 30 minutes on Variables.
Your first data-driven page is working because PHP values now control the visible plan.
Seeing missing details?
Check that the variable names begin with $. Confirm that each name matches its echo statement exactly.
Help me debug the missing PHP values.
Render the task list
An array keeps several related values in one variable. A foreach loop processes each value in order.
- Place your cursor below $minutes = 30; in the opening PHP block.
- Store the two study tasks by pasting this array:
$tasks = [
"Review the key ideas in $topic",
"Complete one short $topic exercise",
];
What does the task array do?
- The $tasks variable groups the study instructions.
- Each task uses $topic to include the selected subject.
- The array keeps the task order predictable.
- Place your cursor on the line after the study summary paragraph.
- Render every task by pasting this numbered list:
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo $task; ?></li>
<?php } ?>
</ol>
How does the loop build the list?
- The ordered list gives each task a number.
- The foreach loop takes one value from $tasks at a time.
- The $task variable holds the current value.
- The list item displays that current value.
- Save index.php.
Before you reload, how many numbered tasks do you expect PHP to render?
- Reload the page in Safari.
You'll see a styled plan for Alex. The summary reads 30 minutes on Variables above two numbered tasks.
That completes the core rendering loop. PHP data now becomes a visible study plan.
Task list missing?
Confirm that $tasks is defined before the HTML begins. Check that the loop uses the same variable name.
Make sure the ordered list sits inside the main element.
Help me debug the missing PHP task list.
The visible values come directly from the variables at the top of index.php. A quick edit proves that connection.
- Find $name = "Alex"; in index.php.
- Replace Alex with your first name.
- Save index.php.
- Reload the page in Safari.
The plan heading now uses your first name. The topic summary stays the same.
- Replace your first name with Alex in index.php.
- Save index.php.
✔️ Awesome, I've got everything!
Your planner code is in place.
ⓧ I'd like to double check the full code
<?php
$name = "Alex";
$topic = "Variables";
$minutes = 30;
$tasks = [
"Review the key ideas in $topic",
"Complete one short $topic exercise",
];
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Study Planner</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 16px;
font-family: Arial, sans-serif;
background: #f4f1ff;
color: #201a32;
}
main {
width: min(680px, 100%);
background: #ffffff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 18px 45px rgba(49, 35, 91, 0.14);
}
li + li {
margin-top: 10px;
}
</style>
</head>
<body>
<main>
<h1>PHP Study Planner</h1>
<h2><?php echo $name; ?>'s plan</h2>
<p><?php echo $minutes; ?> minutes on <?php echo $topic; ?></p>
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo $task; ?></li>
<?php } ?>
</ol>
</main>
</body>
</html>
Before your final reload, do you think Safari can change Alex's details without another file edit?
- Reload http://localhost:8000 in Safari.
You'll see Alex's plan restored at http://localhost:8000. The summary reads 30 minutes on Variables above two numbered tasks.
The page has no controls for changing those fixed values. That limitation is intentional.
Your fixed planner proves that PHP can turn stored data into a complete page. Next, you'll let the learner personalize it directly in Safari.
Turn the Plan into an Interactive Form
Your hard-coded planner already turns PHP values into a styled page. Every visitor still receives Alex's plan.
This step connects an HTML form to PHP through a POST request. A function will turn each submission into a fresh task list.
In this step, get ready to:
- Add fields for the learner's name and study topic.
- Collect a time budget through a number field.
- Generate a personalized task list from submitted values.
Add the study form
An HTML form packages each value under its field name. The POST method sends those values back to index.php when the learner submits the form.
- Switch back to Visual Studio Code.
- Select index.php from the Explorer sidebar.
- Find the PHP Study Planner heading inside the main element.
- Insert the form immediately after that heading by pasting this code:
<p>Build a focused study session from three simple choices.</p>
<form action="" method="post">
<label for="name">
Your name
<input id="name" name="name" type="text" value="<?php echo $name; ?>">
</label>
<label for="topic">
Topic
<select id="topic" name="topic">
<option value="Variables">Variables</option>
<option value="Arrays">Arrays</option>
<option value="Functions">Functions</option>
</select>
</label>
<label for="minutes">
Available minutes
<input id="minutes" name="minutes" type="number" step="15" value="<?php echo $minutes; ?>">
</label>
<button type="submit">Build my plan</button>
</form>
What does this form do?
- The name field collects the learner's name.
- The topic selector provides three PHP concepts.
- The minutes field collects a numeric time budget in 15-minute steps.
- The empty action value sends the submission back to the current page.
- The post method keeps the submitted values inside the request body.
- Save index.php.
- Return to Safari.
- Reload the page to display the three form controls above Alex's existing plan.
You now have a form that gathers the three choices needed for a personalized study session.
Form missing from the page?
Confirm that the form sits inside the main element. Check that you saved index.php before reloading Safari.
Still stuck? Help me find why my PHP form is missing from Safari.
The browser can submit bare form controls. Clear spacing makes each choice easier to scan.
- Find the closing brace of the main style rule.
- Add the form layout rules immediately below it by pasting this code:
h1,
h2 {
margin-top: 0;
}
form {
display: grid;
gap: 16px;
margin: 24px 0;
}
label {
display: grid;
gap: 7px;
font-weight: 700;
}
input,
select,
button {
width: 100%;
padding: 12px;
border-radius: 10px;
border: 1px solid #c8c0df;
font: inherit;
}
How does the form layout work?
- The form rule places each field in a vertical grid.
- The label rule keeps each field name close to its control.
- The shared control rule gives every input the same width and spacing.
- Save index.php.
- Reload Safari to display evenly spaced fields with rounded borders.
Form still looks unstyled?
Check that the new rules are inside the existing style element. Confirm that each CSS rule has matching braces.
Need another pair of eyes? Help me debug the form styles in index.php.
- Find the shared input control rule.
- Add the button and result styles immediately below that rule by pasting this code:
button {
border: 0;
background: #6547c7;
color: #ffffff;
font-weight: 700;
cursor: pointer;
}
.plan {
padding: 20px;
border-radius: 14px;
background: #eee9ff;
}
What do these styles change?
- The button rule turns the submit control purple.
- The .plan rule gives a generated result its own panel.
- Save index.php.
- Reload Safari to display a purple Build my plan button.
Button missing its purple background?
Confirm that the button rule appears after the shared control rule. A later CSS rule supplies the purple background.
Still seeing a plain button? Help me debug the button style in my PHP page.
Generate tasks from submitted values
The form now collects choices. PHP needs to read those values before it can create a matching plan.
A dedicated function keeps the task rules together. The page can call the same function for every submission.
- Scroll to the opening PHP block at the top of index.php.
- Select everything from the opening PHP tag through its closing tag.
- Replace the selected block with this code:
<?php
function buildStudyPlan($minutes, $topic)
{
$tasks = [
"Review the key ideas in $topic",
"Complete one short $topic exercise",
];
if ($minutes >= 45) {
$tasks[] = "Write a summary of what you learned about $topic";
}
return $tasks;
}
$name = "";
$topic = "Variables";
$minutes = 30;
$tasks = [];
if ($_POST) {
$name = $_POST["name"];
$topic = $_POST["topic"];
$minutes = (int) $_POST["minutes"];
$tasks = buildStudyPlan($minutes, $topic);
}
?>
How does PHP process the form?
- The buildStudyPlan() function creates the task array for the selected topic.
- A budget of at least 45 minutes adds a third task.
- The $_POST values come from the form fields with matching names.
- The (int) conversion uses type casting to store the submitted minutes as an integer.
- The empty defaults keep the result free of tasks before the first submission.
- Save index.php.
Before you submit the form, how many tasks do you expect from a 45-minute plan?
- Return to Safari.
- Enter Jordan in the Your name field.
- Select Arrays from the Topic menu.
- Enter 45 in the Available minutes field.
- Click Build my plan.
You'll see Jordan's plan for 45 minutes on Arrays with three tasks.
You just made the planner respond to a visitor. The submitted choices now control the result.
Submission still shows the old plan?
Check that each $_POST key matches its form field name. Confirm that buildStudyPlan() receives $minutes before $topic.
Need help tracing the submitted values? Help me debug my PHP POST form and buildStudyPlan function.
Test the personalized result
The page currently keeps its result markup visible before a plan exists. A condition can show that section only when $tasks contains items.
- Find the existing plan output directly below the form.
- Replace its heading, summary, and ordered list with this conditional result block:
<?php if ($tasks) { ?>
<section class="plan">
<h2><?php echo $name; ?>'s plan</h2>
<p>
<?php echo $minutes; ?> minutes on
<?php echo $topic; ?>
</p>
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo $task; ?></li>
<?php } ?>
</ol>
</section>
<?php } ?>
How does the result appear?
- The outer condition checks whether the task array contains items.
- The plan section displays the submitted values.
- The existing foreach loop renders every generated task.
- Save index.php.
- Reload Safari to display the form without an empty plan underneath it.
Empty plan still visible?
Confirm that the entire result section sits between the opening and closing task conditions. Check that $tasks starts as an empty array.
Still seeing an empty result? Help me check the conditional result block in index.php.
Before you submit this next value, do you think Safari will show the angle brackets or format the name?
- Enter <strong>Alex</strong> in the Your name field.
- Select Variables from the Topic menu.
- Enter 30 in the Available minutes field.
- Click Build my plan.
You'll see Alex appear in bold in the result heading. Safari interpreted the submitted <strong>Alex</strong> value as page markup.
That shortfall is intentional. Raw submitted text can control how the page renders.
✔️ Awesome, I've got everything!
Great work. Save index.php once more so your interactive planner is ready for the next step.
ⓧ I'd like to double check the full code
<?php
function buildStudyPlan($minutes, $topic)
{
$tasks = [
"Review the key ideas in $topic",
"Complete one short $topic exercise",
];
if ($minutes >= 45) {
$tasks[] = "Write a summary of what you learned about $topic";
}
return $tasks;
}
$name = "";
$topic = "Variables";
$minutes = 30;
$tasks = [];
if ($_POST) {
$name = $_POST["name"];
$topic = $_POST["topic"];
$minutes = (int) $_POST["minutes"];
$tasks = buildStudyPlan($minutes, $topic);
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Study Planner</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 16px;
font-family: Arial, sans-serif;
background: #f4f1ff;
color: #201a32;
}
main {
width: min(680px, 100%);
background: #ffffff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 18px 45px rgba(49, 35, 91, 0.14);
}
h1,
h2 {
margin-top: 0;
}
form {
display: grid;
gap: 16px;
margin: 24px 0;
}
label {
display: grid;
gap: 7px;
font-weight: 700;
}
input,
select,
button {
width: 100%;
padding: 12px;
border-radius: 10px;
border: 1px solid #c8c0df;
font: inherit;
}
button {
border: 0;
background: #6547c7;
color: #ffffff;
font-weight: 700;
cursor: pointer;
}
.plan {
padding: 20px;
border-radius: 14px;
background: #eee9ff;
}
li + li {
margin-top: 10px;
}
</style>
</head>
<body>
<main>
<h1>PHP Study Planner</h1>
<p>Build a focused study session from three simple choices.</p>
<form action="" method="post">
<label for="name">
Your name
<input id="name" name="name" type="text" value="<?php echo $name; ?>">
</label>
<label for="topic">
Topic
<select id="topic" name="topic">
<option value="Variables">Variables</option>
<option value="Arrays">Arrays</option>
<option value="Functions">Functions</option>
</select>
</label>
<label for="minutes">
Available minutes
<input id="minutes" name="minutes" type="number" step="15" value="<?php echo $minutes; ?>">
</label>
<button type="submit">Build my plan</button>
</form>
<?php if ($tasks) { ?>
<section class="plan">
<h2><?php echo $name; ?>'s plan</h2>
<p>
<?php echo $minutes; ?> minutes on
<?php echo $topic; ?>
</p>
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo $task; ?></li>
<?php } ?>
</ol>
</section>
<?php } ?>
</main>
</body>
</html>
Your planner now responds to submitted choices through one PHP function. Next, you'll make user text safe to display and reject unusable values.
Make User Input Safe and Reliable
Your planner now accepts a learner's choices through a POST form. The previous test proved that a browser can interpret a submitted name as HTML.
PHP provides output escaping for the text problem. Escaped text stays visible without becoming page markup.
Conditions handle unusable values. They stop blank names or short time budgets from producing a plan.
In this step, get ready to:
- Escape submitted text before it reaches the page.
- Add warning-free defaults for the first page load.
- Validate the learner name and time budget.
Escape submitted text
Output escaping keeps submitted characters from changing the page structure. The htmlspecialchars() function converts HTML-sensitive characters into text the browser can display literally.
- Switch back to the index.php tab in Visual Studio Code.
- Find the line that starts with <input id="name".
- Replace that line with this escaped version:
<input id="name" name="name" type="text" value="<?php echo htmlspecialchars($name); ?>">
What does this change do?
- The value attribute places the submitted name back into the form.
- The htmlspecialchars($name) call keeps HTML-like characters visible inside that field.
- Find the result block that starts with <?php if ($tasks) { ?>.
- Replace that result block with this escaped version:
<?php if ($tasks) { ?>
<section class="plan">
<h2><?php echo htmlspecialchars($name); ?>'s plan</h2>
<p>
<?php echo $minutes; ?> minutes on
<?php echo htmlspecialchars($topic); ?>
</p>
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo htmlspecialchars($task); ?></li>
<?php } ?>
</ol>
</section>
<?php } ?>
Why escape every text value?
- The learner name comes directly from the form.
- The topic contributes text to each generated task.
- Escaping each text value keeps the entire result section predictable.
- Save index.php.
- Return to Safari.
- Enter <strong>Alex</strong> in the Your name field.
- Select Variables in the Topic field.
- Enter 30 in the Available minutes field.
- Click Build my plan.
You should see the literal text <strong>Alex</strong> in the heading. The browser no longer turns the submitted tags into bold formatting.
Still seeing a bold name?
Confirm that both name outputs use htmlspecialchars($name). Save index.php before submitting the form again.
Help me debug escaped PHP output.
That is the risky output fixed. Your planner now treats an HTML-like name as visible text.
Add warning-free defaults
The null coalescing operator uses a default when a requested form key is absent. This keeps the first page load predictable because no form values have been submitted yet.
- Find the initialization line $tasks = [];.
- Add this error default on the next line:
$error = "";
What does the error variable hold?
The empty string means the current form state has no validation problem. A later condition replaces it with a useful message when a value fails a check.
- Find the three POST assignments that begin with $name = $_POST["name"];.
- Replace those three assignments with these safe reads:
$name = $_POST["name"] ?? "";
$topic = $_POST["topic"] ?? "Variables";
$minutes = (int) ($_POST["minutes"] ?? 0);
How do these defaults work?
- A missing name value becomes an empty string.
- A missing topic value becomes Variables.
- A missing minutes value becomes the integer 0.
- Save index.php.
- Return to Safari.
- Load http://localhost:8000 from the address bar.
The form loads without a missing-key warning. You should see an empty name field plus Variables and 30 as defaults.
Seeing a warning on the first load?
Check that every $_POST lookup includes ?? followed by its default. Confirm that the minutes expression keeps its surrounding parentheses.
Help me find the unsafe POST read.
Validate names and time budgets
A valid plan needs a learner name. It also needs at least 15 minutes.
- Find $tasks = buildStudyPlan($minutes, $topic); inside the POST block.
- Replace that line with these validation conditions:
if ($name === "") {
$error = "Enter your name and choose at least 15 minutes.";
}
if ($minutes < 15) {
$error = "Enter your name and choose at least 15 minutes.";
}
if ($error === "") {
$tasks = buildStudyPlan($minutes, $topic);
}
How does validation control the plan?
- The first condition catches an empty name.
- The second condition catches a time budget below 15 minutes.
- The final condition builds tasks only while $error remains empty.
- Save index.php.
- Return to Safari.
Before you submit, do you expect an empty name to produce a study plan?
- Leave the Your name field empty.
- Enter 30 in the Available minutes field.
- Click Build my plan.
You should see that the generated plan is absent. The empty $tasks array proves that the validation condition stopped task generation.
Does the plan still appear?
Confirm that the buildStudyPlan($minutes, $topic) call sits inside the condition that checks whether $error is empty.
Help me debug the validation condition.
The validation now blocks an unusable plan. The page needs a visible message that explains what the learner should fix.
- Find the .plan style block in index.php.
- Add this message style immediately above it:
.message {
padding: 14px;
border-radius: 10px;
background: #fff0f0;
color: #8f2020;
}
What does this style add?
The .message class gives validation feedback its own padded box. Its background and text colors separate the problem from a successful plan.
- Find the closing </form> tag.
- Add this error output immediately below it:
<?php if ($error !== "") { ?>
<p class="message"><?php echo htmlspecialchars($error); ?></p>
<?php } ?>
When does the message appear?
- The condition displays the paragraph only when $error contains a message.
- The htmlspecialchars($error) call keeps the message safe when it enters the page.
- Save index.php.
Before you test both paths, what do you expect the planner to show for HTML-like text and an empty name?
- Return to Safari.
- Enter <strong>Alex</strong> in the Your name field.
- Enter 30 in the Available minutes field.
- Click Build my plan.
You should see <strong>Alex</strong> as literal text in a personalized plan.
- Clear the Your name field.
- Click Build my plan again.
You should see Enter your name and choose at least 15 minutes. inside the validation message box. The plan should be absent.
Missing the validation message?
Confirm that $error = ""; appears before the POST block. Check that the error output sits below the closing form tag.
Help me debug the missing message.
✔️ Awesome, I've got everything!
Your saved index.php now contains safe output, warning-free defaults, and basic validation.
ⓧ I'd like to double check the full code
- Compare your index.php file with this complete version:
<?php
function buildStudyPlan($minutes, $topic)
{
$tasks = [
"Review the key ideas in $topic",
"Complete one short $topic exercise",
];
if ($minutes >= 45) {
$tasks[] = "Write a summary of what you learned about $topic";
}
return $tasks;
}
$name = "";
$topic = "Variables";
$minutes = 30;
$tasks = [];
$error = "";
if ($_POST) {
$name = $_POST["name"] ?? "";
$topic = $_POST["topic"] ?? "Variables";
$minutes = (int) ($_POST["minutes"] ?? 0);
if ($name === "") {
$error = "Enter your name and choose at least 15 minutes.";
}
if ($minutes < 15) {
$error = "Enter your name and choose at least 15 minutes.";
}
if ($error === "") {
$tasks = buildStudyPlan($minutes, $topic);
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHP Study Planner</title>
<style>
* {
box-sizing: border-box;
}
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
padding: 32px 16px;
font-family: Arial, sans-serif;
background: #f4f1ff;
color: #201a32;
}
main {
width: min(680px, 100%);
background: #ffffff;
border-radius: 18px;
padding: 28px;
box-shadow: 0 18px 45px rgba(49, 35, 91, 0.14);
}
h1,
h2 {
margin-top: 0;
}
form {
display: grid;
gap: 16px;
margin: 24px 0;
}
label {
display: grid;
gap: 7px;
font-weight: 700;
}
input,
select,
button {
width: 100%;
padding: 12px;
border-radius: 10px;
border: 1px solid #c8c0df;
font: inherit;
}
button {
border: 0;
background: #6547c7;
color: #ffffff;
font-weight: 700;
cursor: pointer;
}
.message {
padding: 14px;
border-radius: 10px;
background: #fff0f0;
color: #8f2020;
}
.plan {
padding: 20px;
border-radius: 14px;
background: #eee9ff;
}
li + li {
margin-top: 10px;
}
</style>
</head>
<body>
<main>
<h1>PHP Study Planner</h1>
<p>Build a focused study session from three simple choices.</p>
<form action="" method="post">
<label for="name">
Your name
<input id="name" name="name" type="text" value="<?php echo htmlspecialchars($name); ?>">
</label>
<label for="topic">
Topic
<select id="topic" name="topic">
<option value="Variables">Variables</option>
<option value="Arrays">Arrays</option>
<option value="Functions">Functions</option>
</select>
</label>
<label for="minutes">
Available minutes
<input id="minutes" name="minutes" type="number" step="15" value="<?php echo $minutes; ?>">
</label>
<button type="submit">Build my plan</button>
</form>
<?php if ($error !== "") { ?>
<p class="message"><?php echo htmlspecialchars($error); ?></p>
<?php } ?>
<?php if ($tasks) { ?>
<section class="plan">
<h2><?php echo htmlspecialchars($name); ?>'s plan</h2>
<p>
<?php echo $minutes; ?> minutes on
<?php echo htmlspecialchars($topic); ?>
</p>
<ol>
<?php foreach ($tasks as $task) { ?>
<li><?php echo htmlspecialchars($task); ?></li>
<?php } ?>
</ol>
</section>
<?php } ?>
</main>
</body>
</html>
That is the safety layer working. Your planner now produces predictable results from valid input, invalid values, and HTML-like text.
Secret mission
Add Difficulty Levels
Give learners a Beginner or Challenge mode. The Challenge mode adds an explanation task while the Beginner mode keeps the original plan.
Clean Up Your Resources
Clean Up Your Resources
Your PHP Study Planner runs entirely on your Mac. There are no ongoing costs, subscriptions, or cloud resources.
Resources you used:
- The PHP development server running in Terminal.
- The php-study-planner folder containing index.php.
Keep everything running
No action needed. Choose this if you are still testing the planner or improving its code.
- Keep the Terminal window open so the local app stays available.
- Return to Safari at http://localhost:8000 whenever you want to test the planner.
- Continue editing index.php in Visual Studio Code.
- Leave the php-study-planner folder on your Mac for future experiments.
The server keeps using a local process while it runs. It does not create any usage charges.
Pause - I'll come back to this later
Shut down the local server to free its port. Your planner files remain ready for another session.
- Return to the Terminal window running the PHP development server.
- Press Control-C to stop the server.
- Close the Safari tab showing http://localhost:8000.
- Leave the php-study-planner folder on your Mac.
Terminal returns to its command prompt. Your index.php file remains unchanged.
Delete - I don't want to use this again
Deleting the folder permanently removes your planner code. Your installed development tools stay available for future projects.
- Return to the Terminal window running the PHP development server.
- Press Control-C to stop the server.
- Click Finder in the Dock.
- Use Finder search to locate the php-study-planner folder.
- Open php-study-planner to confirm that it contains index.php.
- Move the php-study-planner folder to the Trash.
- Empty the Trash to permanently remove the folder.
The local server is stopped. The planner folder is now removed from your Mac.
Nice Work!
Nice Work!
You made it! You built a local PHP Study Planner that turns four form choices into a personalized task list.
What you learned:
- Built a dynamic study planner in index.php. You served it locally so each submission produced a visible plan in the browser.
- Used PHP fundamentals to generate study tasks from a topic plus a time budget. Your planning logic used variables, arrays, conditions, buildStudyPlan(), and a foreach loop.
- Made form handling safe and reliable by processing $_POST data. You converted the time budget to an integer. You also used validation plus htmlspecialchars() so HTML-like text appears literally.
- Secret Mission: Added Beginner and Challenge modes. Challenge mode now adds an explanation task while Beginner keeps the plan focused on the core exercises.
Ready to quiz yourself?