← Back to list

Building a Local AI Assistant for PHP Code Review Without Sending Code to the Cloud

Build a fully local PHP code reviewer using Ollama and open-weight models. No API key, no cloud, no data leaving your machine. Complete…

Ann R. in Level Up Coding · 2026-05-11 15:29 · 104 claps · 20.7 min read paywalled
#code-review #artificial-intelligence #php-development #php #web-app-security
Open on Medium ↗
Wiki topics: LLM · Large Language Models AI · AI · General 💻 · Programming

Building a Local AI Assistant for PHP Code Review Without Sending Code to the Cloud

Build a fully local PHP code reviewer using Ollama and open-weight models. No API key, no cloud, no data leaving your machine. Complete setup with git hooks and CI.

Imagine you work at a company where the legal team has a single rule about AI tooling: nothing proprietary leaves the building.

No pasting code into ChatGPT. No Claude API calls with production logic. No GitHub Copilot on repositories that touch customer data. The rule is clear, the reasoning is sound, and it immediately rules out most of the AI developer tools everyone else is talking about.

But the underlying need doesn’t go away. Code review is slow. Junior developers need feedback. Security smells slip through. Inconsistent patterns accumulate. The question isn’t whether AI code review would be useful — it obviously would. The question is whether it can happen without sending a single line of code to an external server.

The answer is yes. And the setup takes about an afternoon.

This walkthrough builds a fully local PHP code review assistant using Ollama, an open-weight code model, and a PHP CLI script that can be wired into pre-commit hooks, CI pipelines, or used manually during development. Everything runs on your hardware. Nothing leaves your network.

TL;DR Speedrun

  • Ollama is a local model runner that makes running large language models as simple as running a Docker container — with an OpenAI-compatible REST API on localhost:11434.
  • Open-weight code models (CodeLlama, Qwen2.5-Coder, DeepSeek-Coder) run entirely on-device with no data leaving the machine, and are genuinely capable of PHP-specific code review.
  • The integration is a PHP CLI script that reads files or diffs, sends them to the local Ollama API, and returns structured feedback.
  • The whole thing can be wired into a git pre-commit hook to gate commits on AI review, or into a CI step that annotates pull requests.
  • Hardware matters: a machine with 16GB RAM can run 7B models comfortably; 32GB+ opens up 13B and 34B models for significantly better results.

What You’ll Learn

  • How Ollama works and which models are worth running for PHP code review
  • Building a PHP CLI code review script from scratch
  • Designing prompts that produce structured, actionable PHP feedback
  • Wiring the reviewer into git pre-commit hooks
  • Extending to a CI-integrated PR annotation system
  • Hardware considerations and model size trade-offs

Why Local Instead of Cloud?

Before getting into the setup, it’s worth being clear about who this is for and why.

You need local AI when:

  • Your company has data governance or compliance requirements (GDPR, HIPAA, SOC 2, internal IP policy)
  • You’re working on client code under NDA where sharing with third-party AI providers would be a breach
  • You want zero ongoing API costs after initial hardware investment
  • You’re working offline — on a plane, in a secure facility, or anywhere without reliable internet
  • You want to self-host everything in your infrastructure stack

Cloud AI still wins when:

  • You need the most capable models (GPT-4, Claude Sonnet, Gemini Ultra)
  • Your hardware is limited — a laptop with 8GB RAM will struggle with any useful local model
  • Setup time matters more than data sovereignty

This article is for the first group. If you’re in the second group, the previous article in this series covers the Claude API integration.

Step 1: Install Ollama

Ollama is the tool that makes running local models practical. It handles model downloading, quantization, GPU/CPU offloading, and exposes a clean REST API that looks — intentionally — a lot like OpenAI’s.

macOS / Linux:

curl -fsSL https://ollama.com/install.sh | sh

