بيئة تطوير متطابقة للفريق بأكمله
وداعاً لعبارة "إنه يعمل على جهازي". يضمن Docker أن يعمل كل مطوّر في بيئة متطابقة، مما يقضي على مشاكل التوافق.
من دون حاويات، ينتهي الأمر بكل جهاز عمل إلى امتلاك إصداره الخاص من PHP، وإضافاته الخاصة، وإصدار من MySQL مختلف عن إصدار الإنتاج، وإعدادات php.ini موروثة من مشاريع قديمة. والأخطاء الناتجة عن ذلك هي الأكثر كلفة في التشخيص، لأنها لا تتكرر في أي مكان آخر. مع Docker، تُوصَف البيئة في ملفات خاضعة لإدارة الإصدارات إلى جانب الشيفرة: يصبح تحديث PHP تغييراً يُراجَع في code review، ويحصل عليه كل عضو في الفريق بمجرد git pull.
يبني هذا المقال بيئة تطوير PHP كاملة: PHP مع Xdebug، وMySQL، وخادم بريد للاختبار، ثم يتناول تصحيح الأخطاء، وأداء وحدات التخزين على macOS وWindows، والأخطاء الكلاسيكية.
بيئة تطوير كاملة
services:
php:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/var/www/html
- composer-cache:/root/.composer
environment:
- APP_ENV=dev
- XDEBUG_MODE=debug
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: app
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
mailhog:
image: mailhog/mailhog
ports:
- "8025:8025"
volumes:
composer-cache:
mysql-data:
لنفصّل الخيارات في هذا الملف:
- الشيفرة مركّبة (
.:/var/www/html): كل تعديل في الـ IDE يظهر فوراً داخل الحاوية، دون إعادة بناء الصورة. - ذاكرة Composer المؤقتة (cache) تعيش في وحدة تخزين مسمّاة: تُحفظ الحزم المحمّلة حتى لو أُعيد إنشاء الحاوية.
- MySQL ينشر المنفذ 3306 ليتمكن عميل رسومي على الجهاز من الاتصال به. وتصمد البيانات بعد إعادة التشغيل بفضل وحدة التخزين
mysql-data. استخدم الإصدار الرئيسي نفسه المستخدم في الإنتاج. - MailHog يعترض كل رسائل البريد الإلكتروني التي يرسلها التطبيق ويعرضها على
http://localhost:8025: لا خطر من مراسلة عملاء حقيقيين من جهاز تطوير. في مشروع Symfony، وجّهMAILER_DSNإلىsmtp://mailhog:1025. وبما أن MailHog لم يعد يُصان، فإن Mailpit (الصورةaxllent/mailpit، بالمنافذ نفسها) بديل نشط ومتوافق اليوم.
كلمات المرور غير المشفّرة مقبولة هنا لأن هذه البيئة لا تغادر جهاز المطوّر أبداً.
ملف Dockerfile الخاص بالتطوير
ينطلق الملف Dockerfile.dev من صورة PHP الرسمية ويضيف الإضافات الشائعة وComposer وXdebug. ويبقى عمداً منفصلاً عن Dockerfile الخاص بالإنتاج، الذي يجب ألا يحتوي أبداً على Xdebug:
FROM php:8.3-fpm
RUN apt-get update \
&& apt-get install -y --no-install-recommends git unzip libicu-dev libzip-dev \
&& docker-php-ext-install intl pdo_mysql zip opcache \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
&& rm -rf /var/lib/apt/lists/*
COPY --from=composer:2 /usr/bin/composer /usr/bin/composer
COPY docker/php/xdebug.ini /usr/local/etc/php/conf.d/zz-xdebug.ini
WORKDIR /var/www/html
يجلب السطر COPY --from=composer:2 الملف التنفيذي لـ Composer من الصورة الرسمية، دون أي سكربت تثبيت. ويبدأ اسم ملف إعدادات Xdebug بالبادئة zz- ليُحمَّل بعد الملفات التي يولّدها docker-php-ext-enable.
إعداد Xdebug
تصحيح الأخطاء أساسي في مرحلة التطوير. إليك الإعداد الأمثل لـ Xdebug:
[xdebug]
xdebug.mode=debug
xdebug.start_with_request=yes
xdebug.client_host=host.docker.internal
xdebug.client_port=9003
لكل سطر أهميته. يفعّل xdebug.mode=debug التصحيح خطوة بخطوة؛ ولمتغير البيئة XDEBUG_MODE في ملف Compose الأولوية على هذه القيمة، مما يسمح بالانتقال إلى XDEBUG_MODE=off دون إعادة بناء الصورة حين لا تحتاج إليه. ويبدأ start_with_request=yes جلسة مع كل طلب؛ وإذا أبطأ ذلك تطبيقك كثيراً، فاستخدم بدلاً منه trigger مع إضافة للمتصفح تضيف المُشغِّل عند الطلب. المنفذ 9003 هو الافتراضي منذ Xdebug 3 (المنفذ القديم 9000 كان يتعارض مع PHP-FPM).
يشير الاسم host.docker.internal إلى الجهاز المضيف، حيث يعمل الـ IDE. يوفّره Docker Desktop تلقائياً على macOS وWindows؛ أما على Linux فيجب التصريح به. ولـ PhpStorm، أضف أيضاً اسم خادم يُستخدم للعثور على مطابقة المسارات، بما في ذلك لأوامر CLI:
services:
php:
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- PHP_IDE_CONFIG=serverName=docker
في PhpStorm، أنشئ بعد ذلك خادماً باسم docker واربط جذر المشروع بـ /var/www/html. من دون هذه المطابقة، يتلقى الـ IDE الاتصال فعلاً لكنه لا يتوقف عند أي نقطة توقف.
إعادة التحميل الفوري والأداء
على macOS وWindows، قد تكون وحدات التخزين المركّبة بطيئة. استخدم استراتيجيات للمزامنة:
- Mutagen لمزامنة الملفات بسرعة
- استثناء
vendor/وnode_modules/من التركيب - استخدام وحدات تخزين مسمّاة للاعتماديات
يأتي البطء من كون Docker يعمل داخل آلة افتراضية على هذه الأنظمة: كل وصول إلى ملف مركّب يعبر الحد بين المضيف والآلة الافتراضية. تستخدم الإصدارات الحديثة من Docker Desktop تقنية VirtioFS، وهي أسرع بكثير من ذي قبل، لكن مشروع Symfony يقرأ آلاف الملفات في vendor/ مع كل طلب. وضع هذه المجلدات في وحدات تخزين مسمّاة يُبقيها داخل الآلة الافتراضية:
services:
php:
volumes:
- .:/var/www/html
- vendor:/var/www/html/vendor
- composer-cache:/root/.composer
volumes:
vendor:
composer-cache:
المقابل: لم يعد المجلد vendor/ مرئياً من المضيف، ويفقد الـ IDE الإكمال التلقائي للاعتماديات. لذلك تشغّل فرق كثيرة أيضاً composer install محلياً من أجل الـ IDE، أو تستخدم المفسّر البعيد في PhpStorm. ويمكن التعامل مع cache الخاص بـ Symfony (var/) بالطريقة نفسها. على Linux، التركيب أصلي وسريع: لا حاجة لهذه التحسينات.
أوامر الاستخدام اليومي
تُنفَّذ كل أوامر PHP داخل الحاوية، وليس أبداً باستخدام PHP المثبّت على المضيف:
docker compose up -d --build
docker compose exec php composer install
docker compose exec php bin/console doctrine:migrations:migrate --no-interaction
docker compose exec php bin/console cache:clear
# On Linux, so that created files belong to your user
docker compose exec -u "$(id -u):$(id -g)" php composer require symfony/uid
اجمع هذه الأوامر في Makefile أو في سكربت موثّق في ملف README: يجب ألا يضطر المطوّر الجديد إلى تذكّر أكثر من أمر أو أمرين.
أخطاء متكررة
- ملفات مملوكة لـ root على Linux: الأوامر المنفّذة بصلاحيات root داخل الحاوية تنشئ ملفات لم يعد بإمكان مستخدم المضيف تعديلها. نفّذها بمعرّف المستخدم (UID) الخاص بك، كما في المثال أعلاه.
- استخدام
localhostللوصول إلى قاعدة البيانات: داخل الحاوية، يشيرlocalhostإلى الحاوية نفسها. مضيف قاعدة البيانات هو اسم الخدمة، أيmysqlهنا. - ترك Xdebug مفعّلاً باستمرار: يبطئ بشكل ملحوظ كل طلب وكل أمر Composer. عطّله عندما لا تكون بصدد تصحيح الأخطاء.
- بيئة تطوير بعيدة جداً عن الإنتاج: الإصدار نفسه من PHP، والإضافات نفسها، ومحرّك قاعدة البيانات نفسه، وإلا تلاشت فائدة Docker.
يتيح هذا النهج لأي مطوّر جديد ينضم إلى الفريق الحصول على بيئة تطوير جاهزة للعمل في أقل من 5 دقائق.