composer.json در مقابل composer.lock - داستان پشت دیپندنسی‌ها

تفاوت composer.json و composer.lock چیست؟

یک روز وارد یک پروژه لاراول می‌شوید.

مخزن پروژه را کلون می‌کنید و دستور زیر را اجرا می‌کنید:

composer install

همه‌چیز به‌خوبی کار می‌کند.

چند روز بعد، توسعه‌دهنده دیگری به همان پروژه اضافه می‌شود. او هم دقیقاً همین دستور را اجرا می‌کند.

اما ناگهان...

«چرا نسخه متفاوتی از پکیج دریافت می‌کنم؟»

پروژه را بررسی می‌کنید و متوجه وجود دو فایل مهم می‌شوید:

composer.json
composer.lock

در نگاه اول، این دو فایل شبیه به هم به نظر می‌رسند. هر دو با وابستگی‌های Composer سروکار دارند.

پس تفاوت واقعی آن‌ها چیست؟

بیایید با یک مثال ساده این موضوع را درک کنیم.

آشنایی با composer.json

فایل composer.json را مانند فهرست خرید پروژه خود در نظر بگیرید.

شما به Composer می‌گویید:

«این‌ها پکیج‌هایی هستند که پروژه من به آن‌ها نیاز دارد.»

برای مثال:

{
    "require": {
        "php": "^8.2",
        "laravel/framework": "^12.0",
        "laravel/sanctum": "^4.0"
    }
}

این فایل لزوماً نسخه دقیق پکیجی را که باید نصب شود مشخص نمی‌کند.

برای مثال، عبارت زیر:

"laravel/framework": "^12.0"

به این معنی است:

«یک نسخه سازگار از Laravel 12.x را مطابق با این محدودیت نسخه انتخاب کن.»

بنابراین، Composer هنگام حل وابستگی‌ها تا حدودی آزادی عمل دارد.

پس چه چیزی نسخه دقیق را مشخص می‌کند؟

اینجاست که فایل دوم وارد ماجرا می‌شود:

composer.lock

فایل composer.lock را مانند رسید نهایی خرید در نظر بگیرید.

این فایل نسخه دقیق پکیج‌هایی را که Composer هنگام حل وابستگی‌ها انتخاب کرده است، ثبت می‌کند.

برای مثال:

laravel/framework → 12.35.1
laravel/sanctum   → 4.2.0
symfony/console   → 7.3.2

بنابراین، رابطه این دو فایل به شکل زیر است:

composer.json
      ↓
«به چه پکیج‌هایی نیاز دارم؟»
      ↓
حل وابستگی‌ها توسط Composer
      ↓
composer.lock
      ↓
«این نسخه‌های دقیق انتخاب شدند.»

یک مثال واقعی

فرض کنید امروز در حال توسعه یک اپلیکیشن لاراول هستید.

در فایل composer.json نوشته‌اید:

"laravel/framework": "^12.0"

امروز ممکن است Composer نسخه زیر را انتخاب کند:

Laravel 12.30.0

یک ماه بعد، نسخه جدیدتری از لاراول منتشر می‌شود:

Laravel 12.35.1

حالا توسعه‌دهنده دیگری پروژه را کلون می‌کند.

اگر composer.lock وجود نداشته باشد

Composer ممکن است نسخه سازگار جدیدتری را انتخاب کند:

  • سیستم شما: 12.30.0
  • سیستم توسعه‌دهنده دیگر: 12.35.1

حالا با این وضعیت روبه‌رو می‌شوید:

«اما روی سیستم من کار می‌کند!»

و فرایند عیب‌یابی شروع می‌شود.

اگر composer.lock وجود داشته باشد، چه اتفاقی می‌افتد؟

فرض کنید نسخه زیر از قبل در فایل composer.lock ثبت شده است:

laravel/framework → 12.30.0

توسعه‌دهنده دیگر پروژه را کلون می‌کند و دستور زیر را اجرا می‌کند:

composer install

Composer از فایل composer.lock استفاده می‌کند و نسخه‌های ثبت‌شده در آن را نصب می‌کند.

در نتیجه:

Developer 1 → 12.30.0
Developer 2 → 12.30.0
Developer 3 → 12.30.0
Production  → 12.30.0

به این ترتیب، نتیجه قابل‌پیش‌بینی‌تری خواهید داشت.

مهم‌ترین تفاوت

ساده‌ترین روش برای به خاطر سپردن تفاوت این دو فایل:

فایل

کاربرد

composer.json

تعریف نیازمندی‌ها و محدودیت نسخه وابستگی‌ها

composer.lock

ثبت نسخه‌های دقیق انتخاب‌شده برای وابستگی‌ها

یا حتی ساده‌تر:

composer.json = چیزی که می‌خواهم
composer.lock = چیزی که انتخاب شده است

هنگام اجرای composer install چه اتفاقی می‌افتد؟

