Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Deploy

on:
workflow_dispatch:
inputs:
environment:
description: "Target deployment environment"
required: true
type: choice
options:
- staging
- production
default: staging
skip_tests:
description: "Skip tests before deploying (not recommended)"
required: false
type: boolean
default: false

jobs:
# ──────────────────────────────────────────────
# Job 1 – Full Test Suite
# ──────────────────────────────────────────────
test:
name: "Test Suite (PHP 8.3)"
runs-on: ubuntu-latest
if: ${{ !inputs.skip_tests }}

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: pdo, pdo_sqlite, mbstring, xml
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-deploy-${{ hashFiles('composer.lock') }}
restore-keys: composer-deploy-

- name: Install Composer dependencies (with dev)
run: composer install --no-progress --prefer-dist --no-interaction

- name: Run syntax check
run: find src/ public/ -name "*.php" -print0 | xargs -0 -n1 php -l

- name: Run PHPUnit tests
run: vendor/bin/phpunit --colors=always

# ──────────────────────────────────────────────
# Job 2 – Production Build
# ──────────────────────────────────────────────
build:
name: "Build (production)"
runs-on: ubuntu-latest
needs: test
# Always run build: even when tests were skipped
if: always() && (needs.test.result == 'success' || needs.test.result == 'skipped')

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: pdo, mbstring, xml
coverage: none

- name: Install production Composer dependencies only
run: |
composer install \
--no-dev \
--optimize-autoloader \
--classmap-authoritative \
--no-progress \
--prefer-dist \
--no-interaction

- name: Create .env from example (CI placeholder)
run: cp .env.example .env

- name: Verify autoloader integrity
run: php -r "require 'vendor/autoload.php'; echo 'Autoloader OK' . PHP_EOL;"

- name: Package release artifact
run: |
mkdir -p dist
zip -r dist/release-${{ github.sha }}.zip \
public/ src/ vendor/ .env.example composer.json \
--exclude "*.git*"
echo "📦 Build artifact: dist/release-${{ github.sha }}.zip"
ls -lh dist/

- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
name: release-${{ github.sha }}
path: dist/release-${{ github.sha }}.zip
retention-days: 7

# ──────────────────────────────────────────────
# Job 3 – Deploy
# ──────────────────────────────────────────────
deploy:
name: "Deploy → ${{ inputs.environment }}"
runs-on: ubuntu-latest
needs: build
environment: ${{ inputs.environment }}

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Download release artifact
uses: actions/download-artifact@v4
with:
name: release-${{ github.sha }}
path: dist/

- name: Copy artifact to server via SCP
uses: appleboy/scp-action@v1
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
source: "dist/release-${{ github.sha }}.zip"
target: "/tmp/"

