Skip to content

Latest commit

 

History

History
596 lines (457 loc) · 14.7 KB

File metadata and controls

596 lines (457 loc) · 14.7 KB

Developer Documentation

This guide provides detailed technical information for developers who want to contribute to or understand the internal workings of pxp-cli.

Table of Contents

Architecture Overview

pxp-cli is a PowerShell-based CLI tool wrapped in a batch file for easy execution. It manages multiple PHP versions in XAMPP by manipulating directories and Apache configuration files.

Design Principles

  1. Safety First: Always backup before making changes
  2. User-Friendly: Clear error messages and helpful guidance
  3. Zero Dependencies: Works with vanilla Windows + XAMPP
  4. Non-Invasive: Doesn't modify core XAMPP files permanently

High-Level Flow

User Command
    ↓
pxp.bat (wrapper)
    ↓
pxp.ps1 (main script)
    ↓
┌─────────────────────────────────────┐
│  Parse command & arguments          │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  Validate XAMPP installation        │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  Execute command logic              │
│  - Switch, Install, List, etc.      │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  Update Apache configuration        │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  Restart Apache (if needed)         │
└─────────────────────────────────────┘
    ↓
Result Output

Core Components

1. pxp.bat

Purpose: Wrapper script to invoke PowerShell with correct execution policy.

@echo off
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0pxp.ps1" %*
  • -NoProfile: Prevents user PowerShell profile from interfering
  • -ExecutionPolicy Bypass: Allows script execution regardless of system policy
  • %~dp0: Expands to the directory containing the batch file
  • %*: Passes all arguments to PowerShell script

2. pxp.ps1

Purpose: Main PowerShell script containing all logic.

Structure:

# Parameters definition
param(...)

# Configuration variables
$PXP_VERSION = "1.3.1"
$XAMPP_ROOT  = "C:\xampp"
...

# Helper functions
function Get-PhpVersion { ... }
function Get-PhpMajor { ... }
...

# Command handlers
switch ($Command) {
    "list"      { ... }
    "switch"    { ... }
    "install"   { ... }
    "uninstall" { ... }
    ...
}

3. Installer (WiX)

Location: installer/Pxp.wixproj and installer/Pxp.wxs

Purpose: Creates MSI installer for easy distribution.

Key Features:

  • Installs scripts to Program Files (x86)\Pxp
  • Adds installation directory to system PATH
  • Creates uninstaller entry in Windows
  • Supports upgrade/downgrade scenarios

How It Works

PHP Version Management

XAMPP stores PHP in C:\xampp\php\. pxp manages multiple versions by:

  1. Renaming directories to store versions:

    • Active: C:\xampp\php\
    • Stored: C:\xampp\php7.4.33\, C:\xampp\php8.0.30\, etc.
  2. When switching:

    • Backup current php/php{version}/
    • Rename target php{version}/php/
    • Update Apache config
    • Restart Apache

Version Detection

From directory name:

# Extract version from directory name: "php8.0.30" → "8.0.30"
$version = $dir.Name -replace "^php", ""

From php.exe:

function Get-PhpVersion ([string]$PhpDir) {
    $exe = Join-Path $PhpDir "php.exe"
    if (Test-Path $exe) {
        return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe).FileVersion
    }
    return $null
}

PHP major version (for Apache module):

function Get-PhpMajor ([string]$PhpDir) {
    if (Test-Path (Join-Path $PhpDir "php7apache2_4.dll")) { return 7 }
    if (Test-Path (Join-Path $PhpDir "php8apache2_4.dll")) { return 8 }
    return $null
}

Key Functions

Get-PhpVersion

Purpose: Extract PHP version from php.exe file metadata.

function Get-PhpVersion ([string]$PhpDir) {
    $exe = Join-Path $PhpDir "php.exe"
    if (Test-Path $exe) {
        return [System.Diagnostics.FileVersionInfo]::GetVersionInfo($exe).FileVersion
    }
    return $null
}

Get-PhpMajor

Purpose: Determine PHP major version (7 or 8) by checking for Apache DLL.

function Get-PhpMajor ([string]$PhpDir) {
    if (Test-Path (Join-Path $PhpDir "php7apache2_4.dll")) { return 7 }
    if (Test-Path (Join-Path $PhpDir "php8apache2_4.dll")) { return 8 }
    return $null
}

