CommunityResearch & Data Analysisgithub.com

dbravoan/opencode-skills

In-place upgrade of legacy PHP 5.x/7.x codebases (custom CMS, TemplatePower, mysql_*, ereg, short tags, PHP4 constructors) to run on PHP 8.3/8.4, then deploy without Docker via a .env config and an FTP/rsync deploy.sh. Use for modernizing legacy PHP CMS/sites and sibling legacy projects, or when a legacy site must be moved to shared hosting (cPanel/Plesk) over FTP.

What is opencode-skills?

opencode-skills is a OpenCode agent skill that in-place upgrade of legacy PHP 5.x/7.x codebases (custom CMS, TemplatePower, mysql_*, ereg, short tags, PHP4 constructors) to run on PHP 8.3/8.4, then deploy without Docker via a .env config and an FTP/rsync deploy.sh. Use for modernizing legacy PHP CMS/sites and sibling legacy projects, or when a legacy site must be moved to shared hosting (cPanel/Plesk) over FTP.

Works with~Claude Code~Codex CLI~Cursor✓OpenCode
npx skills add https://github.com/dbravoan/opencode-skills/tree/HEAD/skills/legacy-php8-modernizer

Installed? Explore more Research & Data Analysis skills: affaan-m/uspto-database, affaan-m/scholar-evaluation, affaan-m/literature-review · View all 6 →

Ask in your favorite AI

Open a new chat with this agent skill pre-loaded.

Documentation

Legacy PHP → PHP 8.3/8.4 Modernizer

When to use me

Use this skill when a legacy PHP project (typically a custom CMS written for PHP 5.x) must:

  • run on PHP 8.3 / 8.4 without a full rewrite, and/or
  • be deployed to shared hosting (cPanel/Plesk) over FTP, without Docker.

Typical symptoms: mysql_*, ereg/eregi, <? short tags, PHP4-style constructors, bareword array keys ($row[Id]), $$var['key'], sizeof(null), MyISAM dump, TemplatePower templates, plaintext credentials in config/defines*.php.

What this skill ships

AssetPurpose
scripts/compat.phpShim: mysql_*→mysqli, ereg*/split→preg, userErrorHandler
scripts/fix_barewords.phpTokenizer-safe quoting of bareword array keys
scripts/fix_variable_variables.phpFixes PHP 8 $$var['key'] uniform-variable-syntax breakage
scripts/check_signatures.phpStatic LSP method-signature checker
scripts/lint-all.shphp -l sweep
scripts/smoke-test.shHTTP smoke test of a URL list
templates/defines.php.env loader + DB constants (works with and without Docker)
templates/env.example.env template (DB_* + FTP_*)
templates/htaccessModern Apache/PHP-FPM .htaccess
templates/user.iniPHP-FPM overrides for shared hosting
templates/timthumb.phpSafe GD thumbnail generator (replaces vulnerable TimThumb)
deploy/deploy.shFTP or rsync deploy; excludes dev files; writes remote .env

Golden rules

  1. Safety first: git init + baseline commit before touching anything. Back up the DB.
  2. Unblock before refactor: the shim gets the whole codebase running immediately.
  3. One central DB layer: swap/limit dbClass first; the shim covers raw mysql_* calls.
  4. Security early: remove dangerous tools and TimThumb as soon as the site renders.
  5. Verify continuously: php -l after every batch, then smoke-test every URL.

Procedure

0. Baseline

git init -q && git add -A && git commit -qm "baseline: legacy PHP"
find . -name '*.php' -not -path './node_modules/*' | wc -l

Save a list of real public URLs (from the DB/sitemap) for scripts/smoke-test.sh.

