# بعد از config:cache همه چیز null شد: قانون env() در لاراول

- منبع: https://di9.ir/blog/config-cache-%d9%88-env-null
- سایت: دیباگ
- نویسنده: danidiba
- تاریخ انتشار: 2026-09-08
- آخرین به‌روزرسانی: 2026-09-08

جمعه شب یک استقرار کوچک داشتیم؛ چند اصلاح ظاهری و یک تغییر در قالب فاکتور. آخر کار، چون اولین درخواست بعد از هر استقرار کمی کند بود، دستور `php artisan config:cache` را هم زدیم. همان دستوری که همه‌ی راهنماها برای پروداکشن توصیه می‌کنند و بی‌خطرترین کار دنیا به نظر می‌رسد.

سه دقیقه بعد اولین پیام رسید: درگاه پرداخت خطای «کلید نامعتبر» می‌داد. کمی بعد معلوم شد ایمیل تأیید سفارش هم نمی‌رود. یک `dd(env(&#039;ZARINPAL_MERCHANT_ID&#039;))` جواب داد `null`. فایل `.env` را باز کردیم؛ کلید سرِ جایش بود، کامل و درست، همان‌طور که ماه‌ها کار کرده بود.

این همان لحظه‌ای است که آدم به همه چیز شک می‌کند جز به دستوری که خودش زده. ولی مقصر دقیقاً همان بود. وقتی پیکربندی کش می‌شود، لاراول دیگر فایل `.env` را نمی‌خواند و هر `env()` که بیرون از پوشه‌ی `config/` صدا زده شود دستش خالی برمی‌گردد. این یادداشت همان مسیر است: علامت، علت، قانونی که جلوی تکرارش را می‌گیرد، و ترتیب درست کش‌ها بعد از استقرار.

## علامت: کلیدها خالی شدند ولی فایل env دست‌نخورده است#

نشانه‌ها معمولاً این‌ها هستند و هیچ‌کدام اسم مقصر را نمی‌گوید:

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

- ایمیل ارسال نمی‌شود یا با خطای اتصال شکست می‌خورد، چون میزبان و کاربر و رمز SMTP خالی‌اند.

- سرویس پیامک، فضای ابری یا هر پکیجی که کلید می‌خواهد، پاسخ «دسترسی ندارید» می‌دهد.

- در لاگ خطاهایی از جنس «آرگومان باید رشته باشد، مقدار null داده شد» می‌بینید.

- هیچ کدی عوض نشده و آخرین کاری که کرده‌اید یک دستور مربوط به کش بوده است.

مشخصه‌ی این خرابی، پراکندگی علامت است: چند سرویس کاملاً بی‌ربط هم‌زمان می‌شکنند. هر جا که کلیدی از محیط می‌آمد، حالا خالی است. همین پراکندگی معمولاً آدم را به بیراهه می‌برد، چون دنبال یک تغییر مشترک در کد می‌گردید و چنین تغییری وجود ندارد.

اول ببینید اصلاً فایل کش ساخته شده یا نه:

Bashکپی`ls -l bootstrap/cache/config.php`

اگر این فایل هست، تشخیص قطعی سه خط طول می‌کشد:

Bashکپی`php artisan tinker`

PHPکپی`&gt;&gt;&gt; env(&#039;ZARINPAL_MERCHANT_ID&#039;);
= null

&gt;&gt;&gt; config(&#039;services.zarinpal.merchant_id&#039;);
= &quot;8d1a0d7e-1f2c-4a1b-9c3e-0a7b2d4e6f10&quot;`

اگر `env()` نال بدهد ولی `config()` همان مقدار درست را بدهد، پرونده بسته است: پیکربندی کش شده و کد شما دارد مستقیم از `env()` می‌خواند.

## علت: کش پیکربندی، فایل env را از مدار خارج می‌کند#

در حالت عادی ترتیب بوت شدن لاراول این است. اول فایل `.env` خوانده و متغیرهایش وارد محیط اجرای PHP می‌شوند. بعد همه‌ی فایل‌های داخل `config/` اجرا می‌شوند و هر جا `env()` دیدند مقدار را از همان محیط برمی‌دارند. نتیجه یک آرایه‌ی بزرگ پیکربندی است که کد شما با `config()` از آن می‌خواند.

دستور `php artisan config:cache` همین آرایه‌ی نهایی را یک بار می‌سازد و به شکل یک فایل PHP در `bootstrap/cache/config.php` می‌نویسد.