Get-ApacheModuleName

Purpose: Get correct Apache module name based on PHP major version.

function Get-ApacheModuleName ([int]$Major) {
    switch ($Major) {
        7 { return "php7_module" }  # For PHP 7.x
        8 { return "php_module"  }  # For PHP 8.x
        default { return "php_module" }
    }
}

XAMPP Convention:

  • PHP 7.x → php7_module (loads php7apache2_4.dll)
  • PHP 8.x → php_module (loads php8apache2_4.dll)

Stop-ApacheService & Start-ApacheService

Purpose: Control Apache service before and after PHP switches.

function Stop-ApacheService {
    if (Test-Path $APACHE_BIN) {
        Write-Host "Stopping Apache..." -ForegroundColor Yellow
        & $APACHE_BIN -k stop 2>&1 | Out-Null
        Start-Sleep -Seconds 2
    }
}

function Start-ApacheService {
    if (Test-Path $APACHE_BIN) {
        Write-Host "Starting Apache..." -ForegroundColor Yellow
        & $APACHE_BIN -k start 2>&1 | Out-Null
        Start-Sleep -Seconds 2
        Write-Host "Apache started successfully!" -ForegroundColor Green
    }
}

Configuration Management

Apache Configuration File

Location: C:\xampp\apache\conf\extra\httpd-xampp.conf

Key sections modified:

# LoadModule directive - different for PHP 7 vs 8
LoadModule php7_module "C:/xampp/php/php7apache2_4.dll"  # PHP 7.x
# or
LoadModule php_module "C:/xampp/php/php8apache2_4.dll"   # PHP 8.x

Backup Strategy

First-time backup:

$backupFile = "$APACHE_CONF.bak"
if (-not (Test-Path $backupFile)) {
    Copy-Item $APACHE_CONF $backupFile
    Write-Host "Created backup: $backupFile"
}

Before each switch:

  • Current php/ directory backed up to php{version}/
  • Configuration updates applied
  • If something fails, original can be restored

Apache Integration

