PROJECT 1 — CLI GRADEBOOK
Starter files for PHP the TPRM Way, Chapter 14


WHAT IS IN THIS FOLDER

  gradebook.php            The program you run. It holds the menu and all of the
                           terminal input and output. Every branch is a TODO.
  gradebook_functions.php  Calculation and persistence functions. Every body is a
                           stub that returns a placeholder.
  students.json            Two students with several scores each, and a third
                           with none. It is the fixture the acceptance tests use,
                           and it is also the shape your own save has to produce.
  README.txt               This file.

These are starter files, not a solution. Run php gradebook.php now: the program
prints a short notice and stops. Nothing else works yet, and that is the point.


WHAT TO BUILD

A command-line gradebook. It stores student records, validates scores,
calculates statistics, and persists its data as JSON.

One student record looks like this:

  {"name": "Maya", "scores": [70, 85, 91]}

All of them together are a list of those records, which is the array of
associative arrays from Chapter 9. Open students.json and you are looking at
exactly that, written out as JSON.


BUILD MILESTONES

  1. Define the student record shape: name plus an array of scores.
  2. Create functions for average, minimum, maximum, and passing status.
  3. Build a text menu for add student, add score, list students, show one
     student, save, and quit.
  4. Validate names and scores before changing state.
  5. Write the current array to JSON with JSON_THROW_ON_ERROR.
  6. Load existing JSON at startup and handle missing or malformed files
     deliberately.
  7. Refactor menu input and output away from calculation functions.

Build one milestone at a time. Type, predict, run, modify, then test. Do not
start by writing the whole program.


ACCEPTANCE TESTS

  Normal          Two students with several scores produce correct averages and
                  save and load successfully.
  Boundary        A score exactly at the pass mark follows the documented rule.
  Empty           A student with no scores does not cause division by zero.
  Malformed file  Invalid JSON is reported without silently replacing good data.

Work out the first row by hand before you run it. With students.json as it
stands, Maya averages 82, Noah averages 91, and Ava has no scores at all. Ava is
there on purpose: she is the Empty row, and she is also the normal state of every
student on the first day of a course.

The Empty row is the one that fails first in most beginner versions, with
Fatal error: Uncaught DivisionByZeroError: Division by zero. Write the guard
before you write the average, and decide what an average of no scores means:
this starter declares average() as ?float so that null is available for it.

The Malformed file row is worth doing properly. Copy students.json somewhere
safe, then break the copy in place by deleting a closing brace, and run the
program against it. Reporting the problem and refusing to continue is right.
Quietly starting from an empty list is wrong, because the next save then
overwrites data that could still have been recovered.


THE PASS MARK

The starter sets it to 60 and passes it as a parameter with a default, so the
rule lives in one place. A score of exactly 60 passes. That is a decision, not a
law: change it if you like, and change the Boundary test with it.


STRETCH GOALS

  - Add letter grades.
  - Sort the student list by name.
  - Export a simple CSV report after the JSON version is stable. Look up
    fputcsv.

Do not add a stretch goal until the core version passes all four acceptance
tests. A smaller correct program teaches more than a larger unstable one.


THE TPRM REVIEW

Once the project works, change one design decision and predict the consequences
before you make the change. What breaks if a student may have the same name as
another one? What breaks if scores are allowed to be decimals? Predict first,
then run the complete acceptance-test set again.