Windows: Download the installer from [https://ollama.com/download.](https://ollama.com/download.)

Once installed, Ollama runs as a background service on http://localhost:11434. Verify it's running:

curl http://localhost:11434/api/tags
# Returns: {"models": [...]}

That’s the same endpoint your PHP code will call.

Step 2: Choose and Pull a Code Model

Choosing the right model is the most consequential decision in this setup. Bigger is generally better — but bigger also requires more RAM and runs slower. Here’s the practical landscape for PHP code review as of 2025:

Qwen2.5-Coder:7b — The current recommendation for most setups. Strong code understanding, fast on modern hardware, fits in 8GB RAM with 4-bit quantization. Excellent PHP awareness.

ollama pull qwen2.5-coder:7b

CodeLlama:13b — Meta’s code-specialized model at 13B parameters. Better reasoning than 7B, requires 12–16GB RAM. Good balance of quality and performance for a dedicated development machine.

ollama pull codellama:13b

DeepSeek-Coder-V2:16b — Excellent at multi-file reasoning and security analysis. Requires 16GB+ RAM. Worth it if your machine can handle it.

ollama pull deepseek-coder-v2:16b

Qwen2.5-Coder:32b — The best local option if your machine has 32GB+ RAM. Approaches GPT-4-level code understanding on many benchmarks. Slower, but the quality difference is meaningful for complex reviews.

ollama pull qwen2.5-coder:32b

For most development machines, start with qwen2.5-coder:7b. If the quality feels thin, move up. Test with a piece of PHP you already know has issues — how well it catches them is your benchmark.

Test any model manually before integrating:

ollama run qwen2.5-coder:7b "Review this PHP for security issues: <?php echo $_GET['name']; ?>"

If it catches the XSS vulnerability immediately and explains why, you’re in good shape.

Step 3: Understand the Ollama API

Ollama exposes two endpoints you’ll use:

Generate (single turn):

POST http://localhost:11434/api/generate

Chat (multi-turn with message history):

POST http://localhost:11434/api/chat

For code review, the generate endpoint is simpler and sufficient. Here’s the request shape:

{
  "model": "qwen2.5-coder:7b",
  "prompt": "Your full prompt with the PHP code here",
  "stream": false,
  "options": {
    "temperature": 0.1,
    "num_predict": 2048
  }
}

Key options:

  • stream: false — get the full response at once instead of streaming tokens (simpler for CLI tools)
  • temperature: 0.1 — low temperature for deterministic, factual review output (not creative writing)
  • num_predict — equivalent to max_tokens; sets the ceiling on response length

The response:

{
  "model": "qwen2.5-coder:7b",
  "response": "The review text here...",
  "done": true
}

That’s the entire API surface you need. No authentication. No API key. No headers beyond Content-Type.

Step 4: Design the Review Prompt

The prompt is the most important part of the whole system. A vague prompt produces vague feedback. A structured prompt produces structured, actionable feedback that’s actually useful in a code review context.

Here’s the prompt architecture that works well for PHP:

function buildReviewPrompt(string $phpCode, string $context = ''): string
{
    $contextSection = $context
        ? "Context about this code: {$context}\n\n"
        : '';

    return <<<PROMPT
You are a senior PHP engineer conducting a thorough code review. Analyze the PHP code below and provide structured feedback.

{$contextSection}Review for the following categories — only report issues that actually exist, skip categories with no issues:

**SECURITY** (label: 🔴 CRITICAL)
- SQL injection vulnerabilities
- XSS vulnerabilities (unescaped output)
- CSRF exposure
- Unsafe use of user input (\$_GET, \$_POST, \$_REQUEST, \$_COOKIE)
- Hardcoded credentials or secrets
- Insecure file operations
- Command injection risks

**CORRECTNESS** (label: 🟠 BUG)
- Logic errors
- Off-by-one errors
- Incorrect return types
- Unhandled null values or edge cases
- Incorrect use of comparison operators (== vs ===)

**PERFORMANCE** (label: 🟡 PERFORMANCE)
- N+1 query patterns
- Unnecessary loops
- Missing indexes implied by query patterns
- Memory-intensive operations on large datasets

**CODE QUALITY** (label: 🔵 SUGGESTION)
- Violation of single responsibility principle
- Poor variable or function naming
- Missing or misleading docblocks
- Magic numbers or strings
- Overly complex conditionals

**FORMAT FOR EACH ISSUE:**
[Label] Line N (or "General"): Brief description
→ Problem: What exactly is wrong and why it matters
→ Fix: The specific change to make, with a code example if helpful

After all issues, add a **SUMMARY** section:
- Overall assessment (one sentence)
- Issue count by severity
- Whether this code is safe to merge as-is

If the code has no issues in a category, do not mention that category.
If the code is genuinely clean, say so briefly — do not invent issues.

PHP CODE TO REVIEW:
```php
{$phpCode}

PROMPT; }


This prompt does several things deliberately:

It tells the model to **skip empty categories** — preventing the hallucinated “no issues found in this category” filler that wastes output tokens and makes the review look thorough when it isn’t.

It uses **explicit labels** with emoji severity markers — making the output parseable programmatically if you want to extract issue counts or severity.

It demands a **fix with code example** — not just “this is bad” but “here’s what to write instead.”

It ends with a **merge recommendation** — the binary signal that makes the reviewer useful in a CI gate.

Low temperature (`0.1`) makes this output deterministic and consistent across runs on the same code.

# Step 5: Build the PHP CLI Reviewer

Now the actual script. This is a standalone PHP CLI tool — no framework required:

!/usr/bin/env php

<?php

/**

  • Local PHP Code Reviewer
  • Powered by Ollama + open-weight code models
  • Zero cloud. Zero data leaving the machine. */

declare(strict_types=1);

// ── Configuration ─────────────────────────────────────────────────────────────

const OLLAMA_HOST = 'http://localhost:11434'; const DEFAULT_MODEL = 'qwen2.5-coder:7b'; const MAX_FILE_SIZE = 100 * 1024; // 100KB — larger files should be reviewed in chunks const TIMEOUT_SECONDS = 120; // Local models can be slow; give them time

// ── Argument parsing ──────────────────────────────────────────────────────────

$options = getopt('f:m:c:h', ['file:', 'model:', 'context:', 'help', 'diff', 'json']); $isHelp = isset($options['h']) || isset($options['help']);

if ($isHelp || empty($options['f'] ?? $options['file'] ?? null)) { echo <<<HELP Local PHP Code Reviewer

Usage:
  php reviewer.php -f <file.php>              Review a single file
  php reviewer.php -f <file.php> --diff       Review only git-staged changes
  php reviewer.php -f <file.php> --json       Output JSON for CI/tooling
  php reviewer.php -f <file.php> -m <model>   Use a specific Ollama model
  php reviewer.php -f <file.php> -c "context" Add context about the code

Examples:
  php reviewer.php -f src/PaymentService.php
  php reviewer.php -f src/UserController.php --diff
  php reviewer.php -f src/Auth.php -m codellama:13b -c "This handles OAuth token exchange"

HELP;
exit(0);

}

$filePath = $options['f'] ?? $options['file'] ?? null; $model = $options['m'] ?? $options['model'] ?? DEFAULT_MODEL; $context = $options['c'] ?? $options['context'] ?? ''; $useDiff = isset($options['diff']); $jsonOutput = isset($options['json']);

// ── Validate input ────────────────────────────────────────────────────────────

if (!file_exists($filePath)) { fwrite(STDERR, "Error: File not found: {$filePath}\n"); exit(1); }

if (!str_ends_with($filePath, '.php')) { fwrite(STDERR, "Warning: File does not have a .php extension. Proceeding anyway.\n"); }

$fileSize = filesize($filePath); if ($fileSize > MAX_FILE_SIZE) { fwrite(STDERR, sprintf( "Warning: File is %.1fKB. Large files may produce incomplete reviews. Consider reviewing sections separately.\n", $fileSize / 1024 )); }

// ── Load code ─────────────────────────────────────────────────────────────────

if ($useDiff) { // Review only the lines changed in the current git diff $code = shell_exec("git diff --cached -- " . escapeshellarg($filePath)); if (empty($code)) { echo "No staged changes found in {$filePath}. Nothing to review.\n"; exit(0); } $reviewTarget = "staged diff of {$filePath}"; } else { $code = file_get_contents($filePath); $reviewTarget = $filePath; }

// ── Check Ollama is running ───────────────────────────────────────────────────

function ollamaIsRunning(): bool { $ctx = stream_context_create(['http' => ['timeout' => 2]]); $result = @file_get_contents(OLLAMA_HOST . '/api/tags', false, $ctx); return $result !== false; }

if (!ollamaIsRunning()) { fwrite(STDERR, "Error: Ollama is not running. Start it with: ollama serve\n"); exit(1); }

// ── Build prompt ──────────────────────────────────────────────────────────────

function buildReviewPrompt(string $phpCode, string $context = ''): string { $contextSection = $context ? "Context about this code: {$context}\n\n" : '';

return <<<PROMPT

You are a senior PHP engineer conducting a thorough code review. Analyze the PHP code below and provide structured feedback.

{$contextSection}Review for the following categories — only report issues that actually exist, skip categories with no issues:

SECURITY (label: 🔴 CRITICAL)

  • SQL injection, XSS, CSRF exposure
  • Unsafe use of user input (\$_GET, \$_POST, \$_REQUEST)
  • Hardcoded credentials or secrets
  • Insecure file operations or command injection

CORRECTNESS (label: 🟠 BUG)

  • Logic errors, off-by-one errors, incorrect return types
  • Unhandled null values or edge cases
  • Incorrect use of == vs ===

PERFORMANCE (label: 🟡 PERFORMANCE)

  • N+1 query patterns
  • Unnecessary loops or redundant operations
  • Memory-intensive operations on large datasets

CODE QUALITY (label: 🔵 SUGGESTION)

  • Single responsibility violations
  • Poor naming, magic numbers, missing docblocks
  • Overly complex conditionals

FORMAT: [Label] Line N: Brief description → Problem: What is wrong and why it matters → Fix: The specific change, with a code example if helpful

End with a SUMMARY: overall assessment, issue count by severity, and whether this is safe to merge.

Skip categories with no issues. If the code is clean, say so briefly.

PHP CODE TO REVIEW:

{$phpCode}

PROMPT; }

// ── Call Ollama ───────────────────────────────────────────────────────────────

function callOllama(string $model, string $prompt): string { $payload = json_encode([ 'model' => $model, 'prompt' => $prompt, 'stream' => false, 'options' => [ 'temperature' => 0.1, 'num_predict' => 2048, ], ]);

$ch = curl_init(OLLAMA_HOST . '/api/generate');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $payload,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_TIMEOUT        => TIMEOUT_SECONDS,
]);

$response = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error    = curl_error($ch);
curl_close($ch);

if ($error) {
    throw new \RuntimeException("cURL error: {$error}");
}

if ($httpCode !== 200) {
    throw new \RuntimeException("Ollama returned HTTP {$httpCode}");
}

$decoded = json_decode($response, true);

if (!isset($decoded['response'])) {
    throw new \RuntimeException("Unexpected Ollama response structure");
}

return $decoded['response'];

}

// ── Run review ────────────────────────────────────────────────────────────────

if (!$jsonOutput) { echo "\n"; echo "🔍 Reviewing: {$reviewTarget}\n"; echo "🤖 Model: {$model}\n"; echo str_repeat('─', 60) . "\n\n"; echo "Analyzing... (this may take 10–60 seconds depending on hardware)\n\n"; }

try { $prompt = buildReviewPrompt($code, $context); $review = callOllama($model, $prompt);

if ($jsonOutput) {
    echo json_encode([
        'file'   => $filePath,
        'model'  => $model,
        'review' => $review,
        'timestamp' => date('c'),
    ], JSON_PRETTY_PRINT);
} else {
    echo $review . "\n";
    echo "\n" . str_repeat('─', 60) . "\n";
    echo "✅ Review complete. Model: {$model}\n\n";
}

// Exit with non-zero if CRITICAL issues found (for CI gate use)
if (str_contains($review, '🔴 CRITICAL')) {
    exit(2); // Distinct exit code for "review found critical issues"
}

exit(0);

} catch (\RuntimeException $e) { fwrite(STDERR, "Review failed: " . $e->getMessage() . "\n"); exit(1); }


Make it executable:

chmod +x reviewer.php


Run it:

./reviewer.php -f src/UserController.php ./reviewer.php -f src/PaymentGateway.php -m codellama:13b -c "Handles Stripe webhook validation" ./reviewer.php -f src/Auth.php --diff # Review only staged changes ./reviewer.php -f src/Api.php --json # Machine-readable output for CI


# Step 6: Wire Into a Git Pre-Commit Hook

Imagine a hook that automatically reviews every PHP file you’re about to commit — catching the obvious issues before they reach code review and before a human has to point them out.

.git/hooks/pre-commit

!/bin/bash

REVIEWER="./tools/reviewer.php" MODEL="qwen2.5-coder:7b" FAILED=0

Get all staged PHP files

STAGED_PHP=$(git diff --cached --name-only --diff-filter=ACM | grep '.php$') if [ -z "$STAGED_PHP" ]; then exit 0 # No PHP files staged - nothing to do fi echo "" echo "🔍 Running local AI code review on staged PHP files..." echo "" for FILE in $STAGED_PHP; do if [ ! -f "$FILE" ]; then continue fi echo "Reviewing: $FILE" php "$REVIEWER" -f "$FILE" --diff -m "$MODEL" EXIT_CODE=$? if [ $EXIT_CODE -eq 2 ]; then echo "" echo "⛔ CRITICAL issues found in $FILE. Fix before committing." FAILED=1 elif [ $EXIT_CODE -eq 1 ]; then echo "⚠️ Review failed for $FILE (Ollama may be unavailable). Proceeding." fi echo "" done if [ $FAILED -eq 1 ]; then echo "❌ Commit blocked - critical security or correctness issues found." echo " Review the feedback above, fix the issues, and try again." echo " To skip review (not recommended): git commit --no-verify" exit 1 fi echo "✅ Code review passed. Proceeding with commit." exit 0


Install the hook:

cp .git/hooks/pre-commit.example .git/hooks/pre-commit # if it doesn't exist yet chmod +x .git/hooks/pre-commit


Or, better — use a team-shared hooks directory so everyone on the project gets the hook:

In your project root:

mkdir -p .githooks cp pre-commit .githooks/ git config core.hooksPath .githooks


Any developer who clones the repo and runs `git config core.hooksPath .githooks` gets the hook automatically. Add it to your onboarding docs.

**The exit code strategy matters here:** The reviewer exits `0` for clean or minor issues, `2` for critical issues, and `1` for operational errors (Ollama not running). The hook only blocks commits on exit code `2` — not on tool failure. If Ollama isn't running, the commit goes through with a warning. You don't want a local AI tool to become a hard dependency that breaks your workflow when it's unavailable.

# Step 7: CI Integration for Pull Request Annotation

Pre-commit hooks protect individual developers. A CI step protects the repository — catching issues that slipped through or were bypassed with `--no-verify`.

Imagine this in a GitHub Actions workflow:

.github/workflows/ai-code-review.yml

name: Local AI Code Review (Self-Hosted) on: pull_request: paths:

  • '**.php' jobs: ai-review: runs-on: self-hosted # CRITICAL: must be self-hosted to keep code local steps:

  • uses: actions/checkout@v4 with: fetch-depth: 0 # Need full history for diff

  • name: Install Ollama (if not cached) run: | if ! command -v ollama &> /dev/null; then curl -fsSL https://ollama.com/install.sh | sh fi

  • name: Start Ollama and pull model run: | ollama serve & sleep 3 ollama pull qwen2.5-coder:7b

  • name: Review changed PHP files run: | CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '.php$')

    if [ -z "$CHANGED" ]; then echo "No PHP files changed. Skipping review." exit 0 fi

    REVIEW_OUTPUT="" HAS_CRITICAL=0

    for FILE in $CHANGED; do if [ -f "$FILE" ]; then RESULT=$(php tools/reviewer.php -f "$FILE" --json) EXIT_CODE=$? REVIEW_OUTPUT="${REVIEW_OUTPUT}\n\n### ${FILE}\n$(echo $RESULT | jq -r '.review')"

      if [ $EXIT_CODE -eq 2 ]; then
        HAS_CRITICAL=1
      fi
    fi

    done

    Write review to a file for the annotation step

    echo -e "$REVIEW_OUTPUT" > /tmp/ai_review.md echo "HAS_CRITICAL=$HAS_CRITICAL" >> $GITHUB_ENV

  • name: Post review as PR comment uses: actions/github-script@v7 with: script: | const fs = require('fs'); const review = fs.readFileSync('/tmp/ai_review.md', 'utf8');

    await github.rest.issues.createComment({
      owner: context.repo.owner,
      repo: context.repo.repo,
      issue_number: context.issue.number,
      body: `## 🤖 Local AI Code Review\n\n${review}\n\n---\n*Review powered by on-premise Ollama. No code was sent to external services.*`
    });
  • name: Fail on critical issues if: env.HAS_CRITICAL == '1' run: | echo "Critical security or correctness issues found. Please review the PR comments." exit 1