Configuration Update Process

  1. Read current config:

    $content = Get-Content $APACHE_CONF -Raw
  2. Replace LoadModule directive:

    $pattern = 'LoadModule\s+(php7?_module)\s+"[^"]*"'
    $replacement = "LoadModule $moduleName `"C:/xampp/php/$dllName`""
    $content = $content -replace $pattern, $replacement
  3. Write updated config:

    Set-Content -Path $APACHE_CONF -Value $content -NoNewline
  4. Restart Apache:

    Stop-ApacheService
    Start-ApacheService

Apache Control Commands

# Stop Apache
& "C:\xampp\apache\bin\httpd.exe" -k stop

# Start Apache
& "C:\xampp\apache\bin\httpd.exe" -k start

# Restart Apache
& "C:\xampp\apache\bin\httpd.exe" -k restart

Error Handling

Common Error Scenarios

  1. XAMPP not installed:

    if (-not (Test-Path $XAMPP_ROOT)) {
        Write-Host "Error: XAMPP not found at $XAMPP_ROOT" -ForegroundColor Red
        exit 1
    }
  2. PHP version not found:

    if (-not (Test-Path $targetPhp)) {
        Write-Host "Error: PHP $version not found" -ForegroundColor Red
        Write-Host "Available versions:" -ForegroundColor Yellow
        Show-InstalledVersions
        exit 1
    }
  3. Permission denied:

    try {
        Rename-Item $phpDir $targetDir -ErrorAction Stop
    }
    catch {
        Write-Host "Error: Permission denied. Run as Administrator." -ForegroundColor Red
        exit 1
    }
  4. Apache not responding:

    $maxRetries = 3
    for ($i = 0; $i -lt $maxRetries; $i++) {
        & $APACHE_BIN -k start
        Start-Sleep -Seconds 2
        if (Test-ApacheRunning) {
            break
        }
    }

Building & Distribution

Building the MSI Installer

Prerequisites:

# Install .NET SDK 6.0+
# Install WiX toolset as .NET tool
dotnet tool restore

Build command:

dotnet build ./installer/Pxp.wixproj -c Release

With custom version:

dotnet build ./installer/Pxp.wixproj -c Release -p:ProductVersion=1.3.0

Output:

  • Location: installer\bin\Release\
  • File: pxp-v{version}.msi

Release Process

  1. Update version in pxp.ps1:

    $PXP_VERSION = "1.3.1"
  2. Update CHANGELOG.md with release notes

  3. Build installer:

    dotnet build ./installer/Pxp.wixproj -c Release -p:ProductVersion=1.3.1
  4. Create Git tag:

    git tag v1.3.1
    git push origin v1.3.1
  5. GitHub Actions automatically creates release and uploads MSI

Testing Guide

Unit Testing

Currently, the project uses manual testing. Future improvements could include:

# Example test structure (Pester framework)
Describe "Get-PhpVersion" {
    It "Returns version from valid PHP directory" {
        $version = Get-PhpVersion "C:\xampp\php"
        $version | Should -Match "\d+\.\d+\.\d+"
    }
    
    It "Returns null for invalid directory" {
        $version = Get-PhpVersion "C:\invalid"
        $version | Should -BeNullOrEmpty
    }
}

Integration Testing

Test scenarios:

  1. Switch between versions:

    pxp switch 7.4.33
    # Verify: php -v shows 7.4.33
    # Verify: Apache is running
    
    pxp switch 8.0.30
    # Verify: php -v shows 8.0.30
    # Verify: Apache is running
  2. Install new version:

    pxp install 8.3.15
    # Verify: php8.3.15 directory exists
    # Verify: Files downloaded correctly
    
    pxp switch 8.3.15
    # Verify: Switch successful
  3. List versions:

    pxp list
    # Verify: All installed versions shown
    # Verify: Current version marked with *

Test Checklist

  • Commands work in PowerShell 5.1
  • Commands work in PowerShell 7+
  • Commands work via batch file
  • Works on Windows 10
  • Works on Windows 11
  • Admin rights handled correctly
  • Error messages are clear
  • Apache restarts successfully
  • No data loss during operations
  • Installer works correctly

Debugging Tips

Enable Verbose Output

Add debug output to functions:

function Debug-Log ([string]$Message) {
    if ($DebugPreference -eq "Continue") {
        Write-Debug $Message
    }
}

# Usage
Debug-Log "Switching from PHP $currentVersion to $targetVersion"

Common Debug Commands

# Check PHP version
& "C:\xampp\php\php.exe" -v

# Check Apache configuration
& "C:\xampp\apache\bin\httpd.exe" -t

# View Apache error log
Get-Content "C:\xampp\apache\logs\error.log" -Tail 50

# Check Apache status
Get-Process httpd -ErrorAction SilentlyContinue

# Test Apache config syntax
& "C:\xampp\apache\bin\httpd.exe" -t

PowerShell Debugging

# Enable script debugging
Set-PSDebug -Trace 1

# Disable script debugging
Set-PSDebug -Off

# Set breakpoint
Set-PSBreakpoint -Script .\pxp.ps1 -Line 100

# Step through code
$DebugPreference = "Continue"

Log File Analysis

# Apache error log
Get-Content "C:\xampp\apache\logs\error.log" | Select-String "PHP"

# Apache access log
Get-Content "C:\xampp\apache\logs\access.log" | Select-String "error" -Context 2

Performance Considerations

Optimization Tips

  1. Minimize file operations: Batch file operations when possible
  2. Cache version information: Avoid repeated file system calls
  3. Parallel downloads: Use background jobs for downloads (future enhancement)
  4. Lazy loading: Only load necessary modules

Bottlenecks

  • Apache restart: Takes 2-5 seconds
  • Directory rename: Usually fast, but can be slow on slow disks
  • Download speed: Depends on network and PHP.net servers

Future Enhancements

Planned Features

  1. PHP Extension Management:

    • Enable/disable extensions via CLI
    • List available extensions
  2. Configuration Profiles:

    • Save/load php.ini configurations
    • Quick switch between development/production
  3. Multi-XAMPP Support:

    • Support multiple XAMPP installations
    • Custom installation paths
  4. Backup Management:

    • List all backups
    • Restore from backup
    • Clean old backups
  5. Web Interface:

    • Simple local web UI for management
    • Visual representation of versions

Contributing

See CONTRIBUTING.md for detailed contribution guidelines.

Quick Start for Developers

  1. Fork the repository
  2. Clone your fork
  3. Make changes in a feature branch
  4. Test thoroughly
  5. Submit a pull request

Resources

Documentation

Tools

Support

For questions or issues:


Happy coding! 🚀