از این لحظه به بعد، لاراول هنگام بوت اول سراغ همان فایل می‌رود. اگر موجود باشد، **مرحله‌ی خواندن `.env` را کامل رد می‌کند**. نه این‌که فایل را بخواند و نادیده بگیرد؛ اصلاً بازش نمی‌کند.

نتیجه ساده است. `config()` مقدار درست را می‌دهد، چون مقدارها همان موقعِ ساختنِ کش داخل فایل نوشته شده‌اند. ولی `env()` دیگر منبعی برای خواندن ندارد و مقدار پیش‌فرضش را برمی‌گرداند، که وقتی پیش‌فرضی ندهید یعنی `null`.

یک استثنا هم هست: اگر متغیر واقعاً در سطح سیستم‌عامل یا وب‌سرور تعریف شده باشد و نه در فایل `.env`، آن‌وقت `env()` همچنان آن را می‌بیند. روی هاست اشتراکی سی‌پنل معمولاً چنین چیزی تنظیم نشده و همه چیز از فایل می‌آید.

![دو مسیر خواندن پیکربندی، بدون کش و با کش، و نقطه‌ای که فایل محیطی از مدار خارج می‌شود](/blog/wp-content/uploads/2026/09/fig-config-cache-env-null-svg1-4v6rn-y3uxnc.svg)

## قانون یک‌خطی: env() فقط داخل پوشه‌ی config#

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

> env() فقط داخل config/.

بیرون از آن پوشه، هیچ‌جا. نه در کنترلر، نه در مدل، نه در کلاس سرویس، نه در قالب بلید، نه در فایل روت، نه در میدل‌ور، نه در جاب صف.

دلیلش زمان‌بندی است. فایل‌های `config/` دقیقاً در یک لحظه‌ی مشخص اجرا می‌شوند: یا در ابتدای هر درخواست وقتی کشی در کار نیست، یا فقط یک بار موقع ساخته شدن کش. در هر دو حالت، در آن لحظه `.env` خوانده شده و `env()` معنا دارد. هر جای دیگری از برنامه، زمان اجرا نامعلوم است و در حالت کش‌شده قطعاً بعد از عبور از مرحله‌ی محیط اتفاق می‌افتد.

این قانون از خودمان نیست؛ توصیه‌ی رسمی خود لاراول است. فقط مشکلش این است که تا وقتی کش پیکربندی را روشن نکنید، نقض کردنش هیچ هزینه‌ای ندارد و به همین دلیل سال‌ها بی‌سروصدا در پروژه می‌ماند.

## چرا این طراحی درست است و چرا خطایش خاموش می‌ماند#

### سرعت و یک منبع حقیقت#

خواندن `.env` یعنی باز کردن یک فایل متنی، پارس کردن خط‌به‌خط، و ریختن مقدارها در محیط — در **هر درخواست**. بعد از آن هم ده‌ها فایل پیکربندی باید اجرا شوند. کش پیکربندی همه‌ی این‌ها را به یک `require` از یک آرایه‌ی آماده تبدیل می‌کند که اپ‌کد آن هم در حافظه می‌ماند. روی هاست اشتراکی که منابع پردازشی تنگ است، این تفاوت واقعاً حس می‌شود.

مزیت دوم مهم‌تر است: یک منبع حقیقت. وقتی همه‌ی کد از `config()` می‌خواند، دقیقاً یک جا هست که مقدار نهایی تعیین می‌شود. در تست می‌توانید با `config(&#91;&#039;services.sms.key&#039; =&gt; &#039;fake&#039;])` مقدار را عوض کنید و مطمئن باشید همه‌ی کد همان را می‌بیند. با `env()` پراکنده در کد، چنین تضمینی ندارید.

### خطا هیچ صدایی ندارد#

`env()` وقتی چیزی پیدا نمی‌کند استثنا پرتاب نمی‌کند؛ فقط `null` می‌دهد. آن `null` بی‌سروصدا وارد سازنده‌ی یک کلاینت HTTP می‌شود، در هدر `Authorization` می‌نشیند، و شما در نهایت پیام خطای **سرویس طرف مقابل** را می‌بینید نه خطای واقعی خودتان را. فاصله‌ی بین محل خطا و محل بروزش همان چیزی است که چند ساعت وقت می‌گیرد.