The critical line: runs-on: self-hosted. This is the entire point of the architecture. GitHub Actions runners are cloud machines. Your code would leave your network the moment you use a hosted runner. Self-hosted runners run on your own infrastructure — the code never leaves.

If you don’t have a self-hosted runner yet, tools like act let you run GitHub Actions locally for testing. For production, a self-hosted runner is a small investment — a spare dev machine, a VM in your own data center, or a server in your private cloud.

Step 8: Reviewing Multiple Files and Whole Directories

The single-file reviewer is useful. A directory scanner is more useful for onboarding a legacy codebase or doing a security audit.

#!/usr/bin/env php
<?php
// tools/review-directory.php
// Scan an entire directory and generate a report
declare(strict_types=1);
$directory = $argv[1] ?? '.';
$model     = $argv[2] ?? 'qwen2.5-coder:7b';
$outputFile = $argv[3] ?? 'review-report.md';
$phpFiles = new RecursiveIteratorIterator(
    new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS)
);
$report    = "# AI Code Review Report\n\n";
$report   .= "Generated: " . date('Y-m-d H:i:s') . "\n";
$report   .= "Model: {$model}\n";
$report   .= "Directory: {$directory}\n\n";
$report   .= "---\n\n";
$criticalCount = 0;
$bugCount      = 0;
$totalFiles    = 0;
foreach ($phpFiles as $file) {
    if ($file->getExtension() !== 'php') continue;
    // Skip vendor, cache, and generated files
    $path = $file->getPathname();
    if (str_contains($path, '/vendor/') ||
        str_contains($path, '/cache/') ||
        str_contains($path, '/storage/')) {
        continue;
    }
    $totalFiles++;
    echo "Reviewing {$path}...\n";
    $result = shell_exec(sprintf(
        'php %s -f %s -m %s --json 2>/dev/null',
        escapeshellarg(__DIR__ . '/reviewer.php'),
        escapeshellarg($path),
        escapeshellarg($model)
    ));
    $decoded = json_decode($result, true);
    $review  = $decoded['review'] ?? 'Review failed for this file.';
    $hasCritical = str_contains($review, '🔴 CRITICAL');
    $hasBug      = str_contains($review, '🟠 BUG');
    if ($hasCritical) $criticalCount++;
    if ($hasBug) $bugCount++;
    // Only include files with actual issues in the report
    $hasIssues = $hasCritical || $hasBug || str_contains($review, '🟡 PERFORMANCE');
    if ($hasIssues) {
        $report .= "## " . ($hasCritical ? '🔴 ' : ($hasBug ? '🟠 ' : '🟡 '));
        $report .= "`{$path}`\n\n";
        $report .= $review . "\n\n---\n\n";
    }
}
$report .= "## Overall Summary\n\n";
$report .= "- Total files reviewed: {$totalFiles}\n";
$report .= "- Files with critical issues: {$criticalCount}\n";
$report .= "- Files with bugs: {$bugCount}\n";
file_put_contents($outputFile, $report);
echo "\n✅ Report written to: {$outputFile}\n";
echo "Critical files: {$criticalCount} | Bug files: {$bugCount}\n";

