GitHub Actions لمشاريع PHP الخاصة بك
يوفر GitHub Actions حلاً للتكامل والنشر المستمرين (CI/CD) مدمجاً مباشرة في مستودعك. إليك كيفية إعداد خط أنابيب (pipeline) كامل.
الفائدة الرئيسية هي عدم وجود بنية تحتية يجب صيانتها: مسارات العمل (workflows) ملفات YAML توضع في .github/workflows/، وتُدار بالإصدارات مع الشيفرة، وتُنفَّذ على أجهزة افتراضية يوفرها GitHub (تسمى runners). كل عملية push أو pull request تطلق عمليات التحقق، وتظهر نتيجتها مباشرة في واجهة المراجعة. بالنسبة لمشروع PHP/Symfony، تحصل ببضع عشرات من الأسطر على التحليل الساكن، والاختبارات على قاعدة بيانات حقيقية، والنشر التلقائي.
المفاهيم الأساسية
- Workflow: ملف YAML يصف متى يُنفَّذ (
on) وماذا يفعل (jobs). - Job: مجموعة من الخطوات تُنفَّذ على الـ runner نفسه. تعمل مهام الـ workflow بالتوازي افتراضياً، ما لم يفرض
needsترتيباً معيناً. - Step: أمر shell (
run) أو إجراء قابل لإعادة الاستخدام (uses)، منشور على الـ Marketplace أو في مستودعك الخاص. - Service: حاوية مساندة (MySQL، Redis…) تُشغَّل بجانب المهمة طوال مدة تنفيذها.
مسار عمل الاختبارات
يُنفَّذ هذا المسار الأول عند كل push على main وdevelop، وكذلك عند كل pull request يستهدف main. يشغّل MySQL 8، ويثبّت PHP 8.3 مع الامتدادات اللازمة، ثم يشغّل PHPStan وPHPUnit.
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
tests:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
extensions: mbstring, pdo_mysql, intl
coverage: xdebug
- name: Install dependencies
run: composer install --prefer-dist --no-progress
- name: Run PHPStan
run: vendor/bin/phpstan analyse src
- name: Run tests
run: php bin/phpunit --coverage-clover coverage.xml
env:
DATABASE_URL: mysql://root:root@127.0.0.1:3306/test
بعض التفاصيل المهمة:
shivammathur/setup-phpهو الإجراء المرجعي لـ PHP: يثبّت الإصدار المطلوب والامتدادات وComposer ومشغّل قياس تغطية الشيفرة.- يعرض ربط المنفذ
3306:3306خدمة MySQL على الـ runner، ومن هنا العنوان127.0.0.1فيDATABASE_URL. أما إذا كانت المهمة نفسها تعمل داخل حاوية (container:)، فيجب استخدام اسم الخدمة،mysql، كمضيف. - كلمة المرور
rootمقبولة هنا: فقاعدة البيانات مؤقتة ولا توجد إلا طوال مدة المهمة. أما الأسرار الحقيقية فيجب ألا تظهر أبداً في ملف YAML.
النشر التلقائي
تُضاف مهمة النشر تحت jobs:. تنتظر نجاح الاختبارات (needs) ولا تُنفَّذ إلا على الفرع main.
deploy:
needs: tests
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: |
cd /var/www/app
git pull origin main
composer install --no-dev
php bin/console cache:clear
php bin/console doctrine:migrations:migrate -n
هذا السكربت بسيط عن قصد. في بيئة الإنتاج، أنصح بإضافة set -e في السطر الأول لإيقاف النشر عند أول خطأ، وإضافة --optimize-autoloader إلى composer install. ولتجنب الثواني القليلة التي تكون فيها الشيفرة والاعتماديات غير متزامنة، تبقى استراتيجية مجلدات الإصدارات (مجلد لكل إصدار ورابط رمزي current) هي الأكثر أماناً.
مسار عمل جاهز للإنتاج
تحتفظ النسخة التالية بالخطوات نفسها مع تطبيق أفضل الممارسات: التدقيق (lint) والاختبارات في مهام منفصلة تعمل بالتوازي، ومصفوفة لإصدارات PHP، وتخزين مؤقت لـ Composer، وانتظار جاهزية MySQL، وصلاحيات في حدها الأدنى.
name: CI
on:
push:
branches: [main]
pull_request:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: none
- run: composer install --prefer-dist --no-progress
- run: vendor/bin/php-cs-fixer fix --dry-run --diff
- run: vendor/bin/phpstan analyse src --error-format=github
tests:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['8.2', '8.3', '8.4']
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, pdo_mysql, intl
coverage: pcov
- name: Get Composer cache directory
id: composer-cache
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
- name: Cache Composer dependencies
uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- run: composer install --prefer-dist --no-progress
- run: php bin/phpunit --coverage-clover coverage.xml
env:
DATABASE_URL: mysql://root:root@127.0.0.1:3306/test
ما الذي تغيّر مقارنة بالمسار الأول:
permissions: contents: readيقلّص صلاحياتGITHUB_TOKENإلى الحد الأدنى الضروري. والمهمة التي تحتاج إلى التعليق على pull request أو نشر صورة ستطلب صراحةً صلاحيات إضافية.concurrencyيلغي التنفيذ السابق عند وصول commit جديد إلى الفرع نفسه: فلا فائدة من اختبار شيفرة استُبدلت بالفعل.- المصفوفة تطلق مهمة لكل إصدار من PHP. ومع
fail-fast: false، لا يوقف الفشل على PHP 8.4 الإصدارات الأخرى، مما يمنحك صورة كاملة عن التوافق. - فحص صحة MySQL: من دون
options، قد تبدأ المهمة بينما لا يزال MySQL يهيّئ قاعدته، فتفشل الاختبارات الأولى بشكل عشوائي. أما الآن فينتظر GitHub إلى أن تُعلَن الحاوية سليمة. - التخزين المؤقت لـ Composer مفهرس ببصمة (hash) الملف
composer.lock: ما دامت الاعتماديات لم تتغير، تُستعاد الحزم بدلاً من تنزيلها. pcovيجمع بيانات تغطية الشيفرة أسرع بكثير من Xdebug، الذي لا يفيد إلا إذا احتجت إلى ميزاته الأخرى.--error-format=githubيحوّل أخطاء PHPStan إلى تعليقات توضيحية تظهر مباشرة على الأسطر المعنية في الـ pull request.
حماية النشر عبر بيئة
يوفر GitHub البيئات (environments): أسرار خاصة بكل هدف، وبحسب خطة GitHub التي تستخدمها، قواعد حماية مثل الموافقة اليدوية من مراجعين محددين. تكتفي مهمة النشر بالتصريح بالبيئة التي تستخدمها:
deploy:
needs: [lint, tests]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
concurrency:
group: production
cancel-in-progress: false
steps:
- name: Deploy to production
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_KEY }}
script: |
set -e
cd /var/www/app
git pull origin main
composer install --no-dev --optimize-autoloader
php bin/console cache:clear
php bin/console doctrine:migrations:migrate -n
هنا تضمن مجموعة التزامن production تنفيذ عملية نشر واحدة فقط في كل مرة، دون إلغاء أي عملية جارية أبداً: فمقاطعة نشرٍ في منتصف عمليات الترحيل (migrations) أسوأ بكثير من الانتظار.
أخطاء شائعة
- الأسرار والنسخ المتفرعة (forks): مسارات العمل التي يطلقها pull request قادم من fork لا يمكنها الوصول إلى الأسرار. لذا يجب أن تعمل الاختبارات من دونها.
- إجراءات الأطراف الثالثة: الإجراء المشار إليه بوسم (
@v1) قد يتغير دون علمك. بالنسبة للإجراءات الحساسة، ثبّتها على SHA كامل لـ commit ودع Dependabot يقترح التحديثات. - أسماء الـ status checks: مع المصفوفة، تحمل عمليات التحقق أسماء مثل
tests (8.3). وإعادة تسمية مهمة أو تعديل المصفوفة يتطلب تحديث قواعد حماية الفروع. - النشر من pull request: يمنع الشرط
github.event_name == 'push'أي حدث غير متوقع علىmainمن إطلاق نشر في الإنتاج.
أفضل الممارسات
- استخدام التخزين المؤقت لاعتماديات Composer
- تشغيل مهام الاختبار والتدقيق بالتوازي
- حماية الفروع بـ status checks إلزامية
- تخزين الأسرار في GitHub Secrets
- تقييد
GITHUB_TOKENعبرpermissions - استخدام بيئة محمية للإنتاج
بهذه القواعد القليلة، يُتحقق من كل pull request تلقائياً، وتظهر الأخطاء مباشرة في مراجعة الشيفرة، ويصبح النشر في الإنتاج عملية روتينية بدلاً من حدث مخيف.