این همان جایی است که بسیاری از توسعه‌دهندگان دچار سردرگمی می‌شوند.

اگر فایل composer.lock وجود داشته باشد، با اجرای دستور زیر:

composer install

Composer نسخه‌های ثبت‌شده در فایل قفل را نصب می‌کند.

به همین دلیل، این دستور معمولاً برای راه‌اندازی یک پروژه موجود استفاده می‌شود.

برای مثال:

git clone project
cd project
composer install

در نتیجه، محیط توسعه شما وابستگی‌هایی را دریافت می‌کند که نسخه‌های آن‌ها در فایل قفل پروژه ثبت شده‌اند.

هنگام اجرای composer update چه اتفاقی می‌افتد؟

اینجا شرایط متفاوت است.

وقتی دستور زیر را اجرا می‌کنید:

composer update

Composer فایل composer.json را بررسی می‌کند و وابستگی‌ها را دوباره حل می‌کند.

ممکن است نسخه‌های جدیدتری پیدا کند که با محدودیت‌های نسخه تعریف‌شده در composer.json سازگار باشند.

سپس فایل composer.lock را به‌روزرسانی می‌کند.

برای مثال:

قبل از به‌روزرسانی:

Laravel → 12.30.0

اجرای دستور:

composer update

بعد از به‌روزرسانی:

Laravel → 12.35.1

فایل composer.lock تغییر می‌کند، چون نتیجه حل وابستگی‌ها تغییر کرده است.

یک سناریوی رایج در لاراول

فرض کنید پروژه شما شامل وابستگی‌های زیر است:

{
    "require": {
        "laravel/framework": "^12.0",
        "spatie/laravel-permission": "^6.0"
    }
}

ممکن است فایل composer.lock این نسخه‌ها را ثبت کرده باشد:

laravel/framework → 12.30.0
spatie/laravel-permission → 6.20.0

حالا Spatie نسخه جدیدی منتشر می‌کند:

6.21.0

لزومی ندارد composer.json را تغییر دهید؛ زیرا محدودیت نسخه زیر:

"spatie/laravel-permission": "^6.0"

از قبل نسخه‌های سازگار سری 6.x را مجاز می‌داند.

اجرای دستور زیر:

composer update

می‌تواند نسخه این پکیج را در composer.lock به‌روزرسانی کند.

اما اجرای دستور زیر:

composer install

معمولاً نسخه‌های از قبل ثبت‌شده در فایل قفل را نصب می‌کند.

آیا باید composer.lock را در Git ثبت کنیم؟

برای بیشتر اپلیکیشن‌ها، بله.

برای مثال، مخزن یک اپلیکیشن لاراول معمولاً ساختاری مشابه زیر دارد:

app/
bootstrap/
config/
database/
resources/
routes/
composer.json
composer.lock

هر دو فایل را در Git ثبت کنید:

git add composer.json composer.lock
git commit -m "Update dependencies"

چرا؟

زیرا تیم شما می‌خواهد نسخه‌های وابستگی‌ها در محیط‌های مختلف تا حد امکان یکسان باشند:

Local
  ↓
Development
  ↓
Staging
  ↓
Production

در حالت ایدئال، همه این محیط‌ها باید از مجموعه یکسانی از نسخه‌های وابستگی استفاده کنند.

وضعیت پکیج‌ها و کتابخانه‌های PHP چیست؟

اینجا باید بین یک اپلیکیشن و یک پکیج یا کتابخانه قابل‌استفاده مجدد تفاوت قائل شوید.

برای یک اپلیکیشن لاراول، معمولاً فایل composer.lock باید در Git ثبت شود.

اما برای یک کتابخانه PHP که قرار است در پروژه‌های دیگر استفاده شود، معمولاً فایل composer.lock بخشی از قرارداد وابستگی‌های منتشرشده آن کتابخانه نیست.

دلیلش این است که مصرف‌کنندگان کتابخانه باید بتوانند وابستگی‌های آن را در بستر پروژه خودشان حل کنند.

این تفاوت هنگام کار با پکیج‌های منتشرشده در پلتفرم‌هایی مانند Packagist اهمیت دارد.

آیا فایل composer.lock به‌صورت خودکار ساخته می‌شود؟

این فایل معمولاً هنگام حل وابستگی‌ها توسط Composer ایجاد می‌شود.

برای مثال، دستورهای زیر بسته به وضعیت پروژه می‌توانند فایل قفل را ایجاد یا به‌روزرسانی کنند:

composer install

یا:

composer update

یک پروژه ممکن است در ابتدا فقط این فایل را داشته باشد:

composer.json

و پس از حل وابستگی‌ها، ساختار آن به شکل زیر دربیاید:

composer.json
composer.lock
vendor/

داخل composer.lock چه اطلاعاتی وجود دارد؟

