feat: add lab - #4
Conversation
There was a problem hiding this comment.
Pull request overview
This PR turns the repository into a self-contained “Senior Interview Lab” with two SQLite-backed PHP exercises and a lab.php CLI to set up/reset/inspect the exercise databases.
Changes:
- Added
lab.phpCLI plus support utilities (Database,Schema,Args) to manage SQLite datasets. - Added two exercise scripts for user maintenance and order processing.
- Updated devcontainer and README to support the new lab workflow; removed the old
index.phpentrypoint.
Reviewed changes
Copilot reviewed 11 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| support/Schema.php | Defines SQLite schemas for users and orders exercises. |
| support/Database.php | Provides SQLite PDO connection helper (enables foreign keys/WAL). |
| support/Args.php | Adds simple --key=value CLI argument parsing helpers. |
| README.md | Replaces starter README with lab/exercise instructions. |
| lab.php | Adds CLI commands to setup/reset/inspect SQLite datasets. |
| index.php | Removes the previous “Hello, World!” starter entrypoint. |
| exercises/01_user_maintenance.php | Adds the user-maintenance exercise script. |
| exercises/02_order_processing.php | Adds the order-processing exercise script. |
| data/.gitkeep | Keeps data/ directory present in git for SQLite files. |
| .gitignore | Ignores generated SQLite DB/WAL/SHM files and jsonl artifacts. |
| .devcontainer/php-memory-limit.ini | Sets PHP memory limit to 32M in the devcontainer. |
| .devcontainer/Dockerfile | Builds a PHP devcontainer image with pdo_sqlite enabled. |
| .devcontainer/devcontainer.json | Switches devcontainer to build from the custom Dockerfile. |
Suppressed comments (1)
lab.php:46
- The help text advertises a --fail-on option for the order-processing exercise, but the exercise script does not implement or parse that option. This will confuse users following the help output.
php exercises/02_order_processing.php --fail-on=4
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
support/Database.php:12
mkdir()is not checked for failure; if directory creation fails (permissions, read-only FS, etc.) the code proceeds and the later PDO error will be harder to diagnose. Validatemkdir()succeeded and throw a clear exception if not.
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
support/Args.php:29
- If a numeric option is passed as a flag without a value (e.g.
--usersinstead of--users=100),parse()storestrueand this method will cast it to1, which is surprising and can lead to accidental behavior. Treat boolean flags as “not provided” for int options.
public static function int(array $args, string $key, int $default): int
{
return isset($args[$key]) ? (int) $args[$key] : $default;
}
support/Args.php:34
- Similarly, if a string option is passed as a flag without a value (e.g.
--foo),parse()storestrueand this method will return'1'after casting, which is confusing. Treat boolean flags as “not provided” for string options.
public static function string(array $args, string $key, string $default): string
{
return isset($args[$key]) ? (string) $args[$key] : $default;
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (5)
exercises/02_order_processing.php:67
- After introducing a transaction, the success path should
commit()before printing completion. Otherwise the changes may be rolled back when the script exits.
$payment = $pdo->prepare("INSERT INTO payments(order_id, amount_cents, status) VALUES (?, ?, 'pending')");
$payment->execute([$orderId, $total]);
echo "Order {$orderId} completed successfully.\n";
exercises/02_order_processing.php:72
- If an exception is thrown mid-order, the current code exits without rolling back any in-flight transaction/work. Rolling back when
inTransaction()keeps the DB consistent and avoids leaving locks behind.
} catch (Throwable $e) {
fwrite(STDERR, "FAILED: {$e->getMessage()}\n");
fwrite(STDERR, "Inspect the DB now. Partial writes may remain.\n");
exit(1);
}
support/Database.php:12
Database::connect()callsmkdir()without checking the return value. If directory creation fails (permissions, read-only FS), the subsequent PDO connection error will be harder to diagnose.
Consider failing fast with a clear exception when the directory cannot be created.
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0777, true);
}
exercises/01_user_maintenance.php:31
- This script loads all eligible users into memory via
fetchAll()(includingprofile_blob) and then performs one UPDATE per row. With the default dataset size, this can easily exceed the configured memory limit and is much slower than necessary.
You can preserve the intended behavior with a single set-based UPDATE that does not fetch user rows into PHP.
$users = $pdo->query(<<<'SQL'
SELECT *
FROM users
WHERE status = 'active'
AND last_login_at < '2025-08-13'
exercises/02_order_processing.php:15
$argsis parsed but never used in this exercise, and the extrarequire_oncefor Args is therefore also unused. Keeping dead setup code makes the exercise harder to follow.
require_once __DIR__ . '/../support/Database.php';
require_once __DIR__ . '/../support/Args.php';
// Parse command-line options and connect to the exercise database.
$args = Args::parse($argv);
No description provided.