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) — usuallysystemctl reload php-fpm. - Framework cache rebuild —
php 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.
Context collection consent¶
Default is full (WordPress defaults to off until chosen in WP admin).
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¶
- Download the PHP Connector Files
- Download
php-connector.zipfrom the latest connector release. - 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:
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 analysisGET /local-approvals— relays the list of pending approvals from Patcherly so on-device tooling can fetch themPOST /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-contentrequiresAuthorization: Bearer <oauth-access-token>plus anX-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 includeerror_id.- File reads are constrained to
PATCHERLY_TARGET_ROOTS(path-separator delimited list of allowed roots) or the connector's working directory, enforced afterrealpath()so symlink escapes are blocked. /local-approvalsendpoints requireAuthorization: Bearer <oauth-access-token>. The{id}path segment on/approveand/reject-patchmust match^[A-Za-z0-9_-]{1,128}$./reject-patchrequires aresolutionbody (manual_suggestion,manual_own, ornot_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:
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_ROOTenvironment variable - Security: The agent writes
.htaccessand IISweb.configin the backup directory automatically — these work on Apache withAllowOverride Alland on IIS, but are ignored on Nginx and on Apache withAllowOverride None. For full coverage (including ready-to-paste Nginx + Apache vhost snippets), see Hardening: backup folders and the public web.
Running the Connector¶
Method 1: Continuous Monitoring (Recommended)¶
Run the agent as a background process:
Or with logging:
Method 2: Cron Job¶
Add to your crontab for periodic checks:
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:
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 ownerror_logpoints at a watched file (log_errors=Oninphp.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:
- Run
php patcherly_agent.php logoutto revoke the current token server-side - Run
php patcherly_agent.php loginto pair a fresh OAuth token - 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
runwhen-r/ shell snippets need semicolons; string-form applies the denylist. Defaults allowlist includesphp/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_iddeduplication 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 whensystemctl reload php-fpmneeds 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.
- 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.
- On the server where you ran
php patcherly_agent.php login, run: - 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:
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¶
- Check OAuth credentials
-
Make sure
~/.patcherly/credentials.jsonexists on the target server. If it is missing or empty, re-pair withphp patcherly_agent.php login. -
Test API Connectivity
- Check network connectivity and firewall (outbound HTTPS to
api.patcherly.com) -
Verify Patcherly service is running
-
Check Logs
- Review
logs/error.logfor connection errors - Check PHP error logs
Fixes Not Applying¶
- 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.
- Verify file permissions
- Make sure the PHP process can write to your backup directory.
- Make sure the PHP process can modify your application files.
- Re-pair if you see signature errors
- If the connector logs report an "HMAC signature mismatch" or "authentication failed", re-pair with
php patcherly_agent.php loginto issue a fresh token and signing secret. - Make sure the server clock is in sync (NTP).
- Restart the connector after re-pairing.
Backup Issues¶
- Backup Directory Not Writable
- Check directory permissions:
chmod 755 ../backups -
Verify PHP process has write access
-
Backups Not Found
- Check
PATCHERLY_BACKUP_ROOTenvironment variable - Verify backups are outside webroot
Security Best Practices¶
- Protect Backup Directory
- 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
.htaccessthe agent creates -
Restrict file permissions (600 for backup files)
-
Secure OAuth Credentials
~/.patcherly/credentials.jsoncontains your OAuth token — restrict permissions (600) and never commit it to version control-
To rotate, run
php patcherly_agent.php logoutthenphp patcherly_agent.php login -
HMAC Signing
- The per-token HMAC secret is in
~/.patcherly/credentials.json— no manual configuration needed -
HMAC verification is always enabled and required
-
Monitor Logs
- Regularly review connector logs
- Monitor for suspicious activity
- Set up log rotation