Run it:

php tools/review-directory.php src/ qwen2.5-coder:7b security-audit.md

This produces a Markdown report covering every PHP file in src/, skipping vendor and generated files, containing only files that have issues. Clean files are silently excluded — the report stays focused on what needs attention.

Hardware Considerations

This is the part most tutorials skip, and it directly determines whether your setup is usable or frustrating.

What actually matters: RAM

Model size in RAM (with 4-bit quantization, the default in Ollama):

  • 7B model → ~5–6GB RAM
  • 13B model → ~9–10GB RAM
  • 16B model → ~12–13GB RAM
  • 32B model → ~22–24GB RAM
  • 70B model → ~45–50GB RAM

RAM that’s allocated to the model isn’t available to your OS. On a 16GB machine, a 13B model leaves ~4–6GB for everything else — workable, but tight if you’re running Docker, a browser, and an IDE simultaneously.

GPU acceleration makes a significant difference:

Without GPU (CPU only): A 7B model might take 15–30 seconds to generate a review. A 13B model could take 2–5 minutes.

With GPU (CUDA or Apple Silicon): A 7B model generates in 2–5 seconds. A 13B model in 10–20 seconds.

Ollama handles GPU detection automatically on NVIDIA (CUDA), AMD (ROCm), and Apple Silicon (Metal). No configuration needed — if a GPU is present and has enough VRAM, Ollama uses it.