همین قاعده برای `APP_DEBUG` هم صادق است: اگر بعد از کش شدن پیکربندی مقدارش را در `.env` عوض کنید، تا ساخته شدن دوباره‌ی کش هیچ اتفاقی نمی‌افتد. چون این متغیر مستقیماً روی چیزی که کاربر و مهاجم می‌بینند اثر می‌گذارد، اشتباه در آن گران تمام می‌شود؛ در [یادداشت APP_DEBUG روی هاست اشتراکی](/blog/app-debug-روی-هاست-اشتراکی) دقیقاً همین را باز کرده‌ایم.

## پیدا کردن موارد نقض env() با grep#

قبل از این‌که کش را دوباره بسازید، یک بار کل پروژه را جارو کنید. این دستور همه‌ی فراخوانی‌های `env()` را بیرون از `config/` نشان می‌دهد:

Bashکپی`grep -rn --include=&#039;*.php&#039; -E &#039;(^|&#91;^_a-zA-Z0-9])env\(&#039; app routes resources database bootstrap`

دو نکته درباره‌ی خود دستور. الگوی `&#91;^_a-zA-Z0-9]` جلوی هم‌خوانی با چیزهایی مثل `getenv(` یا `my_env(` را می‌گیرد. و `--include=&#039;*.php&#039;` فایل‌های بلید را هم می‌گیرد، چون پسوندشان `.blade.php` است و به `*.php` می‌خورد.

برای این‌که فقط عدد را ببینید:

Bashکپی`grep -rn --include=&#039;*.php&#039; -E &#039;(^|&#91;^_a-zA-Z0-9])env\(&#039; app routes resources database bootstrap | wc -l`

عدد درست این شمارش **صفر** است. هر عددی بزرگ‌تر از صفر، فهرست کارهای امروز شماست.

و برای مقایسه، ببینید در خود `config/` چند مورد دارید. این‌ها مجازند و باید هم باشند:

Bashکپی`grep -rn &quot;env(&quot; config | wc -l`

![پوشه‌ی پیکربندی که خواندن متغیر محیطی در آن مجاز است، در برابر پوشه‌های دیگر پروژه](/blog/wp-content/uploads/2026/09/fig-config-cache-env-null-svg2-jwi6n-lvmwou.svg)

## الگوی درست: کلید در config و خواندن با config()#

فرض کنیم کلید درگاه پرداخت را در `.env` دارید:

INIکپی`ZARINPAL_MERCHANT_ID=8d1a0d7e-1f2c-4a1b-9c3e-0a7b2d4e6f10
ZARINPAL_SANDBOX=false`

یک ورودی برایش در `config/services.php` بسازید:

PHPکپی`&#039;zarinpal&#039; =&gt; &#91;
    &#039;merchant_id&#039; =&gt; env(&#039;ZARINPAL_MERCHANT_ID&#039;),
    &#039;sandbox&#039; =&gt; env(&#039;ZARINPAL_SANDBOX&#039;, false),
    &#039;callback&#039; =&gt; env(&#039;ZARINPAL_CALLBACK&#039;, &#039;/payment/verify&#039;),
],`

و در کد فقط از `config()` بخوانید:

PHPکپی`$merchantId = config(&#039;services.zarinpal.merchant_id&#039;);

if (blank($merchantId)) {
    throw new RuntimeException(&#039;کلید درگاه پرداخت تنظیم نشده است.&#039;);
}`

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

سه نکته‌ی ریز که وقت می‌خورند:

- مقدار پیش‌فرض را داخل `config/` بگذارید، نه در کد. `config(&#039;services.zarinpal.sandbox&#039;, false)` هم کار می‌کند، ولی آن پیش‌فرض فقط وقتی به کار می‌آید که کلید اصلاً در آرایه نباشد؛ اگر کلید باشد و مقدارش `null` باشد، همان `null` را می‌گیرید.

- مقدارهای `.env` رشته‌اند. لاراول رشته‌های `true`، `false`، `null` و `empty` را خودش به مقدار متناظر تبدیل می‌کند، ولی عدد را نه. اگر انتظار عدد دارید، در فایل پیکربندی صریح تبدیل کنید: `(int) env(&#039;PAYMENT_TIMEOUT&#039;, 30)`.

- در فایل‌های `config/` کلوژر ننویسید. اگر مقداری قابل سریالایز شدن نباشد، `config:cache` با پیام «Your configuration files are not serializable» شکست می‌خورد و تا وقتی آن کلوژر را برندارید کش اصلاً ساخته نمی‌شود.