این فایل اطلاعات بسیار بیشتری از نام پکیج‌ها را نگه می‌دارد.

از جمله:

  • نام پکیج
  • نسخه
  • اطلاعات منبع کد (Source)
  • اطلاعات فایل توزیع‌شده (Distribution)
  • وابستگی‌ها
  • نیازمندی‌های نسخه PHP
  • هش‌ها و فراداده‌ها (Metadata)

یک نمونه ساده‌شده:

{
    "name": "laravel/framework",
    "version": "v12.30.0",
    "require": {
        "php": "^8.2"
    }
}

فایل واقعی بسیار بزرگ‌تر است؛ زیرا خود فریم‌ورک لاراول به پکیج‌های متعدد دیگری وابسته است.

لاراول وابستگی‌های بیشتری از چیزی دارد که فکر می‌کنید

ممکن است شما فقط این مورد را در composer.json بنویسید:

"laravel/framework": "^12.0"

اما لاراول به تعداد زیادی از پکیج‌های اکوسیستم Symfony و PHP وابسته است.

بنابراین، Composer یک درخت وابستگی ایجاد می‌کند:

Your Laravel App
      |
      └── Laravel Framework
             |
             ├── Symfony Console
             ├── Symfony HTTP Foundation
             ├── Symfony Routing
             ├── Symfony Process
             └── ...

فایل composer.lock گراف وابستگی حل‌شده را ثبت می‌کند.

این یکی از دلایلی است که این فایل می‌تواند حجم زیادی داشته باشد.

اشتباهی که توسعه‌دهندگان زیاد مرتکب می‌شوند

فرض کنید آخرین تغییرات پروژه را دریافت می‌کنید:

git pull

و یکی از اعضای تیم تغییراتی در این دو فایل ایجاد کرده است:

composer.json
composer.lock

شما صرفاً برای نصب پکیج جدید، دستور زیر را اجرا می‌کنید:

composer update

این کار ممکن است بسیاری از وابستگی‌های دیگر را هم به‌روزرسانی کند.

در نتیجه، ممکن است پکیج‌هایی که قبلاً به‌درستی کار می‌کردند، به‌طور ناخواسته ارتقا پیدا کنند.

برای یک پروژه موجود که فایل قفل آن مجموعه وابستگی‌های موردنظر را مشخص می‌کند، معمولاً این دستور انتخاب مناسب‌تری است:

composer install

چه زمانی باید از composer update استفاده کنیم؟

زمانی از این دستور استفاده کنید که عمداً می‌خواهید وابستگی‌ها را دوباره حل کنید یا نسخه آن‌ها را ارتقا دهید.

برای مثال:

composer update

یا برای به‌روزرسانی هدفمند یک پکیج:

composer update laravel/framework

پس از بررسی تغییرات و اجرای تست‌ها، فایل composer.lock به‌روزشده را نیز در Git ثبت کنید.

قانون طلایی

این دو دستور را به خاطر بسپارید:

composer install
       ↓
نصب نسخه‌های ثبت‌شده در فایل قفل
composer update
       ↓
حل مجدد وابستگی‌ها
       ↓
به‌روزرسانی composer.lock

و این دو فایل:

composer.json
       ↓
قوانین و نیازمندی‌های وابستگی‌ها
composer.lock
       ↓
نسخه‌های دقیق وابستگی‌های انتخاب‌شده

جمع‌بندی نهایی

ساده‌ترین تشبیه این است:

composer.json دستور پخت شماست.

این فایل می‌گوید:

«من به Laravel، Sanctum، Spatie Permission و PHP نسخه 8.2 یا بالاتر نیاز دارم.»

composer.lock فهرست دقیق مواد اولیه انتخاب‌شده است.

این فایل می‌گوید:

«این‌ها نسخه‌های دقیق تمام اجزایی هستند که در کنار یکدیگر برای اجرای این اپلیکیشن انتخاب شده‌اند.»

به همین دلیل، هر دو فایل مهم هستند؛ اما وظایف کاملاً متفاوتی دارند.

وقتی روی یک پروژه تیمی لاراول کار می‌کنید، درک این تفاوت می‌تواند شما را از یکی از رایج‌ترین مشکلات وابستگی‌ها نجات دهد:

«روی سیستم من کار می‌کند، اما روی سیستم تو نه!»

پس دفعه بعد که این دو فایل را دیدید، به خاطر داشته باشید:

  • composer.json قوانین را تعریف می‌کند.
  • composer.lock نتیجه را ثبت می‌کند.
مجتبی پاکزاد

مجتبی پاکزاد

حل مساله و چالش رو خیلی دوست دارم و رابطه خیلی خوبی با ریاضیات، برنامه‌نویسی و اقتصاد دارم. علاقه زیادی به هوش‌مصنوعی، یادگیری ماشین و موضوعات مرتبط دارم.

دیدگاه‌ها


ثبت دیدگاه