Skip to content

PHP Connector

The PHP connector is a standalone PHP script that integrates your PHP applications with Patcherly for automated error detection and fixing.

Registry: Packagist — patcherly/php-connector — unpinned composer require resolves to latest; PHP versions independently from other stacks (see Connectors overview — Package registries).

Post-apply automation (optional): supported on PHP targets. PHP under mod_php / PHP-FPM does not need a process restart for code changes to take effect — each request loads the latest file. So the most common reasons to enable a manifest on PHP are:

  • OPcache reset when you run with opcache.validate_timestamps=0 (a typical production tuning) — usually systemctl reload php-fpm.
  • Framework cache rebuildphp artisan config:clear, php bin/console cache:clear --env=prod, drush cache:rebuild.
  • Composer autoload regeneration when a fix introduces a new class — composer dump-autoload --optimize --no-dev.
  • Queue / worker restart so long-running PHP-CLI processes pick up the new code — php artisan queue:restart, php artisan horizon:terminate, supervisorctl restart laravel-worker:*.
  • PHPUnit / Pest smoke tests to verify the patched code before declaring the fix done — ./vendor/bin/phpunit --testsuite=Smoke, php artisan test --filter=SmokeTest.

After each successful apply, the agent fetches a signed manifest from Patcherly and runs the approved steps. Each step runs without going through a shell (proc_open with an argv array on PHP 7.4+). String-form run applies a built-in denylist for shell metacharacters (&&, ||, |, ;, backticks, $(, >, <) before tokenisation; array-form skips that string scan (argv[0] allowlist still applies). Per-error_id deduplication prevents double-runs within a single agent run.

See App restart automation for full configuration steps and per-language YAML examples.

Easiest setup: if you have terminal access, use the Universal Installer — no need to configure anything manually. The steps below are for manual installation or Composer installs.

Composer (Packagist)

composer global require patcherly/php-connector
patcherly login
patcherly heartbeat   # optional cron/systemd-timer liveness ping
php patcherly_agent.php   # long-running poll loop (same as universal-installer layout)

Universal installer remains the recommended first path when you want a full tree under /opt/patcherly-connector with systemd wiring.

Quick Start

Get up and running in minutes:

# 1. Download the PHP connector files to your server
# Place patcherly_agent.php and supporting files in your project directory

# 2. Pair with your Patcherly target (OAuth Device Authorization Grant)
php patcherly_agent.php login

# 3. Run the agent
php patcherly_agent.php

patcherly login (or php patcherly_agent.php login) prints a verification_uri and a short user code. Open the URL in any browser, sign in to your Patcherly dashboard, select the target, and confirm the code. Credentials are saved to ~/.patcherly/credentials.json.

Default is full (WordPress defaults to off until chosen in WP admin).

patcherly context get
patcherly context set full|minimal|off
patcherly context upload

Env PATCHERLY_CONTEXT_CONSENT overrides {PATCHERLY_CACHE_DIR}/context_consent.

The connector will automatically: - Read OAuth credentials from ~/.patcherly/credentials.json - Start monitoring your application logs - Sign requests with the per-token HMAC secret from the credential file

Installation

Step 1: Download the Connector

  1. Download the PHP Connector Files
  2. Download php-connector.zip from the latest connector release.
  3. Extract it into your install directory — the universal installer's default is /opt/patcherly-connector/ on Linux/macOS and %USERPROFILE%\patcherly-connector\ on Windows; you can pick any path as long as you reference the same one in the start commands and systemd/cron entries below. Use all files from the zip — the agent needs every module (OAuth client, credential store, patcherly CLI, queue manager, etc.) to pair and run; missing files break login or apply.

Tip: Most installs go through the universal installer instead (curl https://api.patcherly.com/install | bash) which downloads and extracts the right zip for you. The manual path above is for environments where the installer can't run (locked-down hosting, no shell access, etc.).

Step 2: Pair with a Target (OAuth)

Run the login command once per target server:

php patcherly_agent.php login

Follow the prompt: open the printed URL in any browser, sign in, and confirm the user code. Credentials are saved to ~/.patcherly/credentials.json. To log out and revoke the token, run php patcherly_agent.php logout — Patcherly also flips the target row on the dashboard from healthy/stale to inactive immediately on the same call (no more "still says healthy three days after I logged out" surprises).

If you want to customise paths, create a .env file:

# OPTIONAL: Custom paths
PATCHERLY_BACKUP_ROOT=../backups
PATCHERLY_QUEUE_PATH=patcherly_queue.jsonl
PATCHERLY_IDS_PATH=patcherly_ids.json

Step 3: Initialize and Start the Agent

Option A: Run as Standalone Script

Run the agent directly from the command line:

# From the connector directory
cd /opt/patcherly-connector
php patcherly_agent.php

# Or from your project root
php /opt/patcherly-connector/patcherly_agent.php

The agent tails the configured log file in a 5-second poll loop. There are no command-line options for the cli SAPI entry — log path and optional paths are configured via environment variables (see Step 2 above); credentials come from ~/.patcherly/credentials.json. For the optional /api/file-content + /local-approvals HTTP server, see the "Local file-content + approvals HTTP server" section below.

Optional: Local file-content + approvals HTTP server (php -S)

The PHP connector ships an optional local HTTP server that exposes:

  • POST /api/file-content — sanitised file-context retrieval for AI analysis
  • GET /local-approvals — relays the list of pending approvals from Patcherly so on-device tooling can fetch them
  • POST /local-approvals/{id}/(approve|reject-patch) — relays approve / reject-patch decisions back to Patcherly

This server is hosted by PHP's built-in web server (SAPI cli-server), not by the agent CLI. Launch it explicitly:

# Bind only to localhost. Pick any port; 8083 is the default the dashboard expects.
php -S 127.0.0.1:8083 /opt/patcherly-connector/patcherly_agent.php

Run that in a separate shell from php patcherly_agent.php (the poll loop). The two processes are independent — the poll loop pulls fixes and applies them; the php -S server only serves the local endpoints listed above.

Security model for those endpoints:

  • /api/file-content requires Authorization: Bearer <oauth-access-token> plus an X-Patcherly-Signature + X-Patcherly-Timestamp (5-minute replay window). The HMAC is computed with the per-token secret over the canonical request shape. The signed JSON body must include error_id.
  • File reads are constrained to PATCHERLY_TARGET_ROOTS (path-separator delimited list of allowed roots) or the connector's working directory, enforced after realpath() so symlink escapes are blocked.
  • /local-approvals endpoints require Authorization: Bearer <oauth-access-token>. The {id} path segment on /approve and /reject-patch must match ^[A-Za-z0-9_-]{1,128}$. /reject-patch requires a resolution body (manual_suggestion, manual_own, or not_needed).
  • Always bind to 127.0.0.1 (the example above). The server has no TLS and was not designed for direct exposure to the internet — front it with your existing reverse proxy if you must reach it remotely.

Option B: Include in Your Application

In your PHP application, include and initialize the agent:

<?php
require_once __DIR__ . '/patcherly-connector/patcherly_agent.php';

// The agent will automatically:
// - Read OAuth credentials from ~/.patcherly/credentials.json
// - Read configuration from environment variables / .env
// - Discover workspace and target IDs
// - Start monitoring for errors
// - Process fixes from the queue

$agent = new PHPAgent();
$agent->monitorLogs(); // This runs continuously
?>

Option C: Run as Background Process

# Run in background
nohup php patcherly-connector/patcherly_agent.php > /dev/null 2>&1 &

# Or with logging
nohup php patcherly-connector/patcherly_agent.php >> logs/agent.log 2>&1 &

Option D: Run via Cron Job

Add to your crontab for periodic checks:

# Check every 5 minutes
*/5 * * * * /usr/bin/php /opt/patcherly-connector/patcherly_agent.php

# Or run continuously (check every minute)
* * * * * /usr/bin/php /opt/patcherly-connector/patcherly_agent.php

Option E: Run as Systemd Service (Linux)

Create /etc/systemd/system/patcherly-connector.service:

[Unit]
Description=Patcherly PHP Agent
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/patcherly-connector
EnvironmentFile=/opt/patcherly-connector/.env
ExecStart=/usr/bin/php /opt/patcherly-connector/patcherly_agent.php
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Then enable and start:

sudo systemctl enable patcherly-connector
sudo systemctl start patcherly-connector

Configuration

Environment Variables

Variable Description Default
SERVER_URL or PATCHERLY_API_BASE Leave unset for normal use (https://api.patcherly.com). Only set if Patcherly Support gives you a specific API host — match patcherly login --api-base. (unset — production default)
PATCHERLY_BACKUP_ROOT Directory for backups (outside webroot) ../backups
PATCHERLY_QUEUE_PATH Path to queue file patcherly_queue.jsonl
PATCHERLY_IDS_PATH Path to IDs cache file patcherly_ids.json

Credentials come from ~/.patcherly/credentials.json (created by php patcherly_agent.php login).

HMAC Signing

HMAC signing uses the per-token secret bundled in ~/.patcherly/credentials.json at OAuth issuance. You do not need to configure a secret manually: - Each OAuth token includes its own unique HMAC signing secret - The connector reads it from the credential file automatically - HMAC verification is always mandatory for fix payloads

Backup Directory

Important: The backup directory should be outside your webroot for security:

  • Recommended: ../backups (parent directory)
  • Alternative: Set via PATCHERLY_BACKUP_ROOT environment variable
  • Security: The agent writes .htaccess and IIS web.config in the backup directory automatically — these work on Apache with AllowOverride All and on IIS, but are ignored on Nginx and on Apache with AllowOverride None. For full coverage (including ready-to-paste Nginx + Apache vhost snippets), see Hardening: backup folders and the public web.

Running the Connector

Run the agent as a background process:

php patcherly_agent.php > /dev/null 2>&1 &

Or with logging:

php patcherly_agent.php >> logs/agent.log 2>&1 &

Method 2: Cron Job

Add to your crontab for periodic checks:

*/5 * * * * /usr/bin/php /opt/patcherly-connector/patcherly_agent.php

Method 3: Systemd Service (Linux)

Create /etc/systemd/system/patcherly-connector.service:

[Unit]
Description=Patcherly PHP Agent
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/patcherly-connector
EnvironmentFile=/opt/patcherly-connector/.env
ExecStart=/usr/bin/php /opt/patcherly-connector/patcherly_agent.php
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Then enable and start:

sudo systemctl enable patcherly-connector
sudo systemctl start patcherly-connector

Supported Applications & File Types

✅ Supported Applications

The PHP connector is designed for PHP web applications:

  • WordPress - Content management system (also see WordPress Connector)
  • Laravel - PHP web application framework
  • Symfony - PHP web application framework
  • CodeIgniter - PHP web framework
  • Custom PHP applications - Any PHP-based website, API, or service
  • PHP scripts - Standalone PHP scripts and utilities

✅ Supported File Types

The connector can patch and validate syntax for:

  • .php - Standard PHP files
  • .phtml - PHP template files

❌ Not Supported

Other file types: - Compiled PHP bytecode (.phar files are not patched, but can contain .php files) - Other languages (JavaScript, Python, etc.) - Client-side JavaScript (even if embedded in PHP files)

Note: The connector validates PHP syntax using php -l before and after applying patches to ensure code integrity.

How It Works

1. Error Detection

The connector monitors your application's error log file:

  • Watches for new log entries containing "ERROR"
  • Captures error context (message, stack trace, file, line)
  • Sends errors to Patcherly for analysis

Did you know? The PHP agent only reads log files it is set up to tail — preset paths (Apache/Nginx/PHP logs, common framework locations) plus any custom paths you add under Targets → Log paths. Laravel (storage/logs/laravel.log), Symfony (var/log/), and plain PHP apps all work when errors are written there. If nothing shows up, check that logging is enabled and the path is on the list. For very early fatals before your framework logger runs, ensure PHP's own error_log points at a watched file (log_errors=On in php.ini). See Custom log paths and the connectors overview.

2. Fix Processing

When a fix is available:

  • Connector polls for approved fixes
  • Downloads fix payload with HMAC signature
  • Verifies signature (if HMAC enabled)
  • Creates backup before applying
  • Applies fix using unified diff patches
  • Reports result back to server

3. Queue Management

The connector uses a queue system for reliability:

  • Queue File: Stores pending operations (patcherly_queue.jsonl)
  • Retry Logic: Automatically retries failed operations
  • Dead Letter Queue: Stores permanently failed operations
  • Queue Limits: Maximum 1000 entries (oldest evicted first)

Features

Automatic Backup

  • Before Every Fix: Automatic backup of all existing paths in the patch (copies are written to your backup directory, e.g. PATCHERLY_BACKUP_ROOT)
  • Versioned Backups: Timestamped and error ID tagged
  • SHA256 Checksums: Verify backup integrity
  • Gzip Compression: Efficient storage
  • Indefinite Local Retention (Customer-Managed): These backups are kept in your environment to preserve version history. You can delete them manually at any time.

HMAC Signature Verification

Each OAuth token carries its own per-token HMAC signing secret, stored in ~/.patcherly/credentials.json:

  • Per-token secret: bound to the OAuth credential — no global shared secret
  • Mandatory verification: HMAC verification is always required for fix payloads
  • No manual configuration: The secret is bundled into the credential file at OAuth issuance

The connector verifies all fix payloads:

  • Timestamp Verification: Prevents replay attacks (5-minute window)
  • Signature Verification: Ensures payload authenticity
  • Timing-Safe Comparison: Prevents timing attacks

OAuth Token Rotation

To rotate credentials:

  1. Run php patcherly_agent.php logout to revoke the current token server-side
  2. Run php patcherly_agent.php login to pair a fresh OAuth token
  3. Restart the agent

App restart automation

For PHP website/app (target) types on plans that include app restart automation, the connector can run post-apply shell steps after a successful patch — for example OPcache reset, framework cache clears (php artisan config:clear, php bin/console cache:clear), composer dump-autoload, queue worker restarts (php artisan queue:restart, supervisorctl restart laravel-worker:*), or a PHPUnit / Pest smoke suite (./vendor/bin/phpunit --testsuite=Smoke). PHP under mod_php / PHP-FPM does not need a literal restart for code changes to take effect — this feature is most useful for cache invalidation, OPcache resets, and post-patch tests.

Configuration is only in the dashboard (Targets → App restart On/Off + Configure); pause without deleting the YAML via the toggle. The command list is delivered over the connector API and verified before execution.

  • Guide: App restart automation — full setup, safety model, and per-language YAML examples.
  • Command forms: prefer array-form run when -r / shell snippets need semicolons; string-form applies the denylist. Defaults allowlist includes php / systemctl / pm2 / supervisorctl.
  • YAML parser: ext-yaml is used when present (yaml_parse), with a built-in fallback parser for shared hosts that don't have the PECL extension.
  • Concurrency: the PHP CLI agent processes errors sequentially in its monitoring loop, so the per-process workflow lock that Python and Node ship is not needed here. Per-error_id deduplication still prevents double-runs within a single agent run.
  • Optional environment variables on the agent host (use only if your connector README or support has you set them):
    • PATCHERLY_POST_APPLY_DRY_RUN=1 — log what would run without executing commands.
    • PATCHERLY_POST_APPLY_TEST_DELAY_SEC=<seconds> — pause before running tests when post-apply steps actually ran (useful when systemctl reload php-fpm needs a moment before PHPUnit can reconnect to a pooled DB).

Verify detection end-to-end with patcherly send-test

After pairing, you can prove the whole pipeline (connector → API → dashboard error feed) works without waiting for a real exception. Test events only flow while you've explicitly opened a Test Mode window from your Patcherly dashboard — this gate prevents an exposed connector credential, a curious script, or a CI run from spraying synthetic traffic into your real error feed and triggering notifications. The window is per-target and lasts 30 minutes; you stay in control.

  1. In your Patcherly dashboard, open Targets, click your target, and flip the Test Mode toggle ON — this opens a 30-minute window for this target only.
  2. On the server where you ran php patcherly_agent.php login, run:
    php patcherly_cli.php send-test
    
  3. The synthetic event lands in your dashboard under Errors, flagged as a sample so it does not affect your metrics or fire notifications.

The CLI auto-checks the Test Mode window (GET /v1/targets/connector-status) before sending. If the window is closed, it prints a clear "Test error window is not open for this target" message plus the exact dashboard URL where you can enable it — no traffic is sent. Pass --no-preflight to skip that check (used in tests).

Keep the connection alive on quiet hosts (patcherly heartbeat)

Once you've paired with patcherly login, the connection is designed to stay alive without you having to reconnect — even if the host sits idle for weeks. Every time the connector talks to Patcherly (sending an error, fetching a fix, polling status), the connection's credentials are quietly refreshed in the background.

If the host you paired is paired but rarely runs the connector (low-traffic site, weekend job server, etc.), wire a tiny once-a-day heartbeat into cron so the credentials never age out:

0 6 * * *  /usr/bin/php /opt/patcherly/patcherly_cli.php heartbeat

That one daily call (a) refreshes your credentials long before they expire, and (b) tells the dashboard your connector is still alive (so the "Connector is healthy" check in the onboarding panel stays green). Exits 0 on success, 2 if the host isn't paired yet, 1 on any HTTP / network failure — so cron will email you only when something is actually wrong.

Troubleshooting

Connector Not Connecting

  1. Check OAuth credentials
  2. Make sure ~/.patcherly/credentials.json exists on the target server. If it is missing or empty, re-pair with php patcherly_agent.php login.

  3. Test API Connectivity

  4. Check network connectivity and firewall (outbound HTTPS to api.patcherly.com)
  5. Verify Patcherly service is running

  6. Check Logs

  7. Review logs/error.log for connection errors
  8. Check PHP error logs

Fixes Not Applying

  1. Check that dry-run is off for the target. While dry-run is on, the connector previews the change but does not write files. Edit the target on the Targets page in the dashboard and turn off Dry-run when you're ready.
  2. Verify file permissions
  3. Make sure the PHP process can write to your backup directory.
  4. Make sure the PHP process can modify your application files.
  5. Re-pair if you see signature errors
  6. If the connector logs report an "HMAC signature mismatch" or "authentication failed", re-pair with php patcherly_agent.php login to issue a fresh token and signing secret.
  7. Make sure the server clock is in sync (NTP).
  8. Restart the connector after re-pairing.

Backup Issues

  1. Backup Directory Not Writable
  2. Check directory permissions: chmod 755 ../backups
  3. Verify PHP process has write access

  4. Backups Not Found

  5. Check PATCHERLY_BACKUP_ROOT environment variable
  6. Verify backups are outside webroot

Security Best Practices

  1. Protect Backup Directory
  2. Prefer storing backups outside the webroot; if they must sit under it, add the Nginx or Apache vhost snippet from Hardening: backup folders and the public web in addition to any .htaccess the agent creates
  3. Restrict file permissions (600 for backup files)

  4. Secure OAuth Credentials

  5. ~/.patcherly/credentials.json contains your OAuth token — restrict permissions (600) and never commit it to version control
  6. To rotate, run php patcherly_agent.php logout then php patcherly_agent.php login

  7. HMAC Signing

  8. The per-token HMAC secret is in ~/.patcherly/credentials.json — no manual configuration needed
  9. HMAC verification is always enabled and required

  10. Monitor Logs

  11. Regularly review connector logs
  12. Monitor for suspicious activity
  13. Set up log rotation

Next Steps