◀ العودة إلى المدونة
PHP / Symfony

تطوير Symfony باستخدام Docker

نشر في 22 Oct 2024· 7 min قراءة
#Symfony#Docker#Développement

Symfony وDocker: الثنائي المثالي

يشكّل Docker وSymfony مزيجاً قوياً للتطوير. في Keytchens، منصة food-tech لإدارة الطلبات في الوقت الحقيقي، أتاحت هذه الحزمة التقنية (stack) توحيد بيئات التطوير لدى الفريق بأكمله.

يصف هذا الدليل بيئة تطوير متكاملة: PHP-FPM وNginx وMySQL وRedis وRabbitMQ، تُدار عبر Docker Compose وملف Makefile. الهدف بسيط: يستنسخ المطوّر الجديد المستودع، ويشغّل أمراً واحداً، فيحصل على تطبيق يعمل ومطابق تماماً لما لدى زملائه.

لماذا نضع بيئة التطوير في حاويات

من دون حاويات، يراكم كل جهاز نسخته الخاصة من PHP وإضافاته وخادم MySQL وإعداداته. وتنتهي هذه الفوارق دائماً بالعبارة الشهيرة «يعمل على جهازي». يحلّ Docker هذه المشكلة بوصف البيئة على شكل شيفرة تُدار بنظام الإصدارات مع التطبيق نفسه:

  • إصدار PHP وقائمة الإضافات مثبّتة في ملف Dockerfile؛
  • إصدارات MySQL وRedis وRabbitMQ هي نفسها المستخدمة في الإنتاج، لا ما صادف تثبيته على الجهاز؛
  • تتعايش مشاريع ذات احتياجات متعارضة (PHP 7.4 وPHP 8.3 مثلاً) دون أي تعارض؛
  • يمرّ أي تعديل على البيئة عبر pull request ويُراجَع كأي شيفرة أخرى.

Docker Compose لـ Symfony

يُعرّف الملف compose.yaml (أو docker-compose.yml) مجموع الخدمات:

services:
  php:
    build: .docker/php
    volumes:
      - .:/var/www/html
    depends_on:
      - mysql
      - redis

  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - .:/var/www/html
      - .docker/nginx/default.conf:/etc/nginx/conf.d/default.conf

  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: symfony
    volumes:
      - mysql-data:/var/lib/mysql

  redis:
    image: redis:7-alpine

  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "15672:15672"

volumes:
  mysql-data:

بعض النقاط التي ينبغي فهمها في هذا الملف:

  • الربط المباشر (bind mount) .:/var/www/html يُركّب الشيفرة المصدرية من جهازك داخل الحاويتين php وnginx: يظهر كل تعديل فوراً دون إعادة بناء الصورة.
  • الوحدة المسمّاة (named volume) mysql-data تحفظ بيانات قاعدة البيانات بين تنفيذين للأمر docker compose down. وحده الأمر docker compose down -v يحذفها.
  • الشبكة تُنشأ تلقائياً: يمكن الوصول إلى كل خدمة باسمها (mysql، redis، rabbitmq). لذلك لا نستخدم أبداً localhost من داخل Symfony للوصول إلى قاعدة البيانات.
  • المنفذ 15672 يعرض واجهة إدارة RabbitMQ على http://localhost:15672 (بيانات الدخول الافتراضية guest / guest).

انتظار جاهزية قاعدة البيانات فعلياً

لا يضمن depends_on في صيغته المختصرة سوى ترتيب التشغيل، لا الجاهزية: فقد يكون MySQL ما زال قيد التهيئة حين يحاول PHP الاتصال به. يحلّ فحص healthcheck مع الشرط service_healthy هذه المشكلة:

services:
  php:
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_started

  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-proot"]
      interval: 5s
      timeout: 3s
      retries: 10

إعداد Nginx

يعتمد الملف .docker/nginx/default.conf الإعداد الذي توصي به وثائق Symfony. يقدّم Nginx الملفات الثابتة من المجلد public/ ويمرّر كل ما عداها إلى PHP-FPM، الذي يمكن الوصول إليه بالاسم php على المنفذ 9000:

server {
    listen 80;
    server_name localhost;
    root /var/www/html/public;

    location / {
        try_files $uri /index.php$is_args$args;
    }

    location ~ ^/index\.php(/|$) {
        fastcgi_pass php:9000;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        internal;
    }

    location ~ \.php$ {
        return 404;
    }
}

تُعيد الكتلة الأخيرة خطأ 404 لأي ملف PHP آخر: وحدها وحدة التحكم الأمامية index.php يجب أن تكون قابلة للتنفيذ.

ملف Dockerfile محسَّن لـ PHP

FROM php:8.3-fpm
RUN apt-get update && apt-get install -y \
    libicu-dev libzip-dev \
    && docker-php-ext-install intl pdo_mysql zip opcache \
    && pecl install redis xdebug \
    && docker-php-ext-enable redis xdebug

COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
WORKDIR /var/www/html

