تغییرات .env در لاراول اعمال نمی‌شه؟ راه‌حل config:cache

event 06 آبان 1405
update به‌روزرسانی: 07 آبان 1405
timer 10 دقیقه
کاور مقاله‌ی تغییرات env در لاراول اعمال نمی‌شه: فایل .env ویرایش‌شده که نادیده گرفته می‌شه و فایل config.php کش‌شده با مقدار قدیمی و مهر CACHED
favorite 0

یه مقدار رو توی .env سرور عوض می‌کنی، مثلاً MAIL_HOST یا APP_URL یا کلید یه API، فایل رو ذخیره می‌کنی و صفحه رو رفرش می‌کنی. هیچی عوض نشده. چند بار دیگه فایل رو چک می‌کنی، مقدار جدید درست اونجاست، ولی اپلیکیشن هنوز با مقدار قبلی کار می‌کنه. یا بدتر، یه جایی از کد که با env() مقدار می‌گرفت یهو null برمی‌گردونه.

اگه تغییرات env در لاراول اعمال نمیشه، تقریباً همیشه پای کش کانفیگ وسطه. این باگ نیست، رفتار عمدی خود لاراوله. توی این مقاله می‌بینیم config:cache دقیقاً چی کار می‌کنه، چرا env() بیرون از فایل‌های config بعد از کش شدن null می‌ده، چطور قبل از هر clear بفهمی مشکل واقعاً از کجاست، و اگه بعد از config:clear هم درست نشد، کجاها رو باید بگردی.

جواب کوتاه برای کسی که عجله داره