Apple Silicon is particularly well-suited to this workflow. The unified memory architecture means a 32GB M3 Pro can run a 32B model and share that memory with the OS and other apps simultaneously — something a discrete GPU with 32GB VRAM would cost significantly more.

The minimum viable setup: 16GB RAM, a reasonably modern CPU, and qwen2.5-coder:7b. This gets you genuinely useful reviews at maybe 20–40 seconds per file — acceptable for a deliberate "review this file" workflow, slower for a pre-commit hook on large files.

Pitfalls to Avoid

Running Ollama on the wrong machine. The entire point of this setup is that code stays local. If your Ollama instance is on a remote server you’re SSHing into, you’re still sending code over a network — just an internal one. For true air-gapped operation, Ollama needs to run on the same machine as your code, or on a dedicated on-premise server with no external connectivity.

Using too large a model for your hardware. A 13B model thrashing swap memory on a 16GB machine is slower and produces worse results than a 7B model running cleanly in RAM. Start smaller and upgrade if quality is genuinely insufficient.

Treating the review as authoritative. A 7B local model is capable but not infallible. It will miss issues a senior developer would catch. It will occasionally flag things that aren’t problems. Treat it as a first-pass filter, not a replacement for human review — the goal is to catch the obvious issues automatically so human reviewers can focus on architecture and business logic.