یک عادت خوب هم اضافه کنید: هر کلید جدیدی که به `.env` اضافه می‌کنید، همان لحظه در `.env.example` هم بگذارید، بدون مقدار واقعی. این تنها جایی است که آدمِ بعدی می‌فهمد این پروژه چه متغیرهایی لازم دارد.

## سه تله‌ی نزدیک که همین‌جا گیر می‌اندازند#

### تله‌ی اول: مقدار پیش‌فرض شما را گمراه می‌کند#

خیلی‌ها فکر می‌کنند مشکل فقط `null` است، پس پیش‌فرض می‌گذارند و خیالشان راحت می‌شود:

PHPکپی`$driver = env(&#039;PAYMENT_DRIVER&#039;, &#039;sandbox&#039;);`

در حالت کش‌شده این خط هرگز نال نمی‌دهد؛ همیشه `sandbox` می‌دهد. یعنی مقدار واقعی که در `.env` نوشته‌اید دیگر خوانده نمی‌شود و کد بی‌سروصدا با پیش‌فرض ادامه می‌دهد.

این از نال بدتر است. نال دست‌کم یک خطا تولید می‌کند و شما را خبر می‌کند. پیش‌فرضِ اشتباه می‌تواند هفته‌ها بی‌صدا کار کند — تا روزی که بفهمید همه‌ی پرداخت‌ها روی حالت آزمایشی بوده‌اند.

### تله‌ی دوم: env() داخل کلاس‌های سرویس#

خطرناک‌ترین شکلش داخل سازنده است، چون سرویس معمولاً یک بار هنگام بوت ساخته می‌شود و خطا خیلی دور از محل واقعی ظاهر می‌شود:

PHPکپی`class SmsClient
{
    public function __construct()
    {
        $this-&gt;apiKey = env(&#039;SMS_API_KEY&#039;);
    }
}`

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

PHPکپی`class SmsClient
{
    public function __construct(private ?string $apiKey = null)
    {
        $this-&gt;apiKey = $apiKey ?? config(&#039;services.sms.key&#039;);
    }
}`

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

PHPکپی`$this-&gt;app-&gt;singleton(SmsClient::class, function () {
    return new SmsClient(config(&#039;services.sms.key&#039;));
});`

### تله‌ی سوم: پکیج‌های شخص ثالث#

بعضی پکیج‌ها همین اشتباه را داخل کد خودشان دارند. علامتش خیلی مشخص است: پکیج بدون کش پیکربندی کار می‌کند و با کش می‌شکند، در حالی که شما هیچ کاری با آن نکرده‌اید.

برای پیدا کردنش، فقط پوشه‌ی همان پکیج را بگردید:

Bashکپی`grep -rn --include=&#039;*.php&#039; -E &#039;(^|&#91;^_a-zA-Z0-9])env\(&#039; vendor/VENDOR/PACKAGE/src`

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

Bashکپی`php artisan vendor:publish --provider=&quot;Vendor\Package\ServiceProvider&quot;`

اگر پکیج مقدار را مستقیم از محیط می‌خواند و اصلاً به پیکربندی نگاه نمی‌کند، از بیرون نمی‌شود درستش کرد؛ یا باید مسئله را به مخزنش گزارش کنید یا موقتاً متغیر را در سطح خود سرور تعریف کنید که بسته به پیکربندی هاست ممکن است جواب بدهد و ممکن است ندهد. راه سالم‌تر، جایگزین کردن آن پکیج است.

## کش‌های دیگر و ترتیب درست بعد از استقرار#

کش پیکربندی تنها کش لاراول نیست و پاک کردن یکی، بقیه را پاک نمی‌کند. ترتیبی که ما بعد از هر استقرار می‌زنیم این است: اول همه را پاک کن، بعد از نو بساز.

Bashکپی`php artisan config:clear
php artisan cache:clear
php artisan route:clear
php artisan view:clear

php artisan config:cache
php artisan route:cache
php artisan view:cache`

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

- `cache:clear` کش **برنامه** است، نه کش پیکربندی. اگر درایور کش شما روی دیتابیس یا ردیس باشد، این دستور برای اجرا به اتصال نیاز دارد؛ و اگر پیکربندی خراب باشد ممکن است خودش هم شکست بخورد. برای همین `config:clear` را اول می‌زنیم.

- `config:cache` باید قبل از ساختن بقیه‌ی کش‌ها اجرا شود، چون ساخت آن‌ها خودش به پیکربندی نیاز دارد.

