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 مضبوط دون التحقق من الطوابع الزمنية.
الفوائد اليومية
- بيئة متطابقة لجميع المطوّرين
- عزل تام للخدمات
- سهولة إدماج المطوّرين الجدد
- إصدارات الخدمات متوافقة مع الإنتاج
- تعديلات البيئة تُراجَع وتُدار بالإصدارات مثل الشيفرة
قائمة التحقق للبدء
- كتابة
compose.yamlوملف Dockerfile لـ PHP وإعداد Nginx. - إضافة
healthcheckعلى MySQL وجعل تشغيل PHP مشروطاً به. - تعطيل Xdebug افتراضياً وتفعيله عند الطلب.
- تحديد سلاسل DSN في
.env.localبأسماء الخدمات. - توثيق الأوامر في ملف Makefile وفي ملف README.
- التحقق من أن
git cloneمتبوعاً بـmake upيكفي لتشغيل المشروع على جهاز جديد.
بهذا الأساس، تصبح بيئة التطوير جزءاً أصيلاً من المشروع: قابلة لإعادة الإنتاج، وموثّقة، وسهلة التطوير.