Ignoring the --diff flag in hooks. Reviewing the entire file on every commit is slow and noisy. The --diff flag sends only the staged changes — much faster, and the feedback is immediately relevant to what you actually changed.

Not caching model downloads in CI. Pulling a 4GB model on every CI run defeats the purpose. Use Ollama’s model directory as a cache path in your self-hosted runner configuration.

Blocking commits on tool failure. If Ollama crashes, runs out of memory, or simply isn’t running, your pre-commit hook should warn and proceed — not block the commit entirely. The reviewer is a helper, not a gatekeeper for the build system itself.

Mini Q&A

Q: How does the review quality compare to cloud models like Claude or GPT-4? Honestly, a 7B local model is noticeably weaker than a frontier cloud model on complex reasoning tasks. It catches obvious security issues (SQL injection, XSS, unescaped output) reliably. It’s less reliable on subtle architectural problems, complex business logic bugs, or nuanced performance issues. A 32B model significantly closes this gap. Think of local models as a capable junior reviewer — great at the obvious stuff, not yet at senior level.

Q: Can I fine-tune the model on our internal PHP codebase? Yes, but it’s a significant undertaking. Ollama supports running GGUF-format models, and tools like llama.cpp support fine-tuning. A more practical approach for most teams: use few-shot examples in your system prompt — include two or three examples of "bad PHP" and "good PHP" before the code under review. This guides the model without fine-tuning.