- `route:cache` اگر حتی یک روت داشته باشید که به‌جای کنترلر یک کلوژر است، با خطای «Unable to prepare route for serialization. Uses Closure» شکست می‌خورد. راه‌حل درست، بردن آن کلوژر به یک کنترلر است، نه بی‌خیال شدن از کش روت.

- در نسخه‌های جدیدتر، `php artisan optimize` چند تا از این‌ها را با هم می‌سازد و `php artisan optimize:clear` همه را پاک می‌کند. این‌که دقیقاً کدام‌ها را شامل می‌شود بسته به نسخه فرق می‌کند؛ اگر می‌خواهید دقیقاً بدانید چه اتفاقی می‌افتد، دستورها را صریح بنویسید.

## بازیابی سریع وقتی سایت خوابیده است#

اول سایت را برگردانید، بعد دنبال مقصر بگردید. یک دستور کافی است:

Bashکپی`php artisan config:clear`

این دستور فایل `bootstrap/cache/config.php` را پاک می‌کند و لاراول بلافاصله به خواندن مستقیم `.env` برمی‌گردد. کندتر می‌شود، ولی سالم است.

گاهی خودِ artisan بوت نمی‌شود، چون کش پیکربندی به کلاسی اشاره می‌کند که دیگر وجود ندارد یا مقداری در آن خراب است. در آن حالت هیچ دستوری اجرا نمی‌شود و باید فایل را دستی حذف کنید:

Bashکپی`rm -f bootstrap/cache/config.php`

همین یک خط سایت را برمی‌گرداند. اگر SSH ندارید، از فایل‌منیجر سی‌پنل هم می‌شود همین فایل را پاک کرد؛ اثرش دقیقاً یکی است.

اگر به کش روت هم مشکوکید:

Bashکپی`rm -f bootstrap/cache/routes-*.php`

نام دقیق این فایل بسته به نسخه فرق می‌کند، برای همین از الگو استفاده کرده‌ایم. فایل‌های `packages.php` و `services.php` در همان پوشه را هم می‌شود پاک کرد؛ لاراول خودش دوباره می‌سازدشان و اگر نساخت، `php artisan package:discover` کار را تمام می‌کند.

و اگر بعد از همه‌ی این‌ها مقدارها ظاهراً درست‌اند ولی باز هم اشتباه خوانده می‌شوند، به خود فایل `.env` شک کنید؛ یک پایان‌خط ویندوزی کافی است تا مقدار با یک کاراکتر نامرئی خوانده شود — همان چیزی که در [یادداشت پایان‌خط ویندوزی در فایل .env](/blog/crlf-در-فایل-env) شکافته‌ایم.

## متغیرهایی که در زمان اجرا عوض می‌شوند اصلاً جای env نیستند#

یک اشتباه رایج‌تر از خود `env()` این است که چیزهایی را در `.env` می‌گذاریم که آن‌جا جایشان نیست: هزینه‌ی ارسال، حداقل مبلغ سفارش، متن پیام خوش‌آمد، روشن و خاموش بودن یک قابلیت. یعنی چیزهایی که مدیر سایت باید بتواند از پنل عوض کند.

کش پیکربندی این اشتباه را افشا می‌کند. مقدار در لحظه‌ی ساخت کش منجمد می‌شود و تا استقرار بعدی عوض نمی‌شود. حتی اگر کش هم نداشته باشید، ویرایش `.env` روی سرور یعنی کسی باید به فایل سیستم دسترسی داشته باشد، هیچ تاریخچه‌ای ثبت نمی‌شود، و هیچ اعتبارسنجی‌ای روی مقدار انجام نمی‌گیرد.

مرز ساده است:

کجا بگذاریمچه چیزیچرافایل محیطیکلید API، مشخصات دیتابیس، آدرس سایت، درایور کشبین محیط‌ها فرق دارد و در زمان اجرا عوض نمی‌شوددیتابیسهزینه‌ی ارسال، متن‌ها، سوییچ قابلیت‌هاآدمی ممکن است هر لحظه عوضش کند

الگوی ساده‌ای که خودمان استفاده می‌کنیم، یک جدول تنظیمات به‌علاوه‌ی یک لایه‌ی کش است:

PHPکپی`function setting(string $key, mixed $default = null): mixed
{
    $all = Cache::rememberForever(&#039;settings&#039;, function () {
        return Setting::query()-&gt;pluck(&#039;value&#039;, &#039;key&#039;)-&gt;all();
    });

    return $all&#91;$key] ?? $default;
}`