- name: Extract and deploy via SSH
uses: appleboy/ssh-action@v1
env:
DEPLOY_PATH: ${{ secrets.DEPLOY_PATH }}
SHA: ${{ github.sha }}
with:
host: ${{ secrets.SSH_HOST }}
username: ${{ secrets.SSH_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
port: ${{ secrets.SSH_PORT || '22' }}
envs: DEPLOY_PATH,SHA
script: |
# 1. Create releases and shared directories if they don't exist
mkdir -p $DEPLOY_PATH/releases
mkdir -p $DEPLOY_PATH/shared

# 2. Extract artifact into a new release folder
mkdir -p $DEPLOY_PATH/releases/$SHA
unzip -q /tmp/dist/release-$SHA.zip -d $DEPLOY_PATH/releases/$SHA

# 3. Ensure shared .env exists, then link it
touch $DEPLOY_PATH/shared/.env
ln -sf $DEPLOY_PATH/shared/.env $DEPLOY_PATH/releases/$SHA/.env

# 4. Swap the "current" symlink for zero-downtime deployment
ln -sfn $DEPLOY_PATH/releases/$SHA $DEPLOY_PATH/current

# 5. Clean up the temp zip file
rm -f /tmp/dist/release-$SHA.zip

# 6. Keep only the last 5 releases (cleanup)
ls -dt $DEPLOY_PATH/releases/* | tail -n +6 | xargs -d '\n' rm -rf -- || true

echo "✅ Successfully deployed $SHA to $DEPLOY_PATH/current"

- name: Post-deploy smoke test (stub)
run: |
echo "🔍 Running smoke test against ${{ inputs.environment }}..."
echo "👉 Add curl/http check against your deployed URL here:"
echo ' curl -sf https://your-domain.com/ | grep "Welcome to Basic PHP RESTful API"'
echo "✅ Smoke test stub passed."
149 changes: 149 additions & 0 deletions .github/workflows/pr-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
name: Pull Request Check

on:
pull_request:
branches:
- main

jobs:
# ──────────────────────────────────────────────
# Job 1 – PHP Syntax Lint
# ──────────────────────────────────────────────
syntax-lint:
name: Syntax Lint (php -l)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none

- name: Check PHP syntax on all source files
run: find src/ public/ -name "*.php" -print0 | xargs -0 -n1 php -l

# ──────────────────────────────────────────────
# Job 2 – Code Style Linter (PHP_CodeSniffer)
# ──────────────────────────────────────────────
lint:
name: Lint (phpcs / PSR-12)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
tools: cs2pr
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-8.3-${{ hashFiles('composer.lock') }}
restore-keys: composer-8.3-

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction

- name: Run PHP_CodeSniffer
run: vendor/bin/phpcs -q --report=checkstyle | cs2pr

# ──────────────────────────────────────────────
# Job 3 – Code Formatter Check (PHP CS Fixer)
# ──────────────────────────────────────────────
format:
name: Format Check (php-cs-fixer)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-8.3-${{ hashFiles('composer.lock') }}
restore-keys: composer-8.3-

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction

- name: Run PHP CS Fixer (dry run — fails on any formatting issue)
run: vendor/bin/php-cs-fixer fix --dry-run --diff --verbose

# ──────────────────────────────────────────────
# Job 4 – Static Type Analysis (PHPStan)
# ──────────────────────────────────────────────
analyse:
name: Static Analysis (phpstan)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-8.3-${{ hashFiles('composer.lock') }}
restore-keys: composer-8.3-

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction

- name: Run PHPStan
run: vendor/bin/phpstan analyse --no-progress

# ──────────────────────────────────────────────
# Job 5 – Unit Tests (PHPUnit)
# ──────────────────────────────────────────────
test:
name: Tests (phpunit)
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Set up PHP 8.3
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: pdo, pdo_sqlite, mbstring, xml
coverage: none

- name: Cache Composer dependencies
uses: actions/cache@v6
with:
path: vendor
key: composer-8.3-${{ hashFiles('composer.lock') }}
restore-keys: composer-8.3-

- name: Install Composer dependencies
run: composer install --no-progress --prefer-dist --no-interaction

- name: Run PHPUnit tests
run: vendor/bin/phpunit --colors=always
27 changes: 21 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
vendor/
.env
logs/

# testing tools
.phpunit.cache
vendor/
.env
logs/

# Testing tools
.phpunit.cache

# PHP_CodeSniffer
.phpcs-cache

# PHP CS Fixer
.php-cs-fixer.cache

# Build output
dist/

# OS / IDE
.DS_Store
Thumbs.db
.idea/
.vscode/
38 changes: 38 additions & 0 deletions .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

$finder = PhpCsFixer\Finder::create()
->in([
__DIR__ . '/src',
__DIR__ . '/public',
])
->name('*.php')
->ignoreDotFiles(true)
->ignoreVCS(true);

return (new PhpCsFixer\Config())
->setRiskyAllowed(true)
->setRules([
// Base PSR-12 ruleset
'@PSR12' => true,

// Enforce strict_types on every file
'declare_strict_types' => true,

// Modern array syntax
'array_syntax' => ['syntax' => 'short'],

// Import hygiene
'no_unused_imports' => true,
'ordered_imports' => ['sort_algorithm' => 'alpha'],
'single_import_per_statement' => true,

// Trailing commas in multi-line structures
'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']],

// String & concatenation
'no_trailing_whitespace' => true,
'concat_space' => ['spacing' => 'one'],
])
->setFinder($finder);
Loading
Loading