Q: What about PHP-specific tools like PHPStan or Psalm — why use AI instead? You shouldn’t use it instead — you should use it alongside. PHPStan and Psalm are strictly better at type checking, undefined variable detection, and statically-analyzable correctness issues. They run in milliseconds and don’t hallucinate. The AI reviewer adds value in areas static analysis can’t reach: business logic errors, naming quality, architectural patterns, and security issues that require semantic understanding of what the code is doing. Run both.

Q: How do I keep the model updated? ollama pull qwen2.5-coder:7b checks for and downloads updates. New model versions release regularly with improved capabilities. Build a monthly model update into your maintenance calendar — it's a single command.

Why This Matters Beyond Compliance

The compliance argument is real and important — but it’s not the only reason to think about local AI tooling.

Imagine what happens to your team’s review process when every developer has a reviewer that catches the security obvious issues automatically, before anyone else has to look. Code review becomes a conversation about architecture and trade-offs rather than a corrections session for XSS vulnerabilities and SQL injection patterns that static analysis should have caught.

Junior developers get immediate, private feedback. They learn patterns — not from a colleague’s code review comment that arrives the next day, but from a reviewer that responds in 20 seconds. That feedback loop changes how people develop, not just what they commit.

And because the model runs locally, there’s no API bill that grows with usage. No conversation limits. No quota concerns. The tool runs as many times as the team needs it to, on whatever code they’re working on, with zero marginal cost after setup.

That’s the case for local AI tooling that goes beyond compliance. It’s about building a development environment where the feedback loop is fast, private, and continuous — and where the constraints your legal team imposes don’t have to mean your team falls behind.

Wrap-Up: Local, Capable, and Actually Yours

The stack is simple: Ollama runs the model. The PHP CLI script calls it. The git hook runs the script. The CI job guards the repository. Everything happens on hardware you control.

The initial setup takes an afternoon. The ongoing maintenance is a monthly ollama pull. The return — automatic first-pass review on every PHP file that touches your codebase — compounds every day after that.

Your compliance team says the code can’t leave the building. Now it doesn’t have to.

Your 7-Day Mini-Plan

  • Day 1: Install Ollama. Pull qwen2.5-coder:7b. Run a manual review on a file you know has issues.
  • Day 2: Build the reviewer.php CLI script. Test it on five files across your codebase.
  • Day 3: Tune the prompt. Add domain-specific context about your PHP stack (Laravel, Symfony, vanilla, etc.).
  • Day 4: Install the pre-commit hook. Commit a file with a deliberate XSS vulnerability. Watch it get caught.
  • Day 5: Run the directory scanner on src/. Review the report. Prioritize the critical issues.
  • Day 6: Set up a self-hosted CI runner if you have one. Wire in the GitHub Actions workflow.
  • Day 7: Show the setup to one other developer on your team. The local AI reviewer only compounds in value when the whole team uses it.

