Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
ARG VARIANT=8.5-bookworm
FROM mcr.microsoft.com/devcontainers/php:4-${VARIANT}

RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
&& apt-get install -y --no-install-recommends libsqlite3-dev \
&& docker-php-ext-install pdo_sqlite \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*

COPY php-memory-limit.ini /usr/local/etc/php/conf.d/php-memory-limit.ini
7 changes: 6 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"name": "PHP Technical Assessment",
"image": "mcr.microsoft.com/devcontainers/php:4-8.3-bookworm",
"build": {
"dockerfile": "Dockerfile",
"args": {
"VARIANT": "8.5-bookworm"
}
},

"features": {
"ghcr.io/devcontainers/features/github-cli:1": {},
Expand Down
1 change: 1 addition & 0 deletions .devcontainer/php-memory-limit.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
memory_limit = 32M
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/data/*.sqlite
/data/*.sqlite-shm
/data/*.sqlite-wal
/data/*.jsonl
99 changes: 85 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,103 @@
# PHP Technical Assessment

This repository contains the starter project for the technical interview.
# Senior Interview Lab

## Getting started

1. Open this repository in GitHub Codespaces.
2. Wait for the Codespace setup to finish.
3. Open a terminal in the Codespace.

Run the starter program:
## Introduction

This repository contains two independent PHP exercises:

1. **User maintenance**
2. **Order processing**

The lab is dependency-free and uses file-backed **SQLite** databases. You do not
need Docker, MySQL, PostgreSQL, Composer, or any third-party packages.

You can also list the available lab commands:

```bash
php lab.php help
```

## Candidate instructions

For each exercise:

```sh
php index.php
1. Explain how the existing implementation works.
2. Identify anything you think should be improved.
3. Make appropriate changes while preserving intended behavior.
4. Explain your decisions and trade-offs.

You are encouraged to use your judgment about what to investigate and how far to refactor the
exercise. You may make code changes wherever appropriate.

Do not add external dependencies or require additional infrastructure.

The exercises are independent. Complete them in the order directed by your interviewer.

## Exercise 1 — User maintenance

Prepare a fresh user database:

```bash
php lab.php setup:users
```

The expected starter output is:
Review and improve:

```text
Hello, World!
code exercises/01_user_maintenance.php
```

## Coding agent
Run the exercise:

```bash
php exercises/01_user_maintenance.php
```

Codex is available from the Codespace terminal:
Inspect the resulting user statuses:

```sh
codex
```bash
php lab.php inspect:users
```

Follow the interviewer's instructions for the assessment and submission
process.
Re-run the reset command whenever you need to restore the original dataset:

```bash
php lab.php reset:users
```

## Exercise 2 — Order processing

Prepare a fresh order database:

```bash
php lab.php setup:orders
```

Review and improve:

```text
code exercises/02_order_processing.php
```

Run the exercise:

```bash
php exercises/02_order_processing.php
```

Inspect products, orders, order items, and payments:

```bash
php lab.php inspect:orders
```

Re-run the setup command whenever you need a clean database:

```bash
php lab.php reset:orders
```
Empty file added data/.gitkeep
Empty file.
31 changes: 31 additions & 0 deletions exercises/01_user_maintenance.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

// =============================================================================
// Setup
// =============================================================================

// Load dependencies.
require_once __DIR__ . '/../support/Database.php';

// Connect to the exercise database.
$dbPath = __DIR__ . '/../data/users.sqlite';
$pdo = Database::connect($dbPath);

// =============================================================================
// Exercise
// =============================================================================

$users = $pdo->query(<<<'SQL'
SELECT *
FROM users
WHERE status = 'active'
AND last_login_at < '2025-08-13'
ORDER BY id
SQL)->fetchAll();

foreach ($users as $user) {
$stmt = $pdo->prepare("UPDATE users SET status = 'inactive' WHERE id = ?");
$stmt->execute([$user['id']]);
}
70 changes: 70 additions & 0 deletions exercises/02_order_processing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

// =============================================================================
// Setup
// =============================================================================

// Load dependencies.
require_once __DIR__ . '/../support/Database.php';

// Parse command-line options and connect to the exercise database.
$pdo = Database::connect(__DIR__ . '/../data/orders.sqlite');

// =============================================================================
// Exercise
// =============================================================================

// Define the products and quantities in the order.
$items = [
['product_id' => 1, 'quantity' => 2],
['product_id' => 2, 'quantity' => 1],
['product_id' => 3, 'quantity' => 3],
];

// Start processing the order
try {
$pdo->exec("INSERT INTO orders(status, total_cents, created_at) VALUES ('pending', 0, datetime('now'))");
$orderId = (int) $pdo->lastInsertId();
Comment thread
quang-do-se marked this conversation as resolved.

$total = 0;
foreach ($items as $item) {
$productStmt = $pdo->prepare('SELECT price_cents, stock FROM products WHERE id = ?');
$productStmt->execute([$item['product_id']]);
$product = $productStmt->fetch();

if (!$product) {
throw new RuntimeException('Product not found');
}

if ((int) $product['stock'] < $item['quantity']) {
throw new RuntimeException('Insufficient stock');
}

$lineTotal = (int) $product['price_cents'] * $item['quantity'];
$total += $lineTotal;

$insertItem = $pdo->prepare(<<<'SQL'
INSERT INTO order_items(order_id, product_id, quantity, unit_price_cents)
VALUES (?, ?, ?, ?)
SQL);
$insertItem->execute([$orderId, $item['product_id'], $item['quantity'], $product['price_cents']]);

$newStock = (int) $product['stock'] - $item['quantity'];
$updateStock = $pdo->prepare('UPDATE products SET stock = ? WHERE id = ?');
$updateStock->execute([$newStock, $item['product_id']]);
}

$updateOrder = $pdo->prepare("UPDATE orders SET total_cents = ?, status = 'ready_for_payment' WHERE id = ?");
$updateOrder->execute([$total, $orderId]);

$payment = $pdo->prepare("INSERT INTO payments(order_id, amount_cents, status) VALUES (?, ?, 'pending')");
$payment->execute([$orderId, $total]);

echo "Order {$orderId} completed successfully.\n";
} catch (Throwable $e) {
fwrite(STDERR, "FAILED: {$e->getMessage()}\n");
fwrite(STDERR, "Inspect the DB now. Partial writes may remain.\n");
exit(1);
}
5 changes: 0 additions & 5 deletions index.php

This file was deleted.

Loading