{"id":2020,"date":"2026-02-18T14:29:30","date_gmt":"2026-02-18T08:59:30","guid":{"rendered":"https:\/\/sharanamshah.com\/mymusings\/?p=2020"},"modified":"2026-02-18T14:29:47","modified_gmt":"2026-02-18T08:59:47","slug":"how-to-automate-php-code-audits-with-python","status":"publish","type":"post","link":"https:\/\/sharanamshah.com\/mymusings\/blog\/2026\/02\/18\/how-to-automate-php-code-audits-with-python\/","title":{"rendered":"How to Automate PHP Code Audits with Python"},"content":{"rendered":"<h1>How to Automate Legacy PHP Code Audits with Python<\/h1>\n<h2>The real-world scenario<\/h2>\n<p>Imagine you are a DevOps engineer or a Senior Developer tasked with migrating a legacy PHP 5.6 application to PHP 8.2. Manually opening hundreds of files to find deprecated functions like <b>mysql_connect<\/b> or insecure patterns is a recipe for burnout. It is like trying to find every single expired item in a massive warehouse using nothing but a flashlight. This Python script acts as an automated health inspector, scanning your entire codebase in seconds and generating a detailed report of exactly what needs fixing.<\/p>\n<h2>The solution<\/h2>\n<p>We use a Python script that leverages the <b>pathlib<\/b> library for modern file system navigation and the <b>re<\/b> module for high-speed pattern matching. The script recursively crawls through a directory, identifies <b>.php<\/b> files, and flags specific deprecated functions or syntax errors, outputting the results into a clean <b>audit_report.csv<\/b> file.<\/p>\n<h2>Prerequisites<\/h2>\n<ul>\n<li>Install Python 3.8 or higher.<\/li>\n<li>No external libraries are required; we use built-in modules for maximum compatibility.<\/li>\n<\/ul>\n<h2>The code<\/h2>\n<pre><code>\n\"\"\"\n-----------------------------------------------------------------------\nAuthors: Sharanam &amp; Vaishali Shah\nRecipe: PHP Legacy Auditor\nIntent: Scan PHP projects for deprecated functions and security risks.\n-----------------------------------------------------------------------\n\"\"\"\nimport re\nimport csv\nfrom pathlib import Path\n\ndef audit_php_project(target_dir, report_name):\n    # Define patterns to look for (e.g., deprecated mysql functions or eval)\n    patterns = {\n        'mysql_extension': r'mysql_w+',\n        'eval_usage': r'evals*(',\n        'short_tags': r'&lt;?(s|=)',\n        'var_dump_leftover': r'var_dumps*('\n    }\n\n    results = []\n    base_path = Path(target_dir)\n\n    if not base_path.exists():\n        print(f'Error: The path {target_dir} does not exist.')\n        return\n\n    # Recursively find all PHP files\n    for php_file in base_path.rglob('*.php'):\n        try:\n            content = php_file.read_text(encoding='utf-8', errors='ignore')\n            lines = content.splitlines()\n\n            for line_num, line in enumerate(lines, 1):\n                for issue_type, pattern in patterns.items():\n                    if re.search(pattern, line):\n                        results.append({\n                            'file': str(php_file.relative_to(base_path)),\n                            'line': line_num,\n                            'issue': issue_type,\n                            'snippet': line.strip()[:50]\n                        })\n        except Exception as e:\n            print(f'Could not read {php_file}: {e}')\n\n    # Write findings to CSV\n    output_file = Path(report_name)\n    headers = ['file', 'line', 'issue', 'snippet']\n    \n    with output_file.open('w', newline='', encoding='utf-8') as f:\n        writer = csv.DictWriter(f, fieldnames=headers)\n        writer.writeheader()\n        writer.writerows(results)\n\n    print(f'Audit complete. Found {len(results)} issues. Report saved to: {report_name}')\n\nif __name__ == '__main__':\n    # Set the directory of your PHP project here\n    project_path = '.\/my_php_app' \n    report_filename = 'audit_report.csv'\n    audit_php_project(project_path, report_filename)\n<\/code><\/pre>\n<h2>Code walkthrough<\/h2>\n<p>The script begins by importing <b>pathlib<\/b> for cross-platform path handling and <b>re<\/b> for regular expression logic. We define a dictionary named <b>patterns<\/b> where the keys are the names of the issues and the values are the regex strings used to catch them. For example, <b>mysql_w+<\/b> identifies any function starting with <b>mysql_<\/b>, which was removed in PHP 7.0.<\/p>\n<p>The <b>rglob<\/b> method is the engine of the script; it performs a recursive search for every file ending in <b>.php<\/b> within the <b>target_dir<\/b>. As each file is opened, the script iterates through its lines and checks them against our pattern list. If a match is found, the file name, line number, and a snippet of the code are stored in a list of dictionaries.<\/p>\n<p>Finally, the <b>csv.DictWriter<\/b> takes that list and transforms it into a structured <b>audit_report.csv<\/b>. This allows you to open the results in Excel or Google Sheets to prioritize your refactoring efforts.<\/p>\n<h2>Sample output<\/h2>\n<p>When you execute the script in your terminal, you will see the following progress message:<\/p>\n<pre><code>\n$ python php_auditor.py\nAudit complete. Found 12 issues. Report saved to: audit_report.csv\n<\/code><\/pre>\n<p>Opening the <b>audit_report.csv<\/b> will display data like this:<\/p>\n<pre><code>\nfile,line,issue,snippet\nindex.php,12,short_tags,&lt;? echo \"Hello World\";\ndb.php,45,mysql_extension,$conn = mysql_connect($host, $user);\nutils.php,102,var_dump_leftover,var_dump($user_data);\n<\/code><\/pre>\n<h2>Conclusion<\/h2>\n<p>Automating the discovery phase of a code migration saves hours of manual labor and ensures no deprecated function slips through to production. By using Python to audit PHP, you leverage the best of both worlds: Python&#8217;s superior automation capabilities and PHP&#8217;s web-focused logic. You can easily extend the <b>patterns<\/b> dictionary to search for specific API keys, hardcoded passwords, or custom internal functions that need to be retired.<\/p>\n<hr style=\"margin: 50px 0;border: 0;border-top: 1px solid #eaeaea\">\n<div style=\"background-color: #fcfcfc;padding: 35px;border-left: 5px solid #d63638;border-radius: 6px\">\n<h3 style=\"margin-top: 0;color: #23282d;font-size: 24px;font-weight: 700\">\ud83d\ude80 Don&#8217;t Just Learn PHP \u2014 Master It.<\/h3>\n<p style=\"font-size: 17px;line-height: 1.6;color: #444;margin-bottom: 20px\">\n            This tutorial was just the tip of the iceberg. To truly advance your career and build <strong>professional-grade systems<\/strong>, you need the full architectural blueprint.\n        <\/p>\n<p style=\"font-size: 17px;line-height: 1.6;color: #444;margin-bottom: 25px\">\n            My book, <strong><a href=\"https:\/\/sharanamshah.com\/mymusings\/home\/books-ive-authored\/unlocking-ai-with-php\/\" target=\"_blank\" style=\"color: #d63638;text-decoration: underline\">Unlocking AI with PHP<\/a><\/strong>, takes you from &#8220;making it work&#8221; to &#8220;making it scale.&#8221; I cover advanced patterns, real-world case studies, and the industry best practices that senior engineers use daily.\n        <\/p>\n<p style=\"margin-bottom: 0\">\n            <a href=\"https:\/\/sharanamshah.com\/mymusings\/home\/books-ive-authored\/unlocking-ai-with-php\/\" target=\"_blank\" style=\"background-color: #d63638;color: white;padding: 15px 30px;text-decoration: none;border-radius: 5px;font-weight: bold;font-size: 18px;display: inline-block\"><br \/>\n                \ud83d\udcd6 Grab Your Copy Now &rarr;<br \/>\n            <\/a>\n        <\/p>\n<\/p><\/div>\n","protected":false},"excerpt":{"rendered":"<p>Learn how to use Python to automatically scan legacy PHP projects for deprecated functions and security risks using pathlib and regular expressions.<\/p>\n","protected":false},"author":1,"featured_media":1989,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_themeisle_gutenberg_block_has_review":false,"footnotes":""},"categories":[94],"tags":[],"class_list":["post-2020","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-everyday-automation-hacks"],"_links":{"self":[{"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/posts\/2020","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/comments?post=2020"}],"version-history":[{"count":0,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/posts\/2020\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/media\/1989"}],"wp:attachment":[{"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/media?parent=2020"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/categories?post=2020"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/sharanamshah.com\/mymusings\/wp-json\/wp\/v2\/tags?post=2020"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}