In the fast-paced world of software development, Continuous Integration (CI) has become an essential practice for PHP developers. CI is a development approach that involves frequently integrating code changes into a shared repository, followed by automated testing and deployment. This process helps catch bugs early, improve code quality, and streamline the development workflow.
Understanding Continuous Integration
Continuous Integration is more than just a set of tools; it’s a development philosophy. The core idea is to integrate code changes frequently – ideally, several times a day. Each integration triggers automated builds and tests, providing rapid feedback to developers.
🔑 Key benefits of CI:
- Early detection of bugs and integration issues
- Improved code quality
- Faster release cycles
- Increased visibility and communication within the team
Setting Up a CI Pipeline for PHP Projects
Let’s dive into the practical aspects of setting up a CI pipeline for your PHP project. We’ll use GitHub Actions as our CI tool, but the concepts apply to other CI platforms as well.
Step 1: Creating a Simple PHP Project
First, let’s create a simple PHP project with a basic function and a corresponding test.
// src/Calculator.php
<?php
class Calculator
{
public function add($a, $b)
{
return $a + $b;
}
}
Now, let’s create a test for this function:
// tests/CalculatorTest.php
<?php
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testAdd()
{
$calculator = new Calculator();
$this->assertEquals(4, $calculator->add(2, 2));
}
}
Step 2: Setting Up GitHub Actions
Create a new file in your repository at .github/workflows/ci.yml
:
name: PHP CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.0'
- name: Install Dependencies
run: composer install
- name: Run Tests
run: vendor/bin/phpunit
This workflow will run every time you push to the main branch or create a pull request. It sets up PHP, installs dependencies, and runs your PHPUnit tests.
Enhancing Your CI Pipeline
Now that we have a basic CI pipeline, let’s enhance it with more advanced features.
Code Style Checking
Maintaining consistent code style is crucial for project maintainability. Let’s add PHP CodeSniffer to our pipeline.
First, add PHP_CodeSniffer to your project:
composer require --dev squizlabs/php_codesniffer
Then, update your ci.yml
file:
- name: Check Coding Standards
run: vendor/bin/phpcs --standard=PSR12 src tests
This step will check your code against the PSR-12 coding standard.
Static Analysis
Static analysis tools can help catch potential bugs and improve code quality. Let’s add PHPStan to our pipeline.
First, install PHPStan:
composer require --dev phpstan/phpstan
Then, add this step to your ci.yml
:
- name: Run Static Analysis
run: vendor/bin/phpstan analyse src tests --level=5
This will run PHPStan at level 5 (out of 9) on your src and tests directories.
Automating Deployment
The final step in our CI pipeline is automating the deployment process. This typically involves deploying your application to a staging or production environment after all tests pass.
Here’s an example of how you might set up a deployment step using SSH:
- name: Deploy to Staging
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
env:
PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
HOST: ${{ secrets.HOST }}
USER: ${{ secrets.USER }}
run: |
echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
ssh -o StrictHostKeyChecking=no -i private_key ${USER}@${HOST} '
cd /path/to/your/project &&
git pull origin main &&
composer install --no-dev --optimize-autoloader &&
php artisan migrate --force
'
This step will only run on pushes to the main branch. It uses SSH to connect to your server, pull the latest changes, install production dependencies, and run database migrations.
🔒 Note: Make sure to set up the necessary secrets (SERVER_SSH_KEY, HOST, USER) in your GitHub repository settings.
Best Practices for PHP CI
-
Keep builds fast: Aim for CI builds that complete in under 10 minutes. This ensures quick feedback for developers.
-
Test thoroughly: Include unit tests, integration tests, and end-to-end tests in your CI pipeline.
-
Use parallel jobs: For larger projects, run different types of tests in parallel to speed up the overall build time.
-
Monitor and optimize: Regularly review your CI pipeline for bottlenecks and optimize where possible.
-
Secure your secrets: Never hardcode sensitive information in your CI configuration. Use environment variables and secrets management.
Real-World Example: Laravel CI Pipeline
Let’s look at a more comprehensive example for a Laravel project:
name: Laravel CI/CD
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
laravel-tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:5.7
env:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: laravel
ports:
- 3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
steps:
- uses: actions/checkout@v2
- name: Copy .env
run: php -r "file_exists('.env') || copy('.env.example', '.env');"
- name: Install Dependencies
run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist
- name: Generate key
run: php artisan key:generate
- name: Directory Permissions
run: chmod -R 777 storage bootstrap/cache
- name: Create Database
run: |
mkdir -p database
touch database/database.sqlite
- name: Execute tests (Unit and Feature tests) via PHPUnit
env:
DB_CONNECTION: sqlite
DB_DATABASE: database/database.sqlite
run: vendor/bin/phpunit
- name: Execute static analysis via PHPStan
run: vendor/bin/phpstan analyse
- name: Check coding style
run: vendor/bin/phpcs --standard=PSR12 app tests
- name: Deploy to Production
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
env:
PRIVATE_KEY: ${{ secrets.SERVER_SSH_KEY }}
HOST: ${{ secrets.HOST }}
USER: ${{ secrets.USER }}
run: |
echo "$PRIVATE_KEY" > private_key && chmod 600 private_key
ssh -o StrictHostKeyChecking=no -i private_key ${USER}@${HOST} '
cd /path/to/your/laravel/project &&
git pull origin main &&
composer install --no-dev --optimize-autoloader &&
php artisan migrate --force &&
php artisan config:cache &&
php artisan route:cache &&
php artisan view:cache
'
This pipeline sets up a MySQL service for testing, runs PHPUnit tests, performs static analysis with PHPStan, checks coding style with PHP_CodeSniffer, and deploys to production if all checks pass.
Conclusion
Implementing Continuous Integration in your PHP projects can significantly improve your development workflow. By automating testing and deployment, you can catch bugs earlier, maintain code quality, and deploy with confidence.
Remember, CI is not just about tools – it’s about fostering a culture of continuous improvement and collaboration in your development team. Start small, iterate, and gradually build up your CI pipeline to suit your project’s needs.
🚀 Happy coding, and may your builds always be green!