A centralized quality assurance system for PHP projects that:
- Downloads PHPQA tasks automatically from GitHub
- Uses reusable Castor tasks across all projects
- Provides reusable GitHub Actions workflows
- Requires minimal configuration per project
- Works anywhere, no path dependencies
.castor/phpqa.php- All reusable QA tasks (PHPUnit, PHPStan, ECS, Rector, etc.).github/workflows/reusable-ci.yml- Reusable GitHub Actions workflowscripts/migrate-project.sh- Automatic project migration scriptexamples/- Ready-to-use templates for different project types
GETTING-STARTED.md- Quick start guideINTEGRATION.md- Detailed integration guideARCHITECTURE.md- Technical architecture documentationCHANGELOG.md- Change historySUMMARY.md- This file
Instead of using relative paths, projects now download PHPQA tasks from GitHub:
// castor.php (~40 lines total)
<?php
use function Castor\import;
use function Castor\io;
$phpqaFile = __DIR__ . '/.castor-cache/phpqa.php';
$phpqaUrl = 'https://raw.githubusercontent.com/Spomky-Labs/phpqa/main/.castor/phpqa.php';
$cacheDir = __DIR__ . '/.castor-cache';
if (!is_dir($cacheDir) && !mkdir($cacheDir, 0755, true) && !is_dir($cacheDir)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $cacheDir));
}
if (!file_exists($phpqaFile)) {
io()->note('Downloading PHPQA tasks...');
file_put_contents($phpqaFile, file_get_contents($phpqaUrl));
}
import($phpqaFile);Benefits:
- ✅ No path dependencies
- ✅ Works anywhere
- ✅ Local cache (
.castor-cache/) - ✅ Works offline after first download
- ✅ Easy updates (delete cache to refresh)
your-project/
├── .castor-cache/ # Cache (gitignored)
│ ├── phpqa.php # Downloaded from GitHub
│ └── phpqa.version # Cached version
├── .phpqa-config.php # Configuration (optional)
├── castor.php # ~40 lines with GitHub download
├── .gitignore # Contains: .castor-cache/
└── .github/
└── workflows/
└── ci.yml # Reusable workflow
cd ~/Projects/phpqa
# For a library/bundle
./scripts/migrate-project.sh ~/Projects/your-project library
# For an application
./scripts/migrate-project.sh ~/Projects/your-app applicationThis creates:
- ✅
castor.phpwith GitHub auto-download - ✅
.phpqa-config.phpconfigured for project type - ✅
.github/workflows/ci.ymlready to use - ✅
.gitignorewith.castor-cache/
# Copy an example
cp examples/castor-simple.php your-project/castor.php
# Optional: add configuration
cp examples/.phpqa-config-library.php your-project/.phpqa-config.php
# Run
cd your-project
castor qa:allAll commands in qa: namespace:
castor qa:phpunit # Tests with coverage
castor qa:phpstan # Static analysis
castor qa:phpstan-baseline # Generate PHPStan baseline
castor qa:ecs # Check coding standards
castor qa:ecs-fix # Fix coding standards
castor qa:rector # Check refactoring
castor qa:rector-fix # Apply refactoring
castor qa:deptrac # Validate architecture
castor qa:lint # Check PHP syntax
castor qa:infect # Mutation testing
castor qa:validate # Validate composer.json
castor qa:check-licenses # Check license compatibility
castor qa:js # JavaScript tests
castor qa:prepare-pr # Prepare code for PR
castor qa:all # Run all checks
castor qa:install # Install dependencies
castor qa:update-image # Update PHPQA Docker imageFor applications:
castor app:console [command] # Run Symfony console commandswebauthn-framework/
├── castor.php # 409 lines
└── .github/workflows/ci.yml # 245 lines
jwt-framework/
├── castor.php # 322 lines
└── .github/workflows/ci.yml # 240 lines
cbor-bundle/
├── castor.php # 252 lines
└── .github/workflows/ci.yml # 240 lines
Total: ~1700 lines duplicated
phpqa/
├── .castor/phpqa.php # 370 lines
└── .github/workflows/reusable-ci.yml # 290 lines
each-project/
├── .phpqa-config.php # 10 lines (optional)
├── castor.php # 40 lines (auto-download)
└── .github/workflows/ci.yml # 15 lines (parameters)
Per project: ~50 lines (vs ~650 before)
Reduction: 92% less code per project
<?php
return [
'type' => 'library', // library, bundle, application
'php_version' => '8.4',
'source_dirs' => ['src', 'tests'],
'check_licenses' => true,
'infection_enabled' => true,
'deptrac_enabled' => true,
'js_enabled' => false,
'docker_enabled' => true,
'console_path' => 'bin/console',
];jobs:
ci:
uses: Spomky-Labs/phpqa/.github/workflows/reusable-ci.yml@main
with:
project_type: 'library'
php_versions: '["8.2", "8.3", "8.4"]'
experimental_php_versions: '["8.5"]'
enable_infection: true
enable_deptrac: true
enable_license_check: true
enable_js_tests: falseUse different versions/branches:
// Latest version (default)
$targetVersion = 'main';
// Specific tagged version
$targetVersion = 'v1.0.0';
// Development branch
$targetVersion = 'develop';Change $targetVersion in castor.php and delete .castor-cache/ to switch versions.
Ready-to-use templates in examples/:
castor-simple.php- Minimal versioncastor-library.php- For libraries/bundlescastor-application.php- For applications with Docker tasks.phpqa-config-library.php- Library configuration.phpqa-config-application.php- Application configurationci-library.yml- GitHub Actions for librariesci-application.yml- GitHub Actions for applications
The system is robust:
if ($content !== false) {
// Download successful
file_put_contents($phpqaFile, $content);
} elseif (!file_exists($phpqaFile)) {
// No cache and no internet → error
io()->error('Failed to download');
exit(1);
} else {
// Use existing cache
io()->warning('Using cached version');
}Result:
- ✅ With internet → downloads latest version
- ✅ Without internet → uses cache
- ❌ Fails only if: no internet AND no cache
- Single source of truth for QA tasks
- Updates benefit all projects immediately
- Guaranteed consistency
- Minimal configuration per project
- Optional configuration with smart defaults
- No code duplication
- Support for different project types
- Customizable per project
- Project-specific tasks still possible
- Works anywhere, no path dependencies
- No need to clone phpqa repository
- Works on any machine
- Local cache after first download
- Parallel GitHub Actions jobs
- Docker image reuse
GETTING-STARTED.md- Quick start and common workflowsINTEGRATION.md- Detailed integration guideARCHITECTURE.md- Technical architecture and designCHANGELOG.md- Version historyexamples/README.md- Examples documentation
For each project:
- Run migration script
- Verify created files
- Test
castor list - Test
castor qa:lint - Test
castor qa:phpstan - Test
castor qa:all - Copy custom tasks if needed
- Customize
.phpqa-config.phpif needed - Adjust GitHub Actions workflow if needed
- Commit and push
- Verify CI passes on GitHub
- Delete
castor.php.backuponce validated
- Test with a pilot project (e.g., a simple bundle)
- Validate everything works
- Migrate other projects
- Enjoy the centralized system!
You now have:
✅ A centralized QA system for all your PHP projects ✅ Simplified maintenance (single place to update) ✅ Guaranteed consistency across projects ✅ Automatic download from GitHub ✅ Local caching for performance ✅ Works anywhere, no dependencies ✅ Comprehensive documentation
Time saved: 80-90% reduction in QA configuration maintenance
Issue 1: Cache Extraction Permissions
- Problem: tar failed with "Cannot utime: Operation not permitted" when GitHub Actions tried to restore caches
- Root Cause:
/toolsdirectory owned by root, but container runs as user 1001 - Fix: Added proper ownership in Dockerfile:116-117
Issue 2: Symfony Panther/BrowserKit Incompatibility
- Problem: Fatal error with Panther and BrowserKit 8.0 causing persistent build failures
- Root Cause: Panther had ongoing compatibility issues with newer Symfony components
- Fix: Removed Panther entirely from the Docker image (Dockerfile:82)
- Removed symfony/panther dependency
- Removed Panther environment configuration
- Removed GeckoDriver (only needed for Panther)
- Note: Projects can still install Panther locally as needed
New automated tests prevent regressions:
tests/test-image.sh- Validates published images (10 comprehensive tests)tests/test-local-build.sh- Tests local builds before publishing- GitHub Actions CI - Automatic testing after each image build
tests/README.md- Complete testing documentation
Test Coverage:
- Essential tools verification (tar, git, composer, etc.)
- PHP version validation
- Directory permissions (1001:1001)
- Cache write permissions
- Tar functionality with timestamp operations
- PHP extensions availability
# Test published image
./tests/test-image.sh 8.4
# Test local build
./tests/test-local-build.sh 8.4
# Test all versions
for v in 8.2 8.3 8.4; do ./tests/test-image.sh $v; done