1. Install the compatibility shim

  • Copy scripts/compat.php to e.g. includes/compat.php.
  • Load it at the very top of the bootstrap, before any legacy code:
    include_once(__DIR__.'/../includes/compat.php');
    
  • It provides mysql_*, ereg/eregi/ereg_replace/eregi_replace/split, mysqli_report(MYSQLI_REPORT_OFF) (SQL errors return false instead of throwing), and userErrorHandler() (suppresses PHP 5's old E_NOTICE "undefined index/variable").
  • Register the handler once:
    if (function_exists('userErrorHandler')) set_error_handler('userErrorHandler');
    

2. Mechanical fixes (scripts)

php scripts/fix_barewords.php            # dry run
php scripts/fix_barewords.php --apply
php scripts/fix_variable_variables.php   # dry run
php scripts/fix_variable_variables.php --apply
scripts/lint-all.sh

Then fix what lint still reports:

  • Short tags: <? → <?php (grep <\?(?!php|=|xml)).
  • PHP4 constructors: function ClassName() → function __construct().
  • Curly-brace string offsets: $s{0} → $s[0].
  • Stray break outside loops/switches (fatal in PHP 8).
  • Variable-variables in ${...}: quote barewords / use ${$var['k']}.

3. Signatures, static & null-safety

php scripts/check_signatures.php

For each MISMATCH, add default params to the child method (e.g. $tabla = null). Then fix at runtime as fatals appear:

ErrorFix
ArgumentCountError: too few argumentsgive trailing params defaults
$this in static methoduse self::/static:: or pass the app context
method_exists(null, ...)is_object($x) && method_exists($x, ...)
Cannot access offset of type string on stringreorder is_array($x) before $x['k']
Undefined constant "X"quote bareword keys / constants
sizeof/count/array_keys(null)is_array($x) ? ... : ..., (array)$x
foreach(null)foreach ((array)$x as ...)
Accessing static property X as non static$this->prop → self::$prop

4. Run against the real DB and iterate

Point config at the DB, then request every URL. Fix one fatal per test until scripts/smoke-test.sh reports zero failures and the log has no warnings.

5. Security (do early)

  • Replace TimThumb with templates/timthumb.php.
  • Delete scanner.php, chmod777.php, info.php, stray debug scripts.
  • Make .htaccess deny .env, .git, config/defines.php, dotfiles.
  • Rotate committed DB passwords; move secrets to .env.
  • Recommend CSRF tokens, CSP/HSTS, rate limiting, prepared statements.

6. Config + deployment

  • Use templates/defines.php (.env loader) and templates/env.example.
  • Use templates/htaccess and templates/user.ini.
  • Deploy with deploy/deploy.sh.

.env system

config/defines.php loads .env from the project root, falling back to getenv() and finally safe defaults. This keeps Docker working (same DB_* keys) while making FTP deployment a one-file edit.

.env (never committed):

DB_HOST=localhost
DB_NAME=cpaneluser_termospa
DB_USER=cpaneluser_termospa
DB_PASS=super-secret
DB_SUBDIRECTORIO=

Deployment (no Docker)

Shared hosting (cPanel/Plesk) checklist

  1. PHP: select 8.3 (or 8.1+); enable mysqli, gd, mbstring, pdo_mysql.
  2. DB: create DB + user, import the dump. For MySQL 8:
    SET SESSION sql_mode='NO_ENGINE_SUBSTITUTION';
    
    Expect utf8_spanish_ci → utf8mb3_spanish_ci warnings (or run the utf8mb4 migration).
  3. Files: upload into public_html/ (exclude docker/, tools/, router.php, _DOCS/, .git/).
  4. Permissions: dirs 755, files 644; recursos/cache/, .cache/, upload/ writable (775/777).
  5. Config: create the remote .env (deploy.sh does this) and verify DB_*.
  6. Deny .env access in .htaccess.

deploy.sh

./deploy.sh ftp          # lftp reverse mirror
./deploy.sh ftp --dry-run
./deploy.sh rsync        # rsync over SSH

It reads .env (FTP_HOST/USER/PASS/PATH/TLS, or SSH_*), stages only the runtime files, writes a remote-only .env containing just DB_* (so FTP creds never land on the server), uploads, and prints the permission/DB steps.


Verification checklist

  • scripts/lint-all.sh → all clean
  • scripts/smoke-test.sh urls.txt → 0 failures
  • home / product / project / news / contact pages render
  • /sitemap.xml and /rss return valid XML
  • /imagenes/thumbnail/... returns an image (GD + rewrite)
  • /administracion/login/ loads; login + a content manager works
  • contact form submits
  • admin image upload writes to upload/
  • no PHP Warning/Fatal in the error log

Gotchas that always bite

  • mysqli_query throws on PHP 8.1+ by default; call mysqli_report(MYSQLI_REPORT_OFF) so legacy or die() / if($q=...) keeps working.
  • php_flag / php_value in .htaccess cause HTTP 500 under PHP-FPM — use .user.ini.
  • PHP 8's uniform variable syntax silently breaks $$var['key'] (writes the wrong variable) — run fix_variable_variables.php.
  • include() relative paths resolve against the script dir/CWD; prefer __DIR__.
  • Gone in PHP 8: mysql_*, ereg*, split, create_function, each, magic quotes.
  • OPcache caches old files: restart FPM/Apache after edits.
  • UTF-8: SET NAMES utf8mb4; prefer InnoDB + utf8mb4_unicode_ci for new work.

Sibling projects

These CMS projects share the same architecture. Run the same procedure, adjusting only the bootstrap path and the thumbnail rewrite target. Copy scripts/ into the target's tools/, run from the project root.

Related Skills