توفّر الصورة الرسمية php:8.3-fpm السكربتين docker-php-ext-install وdocker-php-ext-enable. يجب تثبيت مكتبات النظام (libicu-dev لـ intl، وlibzip-dev لـ zip) قبل ترجمة الإضافات. أما إضافات PECL مثل redis وxdebug فتُبنى بالأمر pecl install ثم تُفعَّل. وأخيراً، يجلب COPY --from=composer:2 الملف التنفيذي لـ Composer من صورته الرسمية، دون أي سكربت تثبيت.

إذا كنت تستخدم ناقل AMQP في Messenger مع RabbitMQ، فستحتاج أيضاً إلى الإضافة amqp: أضف librabbitmq-dev إلى الحزم وamqp إلى سطر pecl install.

يُبطئ Xdebug كل طلب بشكل ملحوظ. عطّله افتراضياً ولا تفعّله إلا عند الحاجة، عبر ملف .docker/php/xdebug.ini يُنسخ إلى الصورة:

xdebug.mode=off
xdebug.client_host=host.docker.internal
xdebug.start_with_request=trigger

متغير البيئة XDEBUG_MODE=debug، حين يُمرَّر إلى الحاوية، يتقدّم على xdebug.mode: فتفعّل التصحيح دون إعادة بناء الصورة. على Linux، لا يوجد host.docker.internal افتراضياً: أضف extra_hosts: ["host.docker.internal:host-gateway"] إلى الخدمة php.

ربط Symfony بالخدمات

في الملف .env.local (غير المُضاف إلى المستودع)، تشير سلاسل DSN إلى أسماء خدمات Compose:

DATABASE_URL="mysql://root:root@mysql:3306/symfony?serverVersion=8.0&charset=utf8mb4"
REDIS_URL="redis://redis:6379"
MESSENGER_TRANSPORT_DSN="amqp://guest:guest@rabbitmq:5672/%2f/messages"

لا تنسَ تحديد serverVersion: يستخدمه Doctrine لاختيار منصة SQL المناسبة دون استعلام الخادم عند الإقلاع.

ملف Makefile لتبسيط الأوامر

أوامر Docker طويلة ويسهل نسيانها. يمنح ملف Makefile في جذر المشروع الفريقَ بأكمله المفردات نفسها:

up:
	docker compose up -d

down:
	docker compose down

console:
	docker compose exec php php bin/console $(cmd)

test:
	docker compose exec php php bin/phpunit

migrate:
	docker compose exec php php bin/console doctrine:migrations:migrate -n

طريقة الاستخدام: make up، ثم make console cmd="cache:clear" أو make migrate. انتبه: يجب أن تُزاح أوامر Makefile بمحرف جدولة (tab) لا بمسافات. أضف أيضاً السطر .PHONY: up down console test migrate حتى لا يخلط make بين هذه الأهداف وملفات تحمل الاسم نفسه.

أخطاء شائعة

  • صلاحيات الملفات: يعمل PHP-FPM بالمستخدم www-data داخل الحاوية، بينما تعود ملفاتك إلى مستخدمك أنت. إذا لم يكن var/cache أو var/log قابلين للكتابة، فطابِق UID مستخدم الحاوية مع UID الخاص بك (وسيط بناء UID والأمر usermod) بدلاً من تنفيذ chmod 777.
  • البطء على macOS: الربط المباشر أبطأ هناك منه على Linux. فعّل VirtioFS في Docker Desktop وتجنّب تركيب مجلدات لا حاجة إليها.
  • Composer خارج الحاوية: شغّل composer install داخل الحاوية، وإلا حُلّت الاعتماديات وفق إصدار PHP على جهازك لا وفق إصدار الصورة.
  • كلمات مرور بنص صريح: root / root مقبولة محلياً فقط. لا تُعِد استخدام ملف Compose هذا كما هو في الإنتاج أبداً.
  • صورة الإنتاج: تتضمّن هذه الصورة Xdebug وتركّب الشيفرة كوحدة تخزين. للإنتاج، ابنِ صورة منفصلة (بناء متعدد المراحل multi-stage) تُنسخ فيها الشيفرة، مع composer install --no-dev وOPcache مضبوط دون التحقق من الطوابع الزمنية.

الفوائد اليومية

  • بيئة متطابقة لجميع المطوّرين
  • عزل تام للخدمات
  • سهولة إدماج المطوّرين الجدد
  • إصدارات الخدمات متوافقة مع الإنتاج
  • تعديلات البيئة تُراجَع وتُدار بالإصدارات مثل الشيفرة

قائمة التحقق للبدء

  1. كتابة compose.yaml وملف Dockerfile لـ PHP وإعداد Nginx.
  2. إضافة healthcheck على MySQL وجعل تشغيل PHP مشروطاً به.
  3. تعطيل Xdebug افتراضياً وتفعيله عند الطلب.
  4. تحديد سلاسل DSN في .env.local بأسماء الخدمات.
  5. توثيق الأوامر في ملف Makefile وفي ملف README.
  6. التحقق من أن git clone متبوعاً بـ make up يكفي لتشغيل المشروع على جهاز جديد.

بهذا الأساس، تصبح بيئة التطوير جزءاً أصيلاً من المشروع: قابلة لإعادة الإنتاج، وموثّقة، وسهلة التطوير.