Key metric to track: Number of security and correctness issues caught before code review (by the AI) vs. during code review (by a human). A working setup should shift that ratio measurably within a month.

Common mistake to avoid: Letting the hook block commits when Ollama isn’t running. Build the fallback in from day one — the tool helps when it’s available, it doesn’t obstruct when it isn’t.

CTA

Has your team been blocked from using AI tooling by compliance or data sovereignty requirements? Or have you already built a local AI setup with a different stack? Share it in the comments — especially the model recommendations. The local AI tooling space moves fast and the community benchmarks are more useful than any single article.

And if this gave your team a path through the “no cloud AI” policy that felt like a dead end, share it with the developer who’s been fighting that battle.

Closing Loop

Imagine your next code review meeting — and the security section is shorter than usual. Not because people are skipping it, but because the obvious issues didn’t make it to review. The AI caught them at commit time, the developer fixed them, and the human reviewers spent their time on the things that actually require human judgment.

That meeting exists. It’s running on your hardware, with your models, on your network.

The code never left the building.

“People Also Ask” — 8 Questions & Answers

1. How do I run an AI code reviewer locally without sending code to the cloud? Use Ollama to run an open-weight model (like Qwen2.5-Coder or CodeLlama) on your local machine. Ollama exposes a REST API on localhost:11434 — build a PHP CLI script that reads your PHP files and sends them to this local API. Nothing leaves your machine.

2. What is Ollama and how does it work? Ollama is an open-source tool that makes running large language models locally as simple as ollama pull model-name and ollama run model-name. It handles model downloading, quantization, and GPU/CPU offloading, and exposes an OpenAI-compatible REST API on localhost.

3. Which local AI model is best for PHP code review? qwen2.5-coder:7b is the recommended starting point — it fits in 8GB RAM, runs reasonably fast on most machines, and has strong PHP understanding. For better quality on more powerful machines, qwen2.5-coder:32b or deepseek-coder-v2:16b produce significantly better results.

4. How do I add a local AI code review to git pre-commit hooks? Write a bash pre-commit hook that runs git diff --cached --name-only to get staged PHP files, then calls your PHP reviewer script on each one. Exit with code 1 if critical issues are found to block the commit. Install with chmod +x .git/hooks/pre-commit.

5. Can I use local AI for PHP code review in GitHub Actions? Yes — but only with a self-hosted runner. GitHub’s hosted runners are cloud machines; running Ollama on them would still send your code to GitHub’s infrastructure. A self-hosted runner on your own hardware keeps everything local while still integrating with the GitHub Actions workflow.

6. How does local AI code review compare to PHPStan or Psalm? PHPStan and Psalm are strictly better at type-checking and statically-analyzable correctness. They’re faster, more precise, and don’t hallucinate. Local AI adds value in areas static analysis can’t reach: semantic understanding of business logic, naming quality, architectural patterns, and contextual security issues. Use both.

7. How much RAM do I need to run local AI for code review? A 7B model requires approximately 5–6GB RAM with 4-bit quantization. 16GB total system RAM is the practical minimum. A 13B model needs ~10GB, a 32B model needs ~24GB. Apple Silicon unified memory is particularly well-suited — a 32GB M3 MacBook can run a 32B model alongside other applications.

8. How do I keep local AI models updated for code review? Run ollama pull model-name periodically — it checks for and downloads newer versions automatically. New model versions release frequently with capability improvements. A monthly update check is sufficient for most teams.

Model benchmark note: Performance comparisons between local models are based on community benchmarks as of early 2025 and shift regularly as new model versions are released. Before committing to a specific model, run your own benchmark on a representative sample of your PHP code — the best model for your codebase depends on your specific patterns and hardware.


메타데이터
post_id
4eefc7a496b6
slug
building-a-local-ai-assistant-for-php-code-review-without-sending-code-to-the-cloud-4eefc7a496b6
url
https://levelup.gitconnected.com/building-a-local-ai-assistant-for-php-code-review-without-sending-code-to-the-cloud-4eefc7a496b6
canonical_url
https://levelup.gitconnected.com/building-a-local-ai-assistant-for-php-code-review-without-sending-code-to-the-cloud-4eefc7a496b6
author_url
https://medium.com/@annxsa
status
ok
fetched_at
2026-06-09 15:37:30