مقدمة إلى إدارة الاعتماديات في PHP باستخدام Composer
مقدمة
Composer أداة لإدارة الاعتماديات (Dependencies) يمكن من خلالها تثبيت وتحديث الحزم (Packages) وإدارة نسخها (Versions).
يمكنك تثبيت Composer على جهازك باتباع الخطوات الموجودة في هذا الرابط: (Download Composer)
تثبيت حزمة باستخدام Composer
لنفترض أن لدينا مشروعا فيه ملف واحد index.php
، ونحتاج لاستخدام Package جاهزة للتحقق من صحة البريد الالكتروني.
سنستخدم هذه الـ Package كمثال:
https://github.com/egulias/EmailValidator
لتحميل أي Package باستخدام Composer، نُنفذُ الأمر require
في مجلد المشروع:
1composer require egulias/email-validator
بعد انتهاء تنفيذ الأمر، تلاحظ أن Composer قام بإنشاء ملفات جديدة:
إذا قمت بفتح الملف composer.json
ستجد محتواه مشابها لما يلي:
1{
2 "require": {
3 "egulias/email-validator": "^3.2"
4 }
5}
بداخل require
ستجد قائمة باعتماديات مشروعك، عندما استخدمنا الأمر composer require
تمت إضافة الحزمة إلى هذه القائمة، نلاحظ أيضا وجود رقم النسخة 3.2
وهي نسخة الـ Package المستخدمة.
وجود قائمة الاعتماديات الخاصة بمشروعك يعني أنك لن تضطر لتنفيذ الأمر composer require
لجميع الاعتماديات في كل مرة تثبت فيها المشروع. بما أن الاعتماديات معرفة في الملف، يمكن استخدام الأمر composer install
وسيقوم بتثبيت جميع الاعتماديات المطلوبة في الملف.
قبل الدخول في التفاصيل، سنستخدم الـ Package التي قمنا بتثبيتها.
استخدام الـ Packages التي تم تثبيتها عن طريق Composer
يقوم Composer بتثبيت ملفات الـ Packages داخل مجلد vendor
، لذلك فإن جميع الـ Packages الموجودة في قائمة require
داخل ملف composer.json
سيتم تحميلها ووضعها في مجلد vendor
.
كما تعلم، حتى نتمكن من استخدام أي Function أو Class من ملف PHP داخل ملف PHP آخر، فإننا نقوم بكتابة الأمر require
أو include
، لكن بدلا من عمل require
لجميع الـ Packages التي تم تحميلها، يقوم Composer تلقائيا بإنشاء ملف باسم autoload.php
يساعدك على عمل require
لجميع الـ Packages دفعة واحدة
1<?php
2
3
4require __DIR__ . '/vendor/autoload.php';
لنقم بتجربة استخدام الـ Package التي قمنا بتثبيتها:
1<?php
2
3require __DIR__ . '/vendor/autoload.php';
4
5use Egulias\EmailValidator\EmailValidator;
6use Egulias\EmailValidator\Validation\RFCValidation;
7
8$validator = new EmailValidator();
9$isEmailValid = $validator->isValid("example@example.com", new RFCValidation());
10
11echo $isEmailValid ? 'Email is valid' : 'Email is invalid';
12echo "\n";
وهذه هي النتيجة:
لاحظ أننا عند عمل require
للملف autoload.php
لا نضطر لإضافة جملة require
جديدة لكل Package جديدة يتم تثبيتها، أو حذف الجملة عند حذف أي Package، بل يقوم Composer بهذه العملية تلقائيا داخل ملف autoload.php
.
هذه العملية تسمى Autoloading، للقراءة عنها بشكل مفصل أكثر، يمكنك مراجعة هذا الرابط Composer Autoloading
شجرة الاعتماديات - Dependency Tree
لنقم الآن بإلقاء نظرة على محتويات vendor:
كما ترى، يوجد ملف autoload.php
الذي تحدثنا عنه سابقا، كما توجد ملفات حزمة EmailValidator التي قمنا بتثبيتها في المثال السابق ضمن المسار egulias/email-validator
ومجلد composer
يحتوي على عدد من الملفات المتعلقة بالـ Autoloading وغيرها. لكن من أين جاءت باقي الملفات؟
سنستعرض الآن محتويات الملف vendor/egulias/email-validator/composer.json
، ستجد العديد من البيانات، لكن ما يهمنا الآن هو require
:
1{
2 "require": {
3 "php": ">=7.2",
4 "doctrine/lexer": "^1.2",
5 "symfony/polyfill-intl-idn": "^1.15"
6 }
7}
شرحنا سابقا عن ملف composer.json
في مجلد المشروع وكيف يحتوي على قائمة الاعتماديات الخاصة بمشروعنا، وكما تلاحظ، فستجد بداخل كل Package ملف composer.json يحتوي على مجموعة من الاعتماديات الخاصة بتلك الـ Package، وهذا يذكرنا بالمثال الافتراضي المطروح في مقدمة هذا المقال.
لاحظ في قائمة
require
السابقة وجود php، هذا أن هذه النسخة من الحزمة لا تعمل إلا على نسخة php 7.2 أو أعلى.
هذا هو سبب وجود المجلدات التي رأيناها داخل vendor مع أننا لم نقم باختيارها كاعتماديات.
التعامل مع النُسخ (Versioning)
يقوم Composer بالتعامل مع نُسخ الـ Packages المطلوبة، فتجد دائما في قائمة require
اسم الـ Package والنسخة المطلوب تثبيتها.
الـ Packages الخاصة بـ Composer تعتمد الـ Semantic Versioning لتحديد أرقام النُسخ، فًهمُ الـ Semantic Versioning سيساعدك على فهم إدارة الـ Versions في Composer بشكل أفضل، ويمكنك مراجعة هذا الرابط الذي يشرح هذا الأسلوب في تحديد أرقام النسخ: (الإدارة الدلالية لنُسخ البرمجيات 2.0.0 | Semantic Versioning).
محددات النُسخ في ملف composer.json
يقوم Composer بتثبيت النسخة الأنسب لمشروعك، يثبت Composer أحدث نسخة متوافقة مع مشروعك في حال عدم تحديد النسخة في ملف composer.json
، أما في حالة تحديد نسخة في الملف، يقوم Composer بمحاولة تثبيتها بدلا من أحدث نسخة متوافقة:
1{
2 "require": {
3 "egulias/email-validator": "3.1.1"
4 }
5}
في المثال السابق، سيقوم Composer بتثبيت النسخة 3.1.1
، لأنها النسخة المطلوبة، لكن بما أننا نعلم أن الرقم الثالث (Patch) متعلق باصلاح المشاكل (بحسب الـ Semver)، وهو تحديث متوافق مع النسخ السابقة (Backward Compatible)، فربما تريد من Composer أن يقوم بتثبيت الحزمة مع آخر Patch متوفر:
1{
2 "require": {
3 "egulias/email-validator": "3.1.*"
4 }
5}
في هذا المثال، سيقوم Composer بتثبيت أي نسخة تبدأ بـ 3.1، ويحاول تثبيت أحدث Patch متوفر.
عندما قمنا باستخدام الأمر composer require
لتثبيت الـ Package، قام Composer تلقائيا باستخدام النسخة ^3.2
الرمز ^
قبل رقم النسخة يعني عدم السماح لـ Composer بتثبيت أي نسخة غير متوافقة مع النسخة 3.2.*
هذا يعني تثبيت رقم الـ Major وتحديث ما سواه، مثال:
النسخة | هل يسمح بتثبيتها؟ |
---|---|
3.2.* |
نعم |
3.9.9 |
نعم |
3.1.9 |
لا، لأن اقل رقم Minor مسموح هو 2 |
4.0.0 |
لا، لأن الرمز ^ يمنع تحديث رقم الـ Major |
لمعرفة جميع الصيغ الخاصة بمُحددات النسخ، راجع التوثيق الخاص بـ Composer
لاحظ أننا في composer.json
في العادة لا نحدد النسخة بالضبط، بل نقوم بإعطاء Composer تعليمات عامة حول النسخ المسموحة في المشروع، إعطاء Composer الحرية في اختيار النسخة المناسبة والمتوافقة مع متطلبات مشروعك له عدة فوائد، كالحصول على تحديثات لاعتمادياتك ضمن ضوابط وشروط تضمن عدم تعطل المشروع عن طريق عدم الموافقة على التحديث إلى نسخ تحتوي على Breaking Changes، مما يضمن حصولك على المزايا الجديدة والإصلاحات للاخطاء باستمرار لاعتماديات مشروعك.
ستجد في العادة أن الرمز ^
كثير الاستخدام، لأنه يمنع من تحديث الـ Major Version، وهذا مهم لاستقرار المشروع، راجع هذا الرابط لتفاصيل أكثر Why are unbound version constraints a bad idea? - Composer
كيف يختار Composer النسخة المناسبة
يحاول Composer تثبيت أحدث نسخة (ضمن المحددات الموجودة في ملف composer.json
)، لكن شجرة الاعتماديات (Dependency Tree) قد تكون معقدة ومترابطة، بحيث تجد أن الكثير من الاعتماديات في مشروعك تعتمد على Package محددة.
ومما يزيد الأمور تعقيدا، أن كل واحد من هذه الاعتماديات له محددات نسخ (Versions Constrants) مختلفة، لذلك يحاول Composer تحديد النسخة الأحدث المتوافقة مع جميع هذه الاعتماديات، ويستبعد النسخ التي تتوافق مع بعض الاعتماديات وتتعارض مع بعضها.
في المثال الموضح بالصورة التالية، نعتمد في مشروعنا على package-a
و package-b
وكلاهما يعتمدان على package-c
لنفترض أن هذا هو ملف composer.json
الخاص بـ package-a
:
1{
2 "require": {
3 "package-c": "^5.0||^6.0"
4 }
5}
وهذا ملف composer.json
الخاص بـ package-b
:
1{
2 "require": {
3 "package-c": "^6.0||^7.0"
4 }
5}
علامة || في محددات النسخ تعني (Or) مما يمكنك من دعم أكثر من Major Version لمشروعك بسهولة
في هذه الحالة سيقوم Composer بتثبيت أحدث نسخة متوفرة ضمن الـ Major Version رقم “6” من الـ package-c
، على الرغم من وجود النسخة 7.0 ضمن متطلبات package-b
إلا أن النسخ التي تتوافق مع جميع الاعتماديات هي النسخ الأعلى من 6.0 وأقل من 7.0.
النسخ المُثبتة باستخدام composer.lock
الـ Semantic Versioning مفيد جدا، بحيث أن تعديل آخر مقطعين من رقم النسخة (Minor, Patch) يفترض أن يضمن التوافقية مع النسخ السابقة (Backward Compatability) وعدم وجود (Breaking Changes).
لكن في الواقع، تحديد أرقام هذه النسخ يتم من مطور الـ Package بشكل يدوي، المطور أو المبرمج هو من يحدد ما إذا كان التعديل يحتوي على Breaking Changes أم لا، ويعدل أرقام النسخ بناء على ذلك.
بما أن العملية تتم بشكل يدوي، فحدوث الخطأ وارد جدا، نظريا يفترض أن التحديث من النسخة 5.3.4
إلى النسخة 5.7.2
لن يسبب أي مشاكل، فالتحديث هو لرقم الـ Minor، أي أنه يضيف مزايا جديدة دون الإخلال بالمزايا القديمة، بالإضافة لرقم الـ Patch، أي إصلاح المشاكل دون تأثير على المزايا القديمة، لكن ماذا لو أن مطور ال package قام دون انتباه بتعديل غير متوافق مع النسخ السابقة (Backward Incompatible Update)؟
تخيل مثلا أنك تقوم بالعمل على المشروع من جهازك الخاص، النسخة المثبتة من الحزمة التي تسبب المشكلة على جهازك هي 5.3.4
، ثم قمت برفع تعديلاتك إلى سيرفر البيئة الانتاجية (Production Server)، وعلى ذلك السيرفر تم تثبيت النسخة 5.7.2
والتي يفترض نظريا أن لا تسبب مشكلة، لكنها في الواقع تحتوي على مشكلة تعطل المشروع الخاص بك.
يقوم Composer بحل هذه المشكلة عن طريق ملف composr.lock
، عادة تقوم في ملف composer.json
بتحديد عدة نسخ محتملة، فمثلا تقوم بتحديد ^5.0
والذي يحتمل النسخة 5.0.0
وأي نسخة أقل من 6.0.0
، بينما في ملف composer.lock
سيتم (بشكل آلي دون تدخل منك) بتحديد رقم النسخة المثبتة بشكل دقيق دون احتمالات.
في المثال السابق الذي افترضنا فيه أن Composer قام بتثبيت النسخة 5.3.4
على جهازك، سيقوم Composer أيضا بإضافة النسخة 5.3.4
إلى ملف composer.lock
، وبعد الرفع على السيرفر وتنفيذ الأمر composer install
سيقوم Composer بتثبيت النسخة 5.3.4
لأنها النسخة الموجودة في ملف composer.lock
ولن يقوم بتثبيت النسخة 5.7.2
، وهكذا ستضمن أن النسخة التي كنت تعمل عليها هي نفسها النسخة التي سيتم تثبيتها على السيرفر.
من المهم إضافة
composer.lock
إلى المستودع الخاص بمشروعك في Git ورفعه على السيرفر مع ملفات المشروع، لا تقم أبدا بإضافةcomposer.lock
إلى ملف.gitignore
أوامر تثبيت الحزم وعلاقتها بالملفين composer.json و composer.lock
سنستعرض الآن ثلاثة أوامر متعلقة بتثبيت الحزم:
composer require {package_name}
composer install
composer update
composer require {package_name}
هذا الأمر يستخدم لتثبيت حزمة جديدة، قمنا باستخدام هذا الأمر سابقا لتثبيت حزمة Email Validator، هذا الأمر يقوم بتعديل ملفي composer.json
و composer.lock
أو يقوم بإنشائهما في حال عدم وجودهما بين ملفات المشروع.
composer install
هذا الأمر سيثبت الاعتماديات من ملف composer.json
لذلك يجب استخدام هذا الأمر على السيرفرات لضمان ثبات النسخ وعدم تحديثها، لا يقوم هذا الأمر بتعديل أي ملفات في العادة، لكن إن لم يكن ملف composer.lock
موجودا فسيقوم بإنشائه.
composer update
هذا الأمر سيقوم بتحديث الحزم (مع الأخذ بعين الاعتبار محددات النسخ في composer.json
)، يقوم هذا الأمر بتحديث ملف composer.lock
لإضافة أرقام النسخ الجديدة.
نصائح عند التعامل مع Composer
في ختام هذا المقال، أضع لكم نصائح عامة متعلقة بـ Composer أثناء التعامل مع Git أو التعامل مع السيرفرات، هذه النصائح هي قواعد عامة وقد توجد حالات قليلة استثنائية يمكن عدم تطبيق هذه الإرشادات فيها.
التعامل مع Git
يجب اضافة ملفي composer.json
و composer.lock
إلى مستودع Git، إضافتهما ستمكن جميع المطورين (والسيرفرات) من الحصول على نفس نسخ الحزم بالضبط في حال التثبيت باستخدام composer install
.
على العكس من ملفات Composer، يجب إضافة مجلد vendor
إلى .gitignore
، مجلد vendor
عادة ما يكون حجمه كبيرا جدا، ولا فائدة من وجوده في المستودع، لأن الحزم يمكن تثبيتها بسهولة بمجرد توفرها في ملف composer.json
، كما أن وجود مجلد vendor
في المستودع قد يشجع بعض المبرمجين قليلي الخبرة على التعديل على ملفات الحزم بشكل مباشر داخل مجلد vendor
مما يجعل من الصعب إضافة حزم جديدة أو تحديث الحزم الحالية.
ستجد تفاصيل أكثر عن إضافة مجلد vendor
إلى مستودع Git في هذا الرابط Should I commit the dependencies in my vendor directory? - Composer
التعامل مع السيرفرات
دائما قم باستخدام composer install
على السيرفر بدلا من composer update
، لأن الأمر install
يقوم بتحميل أرقام النسخ المثبتة في composer.lock
مما يضمن الاستقرارية (Stability) بشكل أكبر، كما أنه لا يقوم بالتعديل على الملفات، مما يعني أنه لن يسبب Conflicts عند عمل git pull
لمستودع المشروع في ملفات Composer.