Foundation · 45 MINUTES · VERSION 1
PHP exceptions and debugging a failure
Separate JSON format errors from invalid values, follow try/catch/finally, and reproduce failures safely.
Reviewed 23 Oct 2026Your setup may differ. Windows, Ubuntu, and optional tool bundles →
What you will learn
- Separate malformed JSON from valid JSON with invalid values.
- Follow a thrown exception to a matching catch block.
- Reproduce a failure and record useful debugging evidence.
Theory: follow the failure
An exception interrupts normal execution. A matching catch handles it around a try block. Statements after the failing call inside that try are skipped. A finally block runs as control leaves the try/catch, including ordinary success and handled exceptions. Use it for cleanup; it does not undo changes or make an operation successful.
PHP has an Exception hierarchy and an Error hierarchy; both implement Throwable. Catch failures you can meaningfully handle. Catching every Throwable and reporting success can hide programming mistakes. Diagnostic warnings are a separate mechanism and are not all automatically exceptions. Framework error handling can change how errors are reported.
In this lab, decoding malformed text with JSON_THROW_ON_ERROR raises JsonException. Successfully decoding JSON does not validate its shape or fields. The function throws InvalidArgumentException for a valid JSON value that violates our minutes contract. These are deliberate local categories, not automatic HTTP status codes. A real Laravel request should use framework validation and exception handling.
Worked example: inspect four cases
Use PHP 8.4 or a compatible newer runtime. No database, web server, Python or package is needed. Save this as debug_minutes.php in a separate practice folder:
<?php
declare(strict_types=1);
function readStudyMinutes(string $json): int
{
$input = json_decode($json, false, 512, JSON_THROW_ON_ERROR);
if (! ($input instanceof stdClass)) {
throw new InvalidArgumentException('Provide an object with whole minutes from 0 to 480.');
}
$minutes = $input->minutes ?? null;
if (! is_int($minutes) || $minutes < 0 || $minutes > 480) {
throw new InvalidArgumentException('Provide an object with whole minutes from 0 to 480.');
}
return $minutes;
}
$samples = ['{"minutes":30}', '{', '{"minutes":"30"}', 'null'];
foreach ($samples as $index => $json) {
echo 'Case '.($index + 1).': ';
try {
$minutes = readStudyMinutes($json);
echo 'Accepted '.$minutes.' minutes.'.PHP_EOL;
} catch (JsonException $error) {
echo 'Invalid JSON; correct the message format.'.PHP_EOL;
} catch (InvalidArgumentException $error) {
echo 'Invalid minutes; use an integer from 0 to 480.'.PHP_EOL;
} finally {
echo 'Case finished.'.PHP_EOL;
}
}
Run php debug_minutes.php. Windows PowerShell/Command Prompt and Ubuntu Bash use the same command when your PHP executable is on PATH. Laragon/XAMPP are optional. Use the common setup guide if commands select the wrong PHP version.
Expected output:
Case 1: Accepted 30 minutes.
Case finished.
Case 2: Invalid JSON; correct the message format.
Case finished.
Case 3: Invalid minutes; use an integer from 0 to 480.
Case finished.
Case 4: Invalid minutes; use an integer from 0 to 480.
Case finished.
The function accepts a JSON object with an integer minutes value between 0 and 480 inclusive. It intentionally rejects numeric text, fractions, Booleans, arrays and null. It reads no file and changes no database. Its return value is reached only on success. Each sample has its own try/catch, so the loop continues after a handled failure. The fixed error messages avoid printing the raw input or an exception trace.
Guided debugging lab
- Run the unchanged example and compare every line.
- Change the first sample to
{"minutes":0}and then{"minutes":480}. Both pass. Try 481 and -1; both fail the value rule. - Try
{"minutes":30.5},{"minutes":true},{},[], andnull. Each is valid JSON, but none satisfies the contract. - Try
{and an empty string. Each fails JSON decoding. Identify the catch block reached before changing code. - Run
php -l debug_minutes.phpto check PHP syntax. This does not run the examples or prove their business rules. - In a disposable copy, misspell the function call. Read the local error class, file and line; undo that edit. Do not add a broad catch just to make this mistake disappear.
For a failing request, first record method/path, expected result, actual status, and a dummy input that reproduces it. Follow the trace to your application call site and inspect the smallest relevant value/type. Form one explanation, change one thing, rerun the failing case, then nearby passing cases. A trace location is a clue, not proof that a library is broken. Keep customer input, passwords, tokens and full traces out of public reports. Use protected server logs and a request reference for real incidents; do not enable public debug output in production.
Independent exercise
Adapt the function in a new practice file to read a JSON object with an integer daily task target from 0 to 20. Keep malformed JSON separate from invalid field values. Test 0, 20, 21, a numeric string, a missing field, and broken JSON. Predict the category before running each case. Print no accepted result after a failure. Keep input unchanged and calculation separate from display.
Common mistakes
- Treating valid JSON as validated data: check the object and field types too.
- Returning zero for every failure: zero is a valid value here and would hide errors.
- Putting all samples in one try: a failure skips the remaining statements in that try.
- Returning from finally: this can replace a pending result or exception. Use cleanup only.
- Suppressing failures with
@: reproduce and fix their cause instead.
Self-check — not graded
- Which category handles a broken JSON body?
- Is a JSON string minutes value accepted?
- Does finally mean a record was saved?
Interview practice
“An endpoint started returning 500. What do you do first?” Establish impact, inspect a protected error reference and recent changes, reproduce safely with dummy input, isolate the cause, test a correction or rollback, and verify recovery. Distinguish expected input errors from unexpected failures. Do not guess that restarting fixes the underlying defect.
English practice and glossary
- Exception: a thrown failure that changes control flow.
- Catch: a handler for a matching thrown type.
- Finally: a block used for work as control leaves try/catch.
- Trace: the chain of calls leading to a failure.
- Reproduce: trigger the same problem with known steps.
- Regression: an earlier working behavior that fails after a change.
Write: “I expected __. I observed __ with input __. The failure happened before __. After the fix, I checked __.” Use dummy values and read it aloud.
Recap and next step
Observe before editing. Separate format failures from field validation, catch known cases, and verify both failure and success. Next, move task data into SQL tables with keys and constraints. Official references below were checked on 2026-10-22; this lesson is a draft until review.
Reveal exercise hints and self-check answers
- JsonException is caught for this lab's malformed JSON.
- No. The contract requires a JSON integer, not numeric text.
- No. Finally is control-flow/cleanup behavior, not evidence of storage or success.
Exercise outline: rename the function and field, change the upper limit and fixed error text, and keep the decoding flag. Accept integer 0 and 20; reject 21, numeric text and the missing field as InvalidArgumentException. Broken JSON raises JsonException. Return only after checks and keep printing in the caller. Run syntax, edge-case and repeated-call checks; a caught exception does not repair its input.