وقتی php artisan config:cache (یا php artisan optimize) اجرا می‌شه، لاراول همه‌ی فایل‌های config/*.php رو یه بار اجرا می‌کنه و نتیجه رو با مقدارهای همون لحظه‌ی .env توی فایل bootstrap/cache/config.php می‌نویسه. از اون به بعد تا وقتی این فایل هست، لاراول اصلاً .env رو نمی‌خونه.

پس بعد از هر تغییر توی .env روی سرور:

php artisan config:cache      # کش رو با مقدارهای جدید از نو می‌سازه
php artisan queue:restart     # اگه queue worker داری
sudo systemctl reload php8.3-fpm   # اگه OPcache با validate_timestamps=0 داری (نسخه‌ی PHP خودت رو بذار)

اگه نمی‌خوای کش داشته باشی (مثلاً روی local)، فقط php artisan config:clear بزن. بقیه‌ی مقاله توضیح می‌ده چرا و کجاها ممکنه باز هم گیر کنی.

چرا لاراول بعد از config:cache سراغ .env نمی‌ره؟

در حالت عادی، لاراول موقع بوت شدن با کتابخونه‌ی phpdotenv فایل .env رو می‌خونه، بعد فایل‌های config/*.php رو اجرا می‌کنه و هر جا env('...') دیده، مقدارش رو از همون env برمی‌داره. این کار برای هر request و هر دستور Artisan تکرار می‌شه.

config:cache این مسیر رو کوتاه می‌کنه. همه‌ی config رو یه بار حساب می‌کنه و خروجی نهایی رو به شکل یه آرایه‌ی ساده‌ی PHP ذخیره می‌کنه. نکته‌ی مهم اینجاست که توی این فایل دیگه خبری از env() نیست، فقط مقدارهای نهایی هست:

<?php return array (
  'app' =>
  array (
    'name' => 'Captain Code',
    'env' => 'production',
    'debug' => false,
    'url' => 'https://example.com',
    // ...
  ),
  'mail' =>
  array (
    'default' => 'smtp',
    // ...
  ),
);

(این یه نمونه‌ی خلاصه‌ست، ولی ساختار فایل واقعی همینه: var_export از کل config.)

یعنی مقدار APP_DEBUG همون لحظه‌ای که کش ساختی «پخته شده» و توی فایل نشسته. هر چقدر هم .env رو عوض کنی، لاراول سراغش نمی‌ره، چون اصلاً .env رو لود نمی‌کنه. مستندات رسمی لاراول دقیقاً همین رو می‌گه:

Once the configuration has been cached, your application's .env file will not be loaded by the framework during requests or Artisan commands; therefore, the env function will only return external, system level environment variables.

یه نکته‌ی جالب هم از خود کد فریمورک: دستور config:cache اولین کاری که می‌کنه اجرای config:clear هست و بعد config رو از نو می‌سازه. پس برای ساختن کش تازه، لازم نیست قبلش جدا config:clear بزنی. خیلی از آموزش‌ها این دو تا رو پشت هم می‌نویسن که ضرری نداره، ولی اجباری هم نیست.

env() کجا کار می‌کنه و کجا null برمی‌گردونه؟

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

کجا env('X') صدا زده شده بدون کش config با کش config
داخل config/*.php مقدار از .env خونده می‌شه موقع ساختن کش خونده شده و مقدارش توی config.php ثابت مونده
Controller، Service، Model، Blade مقدار از .env خونده می‌شه null (یا مقدار پیش‌فرضی که خودت دادی)، مگه اینکه متغیر واقعاً توی environment سیستم تعریف شده باشه
دستورهای Artisan و Tinker مقدار از .env خونده می‌شه مثل ردیف بالا
queue worker در حال اجرا مقدار موقع شروع worker خونده شده مقدار موقع شروع worker خونده شده، تا restart نشه عوض نمی‌شه

قانونش ساده‌ست: env() رو فقط توی فایل‌های config صدا بزن و هر جای دیگه از config() استفاده کن.

// بد: توی Controller یا Blade
public function checkout()
{
    $key = env('STRIPE_KEY'); // بعد از config:cache این null می‌شه
    return view('checkout', compact('key'));
}

// خوب: config/services.php
'stripe' => [
    'key' => env('STRIPE_KEY'),
    'secret' => env('STRIPE_SECRET'),
],

// و توی Controller
public function checkout()
{
    $key = config('services.stripe.key');
    return view('checkout', compact('key'));
}

توی Blade هم همین‌طور. به‌جای {{ env('APP_NAME') }} بنویس {{ config('app.name') }}.

برای پیدا کردن همه‌ی env()هایی که جای اشتباهن، یه جست‌وجوی ساده کافیه:

grep -rn "env(" app/ resources/views/ routes/

هر چیزی که اینجا پیدا شد، یه کلید توی یکی از فایل‌های config براش بساز و با config() بخونش.

قبل از clear کردن، اول تشخیص بده

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

۱. اصلاً کش config فعاله؟

php artisan about --only=cache

جلوی Config یا CACHED می‌بینی یا NOT CACHED. اگه کش نیست و باز هم مقدار قدیمیه، مشکل از کش config نیست و باید بری سراغ بخش «هنوز درست نشده» پایین‌تر.

می‌تونی مستقیم فایل رو هم چک کنی:

ls -la bootstrap/cache/config.php

۲. لاراول الان چه مقداری رو می‌بینه؟

php artisan about --only=environment
php artisan config:show mail

config:show مقدار نهایی یه فایل config رو نشون می‌ده، همونی که اپلیکیشن واقعاً استفاده می‌کنه. اگه توی .env مقدار جدید رو گذاشتی ولی اینجا مقدار قدیمی رو می‌بینی، یعنی کش قدیمیه.

۳. یه مقدار خاص رو از داخل اپلیکیشن بخون

php artisan tinker --execute="dump(config('app.url'), env('APP_URL'))"

اگه اولی درسته و دومی null هست، همه‌چیز طبیعیه: کش فعاله و env() بیرون از config کار نمی‌کنه. یعنی کدی که null می‌گیره باید به config() تغییر کنه.

دقت کن که این دستورها با CLI اجرا می‌شن. اگه CLI مقدار درست رو نشون می‌ده ولی سایت هنوز قدیمیه، احتمالاً مشکل از PHP-FPM و OPcache هست که پایین‌تر بهش می‌رسیم.

ترتیب درست بعد از تغییر .env روی سرور

این چک‌لیست رو می‌تونی کپی کنی. مسیر پروژه، کاربر وب‌سرور و نسخه‌ی PHP رو با سرور خودت عوض کن:

cd /var/www/your-app

# ۱. ویرایش .env (با کاربری که مالک پروژه‌ست)
nano .env

# ۲. ساختن کش تازه با مقدارهای جدید
#    (خودش اول config:clear رو اجرا می‌کنه)
php artisan config:cache

# ۳. اگه queue worker داری، بهشون بگو بعد از کار فعلی‌شون restart بشن
php artisan queue:restart

# ۴. اگه OPcache فایل‌ها رو چک نمی‌کنه (validate_timestamps=0)، PHP-FPM رو reload کن
sudo systemctl reload php8.3-fpm

# ۵. چک نهایی
php artisan about --only=cache
php artisan config:show app

چند تا نکته درباره‌ی این ترتیب:

  • optimize:clear رو هر بار نزن. این دستور غیر از کش config، کش route و view و event رو هم پاک می‌کنه و cache:clear رو هم اجرا می‌کنه، یعنی کل cache store اپلیکیشنت (هر چیزی که با Cache:: ذخیره کردی) خالی می‌شه. برای یه تغییر ساده توی .env لازم نیست.
  • queue:restart از کش استفاده می‌کنه. طبق مستندات queue لاراول، سیگنال restart توی cache ذخیره می‌شه، پس cache driver باید درست کار کنه. worker هم بعد از تموم کردن job فعلی خارج می‌شه و Supervisor (یا هر process manager دیگه‌ای) باید دوباره روشنش کنه.
  • اگه از Horizon استفاده می‌کنی به‌جای queue:restart بزن php artisan horizon:terminate. اگه Octane داری هم php artisan octane:reload لازمه، چون Octane اپلیکیشن رو یه بار بوت می‌کنه و توی حافظه نگه می‌داره.

بعد از config:cache هنوز درست نشده؟ این‌ها رو چک کن

۱. OPcache و PHP-FPM

bootstrap/cache/config.php خودش یه فایل PHP هست، پس OPcache هم کشش می‌کنه. اینکه تغییرش کی دیده بشه به تنظیمات OPcache بستگی داره:

; مسیر معمول: /etc/php/8.3/fpm/conf.d/10-opcache.ini یا php.ini
opcache.validate_timestamps=1
opcache.revalidate_freq=2

مقدارهای بالا پیش‌فرض خود PHP هستن: OPcache هر چند ثانیه یه بار چک می‌کنه فایل عوض شده یا نه، پس تغییر خیلی زود دیده می‌شه. ولی خیلی از سرورهای production برای سرعت بیشتر validate_timestamps=0 می‌ذارن. اون‌وقت OPcache دیگه هیچ‌وقت خودش چک نمی‌کنه و تا PHP-FPM رو reload نکنی، همون نسخه‌ی قدیمی config.php اجرا می‌شه.

یه اشتباه رایج هم اینه که با CLI دستور opcache_reset() رو اجرا کنن. OPcache مربوط به CLI از OPcache مربوط به PHP-FPM جداست و این کار روی سایت اثری نداره. راه درست، reload کردن سرویس PHP-FPM هست.

۲. queue worker، Horizon یا Octane که restart نشده

workerها process های طولانی‌مدت هستن. اپلیکیشن رو یه بار موقع شروع بوت می‌کنن و config رو توی حافظه نگه می‌دارن. اگه ایمیل‌هایی که از صف می‌رن هنوز با SMTP قدیمی ارسال می‌شن، تقریباً مطمئن باش worker رو restart نکردی.

۳. env() بیرون از فایل‌های config

اگه یه جای کد بعد از deploy null می‌گیره ولی روی local سالمه، تقریباً همیشه همینه. روی local معمولاً کش config نداری، پس env() کار می‌کنه. روی سرور کش فعاله و همون خط null برمی‌گردونه. راه‌حل همون بالاتره: مقدار رو ببر توی یه فایل config و با config() بخونش.

۴. یه فایل .env.production که ازش خبر نداشتی

طبق مستندات لاراول، اگه APP_ENV از بیرون تعریف شده باشه (یعنی توی environment سیستم یا کانتینر، نه داخل خود .env) یا دستور با --env اجرا بشه، لاراول اول دنبال فایل .env.[APP_ENV] می‌گرده، مثلاً .env.production، و فقط اگه نبود سراغ .env می‌ره.

ls -la .env*

اگه یه .env.production قدیمی اونجاست، داری فایل اشتباه رو ویرایش می‌کنی.

۵. متغیرهای واقعی سیستم، مقدار .env رو می‌پوشونن

لاراول متغیری رو که از قبل توی environment واقعی تعریف شده با مقدار .env بازنویسی نمی‌کنه. پس اگه APP_URL رو توی بخش environment فایل docker-compose.yml، توی تنظیمات pool مربوط به PHP-FPM (مثل env[APP_URL] = ...) یا توی تنظیمات پنل هاست تعریف کرده باشی، هر چی توی .env بنویسی بی‌اثره. اون‌جا رو هم عوض کن و بعد کش رو از نو بساز.

۶. کش با کاربر اشتباه ساخته شده

اگه یه بار config:cache رو با root زدی، فایل bootstrap/cache/config.php مال root می‌شه و ممکنه بعداً کاربر deploy یا وب‌سرور نتونه جاش فایل جدید بنویسه. دستورهای Artisan رو با همون کاربری اجرا کن که مالک پروژه‌ست:

sudo -u www-data php artisan config:cache

(www-data روی Debian و Ubuntu رایجه. روی پنل‌هایی مثل aaPanel معمولاً اسم کاربر www هست.)

۷. اسکریپت deploy ترتیب رو به هم می‌زنه

این یکی خیلی بی‌صدا اذیت می‌کنه: اسکریپت deploy اول php artisan optimize یا config:cache رو اجرا می‌کنه و بعد .env جدید رو روی سرور کپی می‌کنه. نتیجه؟ کش با .env قبلی ساخته شده. .env باید همیشه قبل از هر دستور کش سر جاش باشه.

خودکارش کن: یه اسکریپت deploy که ترتیب رو رعایت کنه

بهترین راه برای اینکه این مشکل دیگه تکرار نشه اینه که این مراحل رو دستی اجرا نکنی. یه نمونه‌ی ساده برای VPS:

#!/usr/bin/env bash
set -euo pipefail

cd /var/www/your-app

php artisan down
git pull --ff-only
composer install --no-dev --optimize-autoloader --no-interaction
php artisan migrate --force

# .env از قبل روی سرور هست، پس حالا کش‌ها با مقدار درست ساخته می‌شن
php artisan optimize

php artisan queue:restart
sudo systemctl reload php8.3-fpm   # کاربر deploy باید اجازه‌ی sudo برای همین دستور رو داشته باشه

php artisan up

php artisan optimize خودش config:cache رو هم اجرا می‌کنه (به‌همراه کش route، و توی نسخه‌های جدیدتر کش event و view). اگه build فرانت‌اند هم داری، npm ci && npm run build باید قبل از optimize بیاد؛ اگه با این بخش مشکل داری، راهنمای خطای Vite manifest not found در لاراول رو ببین.

اگه می‌خوای کل این اسکریپت با هر push خودش اجرا بشه، توی راهنمای deploy خودکار لاراول با GitHub Actions قدم‌به‌قدم ساختیمش.

یه چیز دیگه هم یادت باشه: خیلی از خطاهایی که بعد از deploy می‌بینی هم از همین کش میان. مثلاً اگه SESSION_DOMAIN یا APP_URL رو عوض کردی و کش رو نساختی، ممکنه سر از خطای 419 Page Expired در لاراول و Livewire دربیاری.

هنوز گیری؟ از نیکس بپرس

اگه همه‌ی این‌ها رو چک کردی و هنوز مقدار قدیمی رو می‌بینی، خروجی php artisan about، بخش مربوط به اون کلید توی فایل config، و خطی از کد که env() یا config() رو صدا می‌زنه رو به نیکس، دستیار هوش مصنوعی کاپیتان کد بده. نیکس روی لاراول تمرکز داره و می‌تونه قدم‌به‌قدم باهات بگرده ببینه مقدار کجا گم می‌شه. فقط قبل از فرستادن، مقدار کلیدها و پسوردها رو از خروجی پاک کن.

سوالات متداول

۱. بعد از تغییر .env باید config:clear بزنم یا config:cache؟

روی production فقط config:cache کافیه، چون خودش اول کش قبلی رو پاک می‌کنه و بعد کش تازه رو با .env جدید می‌سازه. config:clear تنها رو وقتی بزن که اصلاً نمی‌خوای کش داشته باشی، مثلاً روی local.

۲. چرا env() روی local جواب می‌ده ولی روی سرور null برمی‌گردونه؟

چون روی سرور کش config فعاله و تو این حالت لاراول فایل .env رو لود نمی‌کنه. env() اون موقع فقط متغیرهای واقعی سیستم رو می‌بینه. env() رو ببر توی یه فایل config و توی کد از config() استفاده کن.

۳. می‌تونم env() رو مستقیم توی Blade بنویسم؟

بهتره ننویسی. با کش config همون مشکل null رو داره. به‌جاش مقدار رو توی یه فایل config تعریف کن و توی Blade با config('app.name') یا کلید خودت بخونش.

۴. برای اعمال تغییرات .env باید PHP-FPM رو reload کنم؟

اگه OPcache روی سرورت با validate_timestamps=0 تنظیم شده، آره. در غیر این صورت OPcache بعد از چند ثانیه خودش تغییر فایل رو می‌بینه. reload کردن ضرری نداره و توی اسکریپت deploy گذاشتنش خیال آدم رو راحت می‌کنه.

۵. queue:restart چه ربطی به .env داره؟

worker ها config رو موقع شروع لود می‌کنن و تا وقتی روشن باشن همون رو نگه می‌دارن. بدون queue:restart (یا horizon:terminate برای Horizon)، jobها با مقدارهای قبلی اجرا می‌شن.


خلاصه‌ی کل مقاله توی یه خط: .env رو عوض کن، config:cache بزن، worker ها رو restart کن، اگه لازمه PHP-FPM رو reload کن، و env() رو فقط توی فایل‌های config نگه دار.

اگه نمی‌خوای هر بار خودت درگیر deploy و سرور و این جور کش‌ها باشی، راه‌اندازی سرور لاراول، اسکریپت deploy و نگهداری پروژه رو می‌تونی به ما بسپری. توی صفحه‌ی نگهداری و امنیت سایت جزئیاتش هست و از همون‌جا می‌تونی پروژه‌ات رو برامون بفرستی.

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

Captain Code
verified
نویسنده

Captain Code

مشاهده پروفایل arrow_back

عاشق دنیای کدنویسی و به اشتراک‌گذاری دانش. ما در کاپیتان کد تلاش می‌کنیم بهترین محتوا را برای شما تولید کنیم.

پرسش‌های متداول

بعد از تغییر .env باید config:clear بزنم یا config:cache؟

روی production فقط `config:cache` کافیه، چون خودش اول کش قبلی رو پاک می‌کنه و بعد کش تازه رو با `.env` جدید می‌سازه. `config:clear` تنها رو وقتی بزن که اصلاً نمی‌خوای کش داشته باشی، مثلاً روی local.

چرا env() روی local جواب می‌ده ولی روی سرور null برمی‌گردونه؟

چون روی سرور کش config فعاله و تو این حالت لاراول فایل `.env` رو لود نمی‌کنه. `env()` اون موقع فقط متغیرهای واقعی سیستم رو می‌بینه. `env()` رو ببر توی یه فایل config و توی کد از `config()` استفاده کن.

می‌تونم env() رو مستقیم توی Blade بنویسم؟

بهتره ننویسی. با کش config همون مشکل `null` رو داره. به‌جاش مقدار رو توی یه فایل config تعریف کن و توی Blade با `config('app.name')` یا کلید خودت بخونش.

برای اعمال تغییرات .env باید PHP-FPM رو reload کنم؟

اگه OPcache روی سرورت با `validate_timestamps=0` تنظیم شده، آره. در غیر این صورت OPcache بعد از چند ثانیه خودش تغییر فایل رو می‌بینه. reload کردن ضرری نداره و توی اسکریپت deploy گذاشتنش خیال آدم رو راحت می‌کنه.

queue:restart چه ربطی به .env داره؟

worker ها config رو موقع شروع لود می‌کنن و تا وقتی روشن باشن همون رو نگه می‌دارن. بدون `queue:restart` (یا `horizon:terminate` برای Horizon)، jobها با مقدارهای قبلی اجرا می‌شن.

کاپیتان کد

این را برای پروژه‌ات می‌خواهی؟

پروژه را به کاپیتان کد بسپار، یا با نیکس همین مسئله را دیباگ کن.

forum

نظرات

lock_person

برای ثبت نظر وارد شوید

برای شرکت در گفتگو، لطفاً ابتدا وارد حساب کاربری‌تان شوید.

login ورود
chat_bubble_outline

هنوز نظری ثبت نشده

اولین نفری باشید که نظر خود را درباره این مطلب به اشتراک می‌گذارد!