و در مدل `Setting`، بعد از هر ذخیره یا حذف، کش را باطل کنید تا تغییر بلافاصله دیده شود:

PHPکپی`protected static function booted(): void
{
    static::saved(fn () =&gt; Cache::forget(&#039;settings&#039;));
    static::deleted(fn () =&gt; Cache::forget(&#039;settings&#039;));
}`

یک نکته‌ی مرتبط: `config(&#91;&#039;x&#039; =&gt; &#039;y&#039;])` مقدار را فقط برای همان درخواست عوض می‌کند و چیزی روی دیسک نمی‌نویسد. برای تست عالی است، برای ذخیره‌ی تنظیمات اصلاً.

و اگر ورکر صف دارید، بعد از هر استقرار `php artisan queue:restart` را هم بزنید. پروسه‌ی در حال اجرا پیکربندی قدیمی را در حافظه نگه داشته و تا وقتی خودش تمام نشود همان را استفاده می‌کند؛ در یادداشت جداگانه‌ای درباره‌ی اجرای صف با کرون‌جاب همین موضوع را بازتر کرده‌ایم.

## اسکریپت استقرار تا این مرحله‌ها فراموش نشوند#

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

Bashکپی`#!/bin/bash
set -euo pipefail

APP=/home/USER/app
PHP=/usr/local/bin/ea-php84
COMPOSER=/opt/cpanel/composer/bin/composer

cd &quot;$APP&quot;

&quot;$PHP&quot; artisan down
git pull --ff-only
&quot;$PHP&quot; &quot;$COMPOSER&quot; install --no-dev --optimize-autoloader
&quot;$PHP&quot; artisan migrate --force

&quot;$PHP&quot; artisan config:clear
&quot;$PHP&quot; artisan cache:clear
&quot;$PHP&quot; artisan route:clear
&quot;$PHP&quot; artisan view:clear

&quot;$PHP&quot; artisan config:cache
&quot;$PHP&quot; artisan route:cache
&quot;$PHP&quot; artisan view:cache

&quot;$PHP&quot; artisan queue:restart
&quot;$PHP&quot; artisan up`

مسیر PHP و کامپوزر روی هر هاست فرق می‌کند؛ با `which php` و `which composer` یا از خود پنل پیدایشان کنید. مهم‌تر این‌که در شل غیرتعاملی هیچ `alias`ی وجود ندارد و `php` ممکن است نسخه‌ی دیگری باشد، پس همیشه مسیر کامل بنویسید — تله‌ای که در [یادداشت PHP 8.4 در کرون‌جاب سی‌پنل](/blog/php84-در-کرون-جاب-سی-پنل) مفصل شرح داده‌ایم.

`set -euo pipefail` یعنی اسکریپت با اولین خطا متوقف می‌شود. عارضه‌اش این است که اگر وسط کار بشکند، سایت در حالت تعمیر می‌ماند. این از سایتِ نیمه‌خراب بهتر است، ولی به شرطی که خبردار شوید؛ خروجی اسکریپت را جایی بفرستید که واقعاً می‌خوانید.

اگر `route:cache` به‌خاطر روت کلوژری شکست می‌خورد، آن خط را حذف نکنید — روت را درست کنید. حذف کردن خط یعنی هر بار که کسی این اسکریپت را می‌خواند، فکر می‌کند کش روت عمداً کنار گذاشته شده است. و اگر می‌خواهید بقیه‌ی تله‌های این نوع هاست را هم یک‌جا مرور کنید، [چک‌لیست هاست اشتراکی برای لاراول](/blog/چک-لیست-هاست-اشتراکی-برای-لاراول) همین‌ها را کنار هم گذاشته است.

## درس اصلی#

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

قانون را همین امروز بگذارید و دیگر به آن فکر نکنید: `env()` فقط داخل `config/`، و همه‌جای دیگر `config()`. یک `grep` ده‌ثانیه‌ای الان، جلوی یک شب خراب را می‌گیرد.

و یک عادت کلی‌تر که بارها به دردمان خورده: هر وقت چیزی بعد از یک دستور «بهینه‌سازی» خراب شد، برگردید ببینید آن دستور دقیقاً کدام مرحله را از مسیر برداشته است. بهینه‌سازی تقریباً همیشه یعنی حذف یک مرحله؛ سؤال درست این است که کدام مرحله، و چه کسی به آن مرحله وابسته بوده.