# PHP Coding Test for Hiring: A Fair Task You Can Reuse

> A good PHP coding test takes no more than three hours, uses a realistic domain that has nothing to do with your product, and is scored against a rubric written before the first submission. Code review tasks work especially well: candidates find problems in existing code and explain their fixes. Pay a flat fee for the candidate's time.

- URL: https://php-entwickler-gesucht.de/en/assessing-candidates/php-coding-test/
- Language: en
- Updated: 2026-10-19
- Publisher: PHP-Entwickler gesucht (OnlineBuilders LLC, https://onlinebuilders.biz)

Most coding tests don't fail because of the candidates. They fail because nobody decided in advance what a good solution looks like. Then each interviewer scores by gut feel, and the submission with the most lines wins.

Time is the other problem. A "small weekend task" quietly grows into eight hours of work. Experienced PHP developers who already have a job and a family, and plans for Saturday, simply decline, and apply to the next company that asks for less.

## Which format to use

There are three common formats. They measure different things and cost different amounts of everyone's time.

**Coding test formats compared**

| | Take-home build | Live coding | Code review |
|---|---|---|---|
| Candidate time | 2 to 3 hours | 45 to 60 minutes | 60 to 90 minutes |
| Team time | scoring plus interview | interview | one-off prep, then interview |
| Close to real work | yes | no | yes |
| Mostly measures nerves | no | yes | partly |
| Shows care for quality | yes | partly | yes |
| Easy to do with AI help | yes | no | partly |

My recommendation for most PHP roles: a code review task, sent in advance and discussed in the interview. It's short, close to daily work and hard to fake, because the reasoning counts more than the code.

It also matches the job. Developers on a team read far more code than they write. Someone who understands unfamiliar code quickly and names problems clearly is valuable on almost any project.

## A ready-made code review task

You can use this task as it is. The domain, importing users from a CSV file, is generic and unrelated to your product. Instructions to candidates: "This class was submitted in a pull request. Write your review: what would you change, and why? Spend no more than 90 minutes."

```php
<?php

class UserImporter
{
    public function import($path)
    {
        $rows = array_map('str_getcsv', file($path));

        foreach ($rows as $row) {
            $user = new User();
            $user->email = $row[0];
            $user->name = $row[1];
            $user->password = md5($row[2]);
            $user->save();
        }

        return count($rows);
    }
}
```

There's more in these lines than it seems. A strong review finds most of the following, roughly in order of severity:

1. Plaintext passwords arrive in a CSV at all, and are then hashed with MD5. Both are wrong. Import users without passwords and send reset invitations instead.
2. `file()` loads the whole file into memory. A 2 GB export will take the server down.
3. Splitting by line and then calling `str_getcsv()` breaks on quoted fields that contain line breaks.
4. The header row is imported as a user.
5. No validation: invalid emails go straight into the database.
6. No handling of duplicates. Running the import twice creates every user twice, or crashes on a unique index halfway through.
7. One query per row, with no batching.
8. The return value counts lines, not imported users, and says nothing about failures.
9. No types, no `declare(strict_types=1)`, class not `final`.

Here's one possible rewrite in PHP 8.3 with Laravel's Eloquent, as a reference for your team:

```php
<?php

declare(strict_types=1);

use App\Models\User;

final readonly class ImportResult
{
    public function __construct(
        public int $imported,
        public array $errors,
    ) {}
}

final class UserImporter
{
    private const int BATCH_SIZE = 500;

    public function import(string $path): ImportResult
    {
        $file = new SplFileObject($path);
        $file->setFlags(
            SplFileObject::READ_CSV
            | SplFileObject::READ_AHEAD
            | SplFileObject::SKIP_EMPTY
            | SplFileObject::DROP_NEW_LINE
        );

        $batch = [];
        $errors = [];
        $imported = 0;

        foreach ($file as $line => $row) {
            if ($line === 0) {
                continue; // header row
            }

            [$email, $name] = array_pad($row, 2, null);

            if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
                $errors[] = sprintf('Line %d: invalid email', $line + 1);
                continue;
            }

            $batch[] = ['email' => strtolower($email), 'name' => trim((string) $name)];

            if (count($batch) === self::BATCH_SIZE) {
                $imported += $this->flush($batch);
                $batch = [];
            }
        }

        $imported += $this->flush($batch);

        return new ImportResult($imported, $errors);
    }

    private function flush(array $batch): int
    {
        if ($batch === []) {
            return 0;
        }

        User::upsert($batch, uniqueBy: ['email'], update: ['name']);

        return count($batch);
    }
}
```

Nobody has to write exactly this. What matters is the reasoning. A candidate who spots point 1 and refuses to import passwords at all is thinking about security rather than syntax. Someone who mentions point 3 has handled real exports from real customers. `SplFileObject` reads the file line by line as CSV, so memory use stays flat; the [PHP manual on SplFileObject](https://www.php.net/manual/en/class.splfileobject.php) covers the flags.

## Write the rubric before the first submission

Decide what you're scoring before any solution arrives. Otherwise the bar shifts from candidate to candidate.

**Rubric for the code review task (0 to 2 points each)**

- [ ] Password handling identified as the main problem
- [ ] Memory use of `file()` explained
- [ ] Multi-line CSV fields or the header row noticed
- [ ] Validation and duplicates addressed
- [ ] Batching or performance mentioned
- [ ] Suggestions prioritised, not just listed
- [ ] Tone of the review: factual, respectful, specific

That last line gets forgotten. A review that finds every bug but tears the author apart tells you a lot about future teamwork. Out of 14 possible points, 9 is a good result for a mid-level role and 11 or more for senior roles. Those thresholds are a suggestion; adjust them.

In the interview, have the candidate go deeper on two or three points. Our list of [PHP interview questions](https://php-entwickler-gesucht.de/en/assessing-candidates/php-interview-questions/) has follow-ups. For senior roles, add an architecture conversation; see [senior PHP developer skills](https://php-entwickler-gesucht.de/en/assessing-candidates/senior-php-developer-skills/).

## If you want a build task instead

Some teams want to see how someone builds from scratch. That's fair, especially for roles that produce a lot of new code. Three rules apply.

Keep it under three hours for someone who doesn't know your team. Have a colleague solve it first and time them. If they need four hours, cut a third.

Provide a skeleton. A fresh Laravel or Symfony project with the database running in Docker saves every candidate an hour of setup that says nothing about their skill.

Say what counts. If you care about tests, write that down. If readability matters more than completeness, say so.

A spec that works: "Build `POST /api/vouchers/redeem`, which redeems a voucher code. Each code can be redeemed once, even under concurrent requests. Expired codes are rejected. Write at least three tests. No UI needed." Four sentences. They test validation and concurrency, plus error handling and testing habits, without anyone spending half a day on a front end you wouldn't score anyway.

## Give feedback, including to rejected candidates

Someone who spent three hours on your task deserves more than a template rejection. Two or three specific sentences are enough. "The batching was clean; validation and duplicates were left open" takes you five minutes to write.

It pays off for you too. The PHP world is small, and candidates talk about how a hiring process went. A fair process with honest feedback gets around. So does a bad one.

## How much to pay

Pay. It shows you take people's time seriously, and it lowers the drop-out rate among candidates who are currently employed. For a sensible amount, anchor it to real hourly figures:

**A fair flat fee for three hours of work**

- US employee level: 3 h × $47.85: $143.55
- UK contractor level: 3 h × £58.75: £176.25
- **Suggested flat fee: $150 to $200, or £150 to £180**

Worked example. US: BLS median hourly pay for web developers and digital designers, May 2025. UK: ITJobsWatch median PHP contract day rate (£470) divided by 8 hours, six months to 18 Oct 2026.

That's affordable for almost any company and meaningful for the candidate. For a broader view of what hiring costs you, see our article on the [cost of hiring a developer](https://php-entwickler-gesucht.de/en/salary-and-cost/cost-of-hiring-developer/).

## AI assistants and coding tests

JetBrains' State of PHP 2025 found that 80 percent of PHP developers use AI assistants regularly. You can't police that on a take-home task anyway. It's more honest to allow AI explicitly and ask for one sentence on how it was used.

For code review tasks this matters less than for build tasks. An assistant will flag MD5 instantly. Whether the candidate can explain why passwords shouldn't be in the CSV in the first place only shows in conversation. That's what the conversation is for.

Once the task is set up, you can reuse it across hiring rounds. Change details now and then so solutions don't circulate. If you lack the time to build all this, an outside team that develops and runs PHP applications can assess candidates for you, for example a studio like [OnlineBuilders](https://onlinebuilders.biz/how-we-work).

Copy the task into a private repository today, have a teammate solve it in 90 minutes and check whether the rubric matches their result. Only then send it to candidates, together with the timeline you promised in your [job description](https://php-entwickler-gesucht.de/en/hiring/php-developer-job-description/).

## FAQ

### Should I pay candidates for a PHP coding test?

Yes. A short flat fee signals that you respect their time and reduces drop-outs among people who already have a job. If you ever use the result in production, payment stops being optional in most jurisdictions anyway.

### How long should a take-home coding test be?

Two to three hours of real work at most, measured by having a colleague who doesn't know the task solve it first. Anything longer filters for free time, not skill.

### Live coding or take-home test?

For PHP web development, a take-home task followed by a conversation gives more realistic results. Live coding mostly measures how someone performs under observation. Reviewing code together on a shared screen is a decent middle ground.

### What if a candidate refuses the coding test?

Offer an alternative, such as walking through code from a public repository or a past project. Senior developers with several offers often turn down long tests, but most accept a short, paid one.

Photo: Crew crew, CC0 (https://commons.wikimedia.org/wiki/File:Programmer_at_work_(Unsplash).jpg)