اولین بار که خطای Deprecated در PHP را جدی گرفتم، در پروژه‌ای بود که از PHP 7.4 به PHP 8.0 مهاجرت می‌کردیم. چند صد پیام زردرنگ روی ترمینال بالا آمد که همه با یک کلمه شروع می‌شدند: Deprecated. کارفرما پرسید این خطاها مهم است یا فقط نویز است؟ جواب صادقانه‌ام این بود: امروز نه، ولی سه ماه بعد که PHP 9 منتشر شود، همین پیام‌ها تبدیل به خطاهای کشنده می‌شوند. این تفاوت بنیادین خطای Deprecated در PHP با بقیه سطوح خطاست - یک هشدار از آینده که امروز ظاهر می‌شود.

خطای Deprecated در PHP دقیقاً به چه معناست؟

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

سه ویژگی کلیدی E_DEPRECATED را از بقیه سطوح جدا می‌کند:

  1. اجرا متوقف نمی‌شود: برخلاف Fatal Error، Deprecated اسکریپت را قطع نمی‌کند.
  2. معنایش کاملاً متفاوت است: Warning می‌گوید این عملیات در حال حاضر اشتباه است؛ Deprecated می‌گوید این عملیات در آینده اشتباه خواهد بود.
  3. زمان‌بندی مشخصی دارد: هر Deprecated یک مسیر از پیش تعیین‌شده دارد - از Deprecated به Warning و در نهایت به Fatal Error.

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

یک نکته‌ی ظریف: نه همه‌ی Deprecatedها هم‌ارزش هستند. بعضی از آن‌ها به رفتار پروژه‌های واقعی لطمه نمی‌زنند، بعضی دیگر می‌توانند روی رفتار برنامه تأثیر جدی بگذارند. تشخیص این تفاوت، اولین کار حرفه‌ای در مواجهه با Deprecated است.

Deprecated یک خطا نیست، یک اخطار زمان‌دار از آینده است. واکنش درست به اخطار، آماده‌شدن پیش از رسیدن موعد است، نه پنهان‌کردن اخطار.

چرا Deprecated را نباید نویز دانست؟

در پروژه‌های واقعی، Deprecated خیلی وقت‌ها نادیده گرفته می‌شود، چون در لحظه‌ی اجرا کاری نمی‌کند. ولی همین نادیده‌گرفتن، در بلندمدت سه هزینه‌ی جدی ایجاد می‌کند که هر کدام به‌تنهایی می‌تواند پروژه را به چالش بکشد:

هزینه‌ی اول: مهاجرت اجباری در بدترین زمان. وقتی نسخه‌ی جدید PHP منتشر می‌شود، آن دسته از Deprecatedها به Warning تبدیل می‌شوند. اگر در همان نسخه‌ی بعدی هم به آن‌ها پاسخ ندهید، در نسخه‌ی بعدتر به Fatal تبدیل می‌شوند. یعنی پروژه‌ای که دو سال بی‌خبر در حال تولید داده بوده، یک‌شبه از کار می‌افتد. این سناریو در پروژه‌هایی که با PHP 5.6 شروع شده و امروز به PHP 8.x مهاجرت می‌کنند، بسیار دیده می‌شود.

هزینه‌ی دوم: انباشت بدهی فنی نامرئی. هر Deprecated، یک نقطه از کد است که بر پایه‌ی رفتاری کار می‌کند که قرار است حذف شود. اگر تعداد Deprecatedها در پروژه از 100 رد شود، شما دارید روی یک سطح نازک از فرض‌های منسوخ راه می‌روید. یک روز که نمی‌دانید کدام بخش کد روی این فرض‌ها ایستاده، پیدا کردن ریشه‌ی مشکل جدید غیرممکن می‌شود.

هزینه‌ی سوم: ناسازگاری با اکوسیستم. کتابخانه‌های اصلی مثل Composer، PHPUnit، Symfony، Laravel و وردپرس، معمولاً سریع‌تر از پروژه‌های شما با PHP جدید هماهنگ می‌شوند. اگر کد شما روی APIهای Deprecated بنا شده باشد، در نسخه‌ی بعدی هرکدام از این اکوسیستم‌ها، مشکلات سازگاری جدی ظاهر می‌شود.

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

جایگاه Deprecated در سطوح خطای PHP

برای اینکه تصویر کاملی از Deprecated داشته باشید، بهتر است این سطح خطا را در کنار سایر سطوح PHP ببینیم. این جدول بر اساس تجربه‌ی من در پروژه‌های مختلف، رفتار هر سطح و اولویت رفع آن را نشان می‌دهد.

سطح خطا معنی در یک جمله رفتار PHP اولویت رفع
E_NOTICE اطلاع از موضوعی که ممکن است باگ باشد ادامه‌ی اجرا با مقدار پیش‌فرض پایین
E_WARNING یک عملیات در حال حاضر اشتباه است ادامه‌ی اجرا با داده نامعتبر بالا
E_DEPRECATED یک API در نسخه‌های آینده حذف می‌شود ادامه‌ی اجرا با رفتار فعلی متوسط (زمان‌دار)
E_STRICT استانداردهای کد رعایت نشده ادامه‌ی اجرا پایین
E_ERROR / E_PARSE خطای کشنده یا خطای نگارش توقف کامل اسکریپت بحرانی

نکته‌ی مهم: Deprecated در PHP 8.x شدت بیشتری یافته است. برخی از APIها که در PHP 7.4 فقط Deprecated بودند، در PHP 8.0 به Warning و در PHP 8.1 به رفتار جدید تغییر کردند. این پدیده به‌خصوص برای پروژه‌هایی که با نسخه‌های قدیمی‌تر PHP شروع شده‌اند، چالش جدی ایجاد کرده است. تفاوت‌های نسخه‌ای در تفاوت PHP 7 و PHP 8 به‌تفصیل باز شده است.

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

چرخه حیات سه‌مرحله‌ای یک API در PHP

تیم توسعه‌ی PHP یک سیاست مشخص برای حذف APIها دارد. این سیاست سه مرحله دارد و هر مرحله یک سطح خطای متفاوت تولید می‌کند:

مرحله‌ی اول: اعلام Deprecated

در این مرحله، API هنوز کار می‌کند ولی تیم PHP تصمیم گرفته که در نسخه‌های آینده حذف شود. پیام E_DEPRECATED ظاهر می‌شود. در این مرحله، معمولاً یک API جانشین (replacement) معرفی می‌شود که بهتر یا امن‌تر است.

مثال: تابع create_function() در PHP 7.2 رسماً Deprecated شد، چون مشکلات امنیتی جدی داشت. جانشین پیشنهادی، anonymous functions بود.

مرحله‌ی دوم: تبدیل به Warning

بعد از یک یا دو نسخه‌ی major، رفتار API تغییر می‌کند و سطح خطا از Deprecated به Warning ارتقا می‌یابد. در این مرحله، کد همچنان اجرا می‌شود ولی رفتار ممکن است تا حدی متفاوت باشد و لاگ‌ها پر از پیام Warning می‌شوند.

مرحله‌ی سوم: حذف کامل (Removal)

در نهایت، API کاملاً حذف می‌شود. اگر کد شما هنوز از آن استفاده کند، خطای Fatal با پیام Call to undefined function رخ می‌دهد و اسکریپت متوقف می‌شود. اینجاست که هزینه‌ی نادیده‌گرفتن Deprecated در مرحله‌ی اول، به‌طور کامل پرداخت می‌شود. الگوهای رفع Fatal در رفع خطای Fatal error در PHP آمده است.

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

در چرخه‌ی حیات APIهای PHP، زمان به نفع شماست، به شرطی که آن را جدی بگیرید. به ضرر شماست، اگر آن را به تأخیر بیندازید.

معروف‌ترین Deprecatedهای PHP در نسخه‌های اخیر

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

هرم پویا: {syntax}

در PHP 8.2، استفاده از متغیرهای پویا داخل رشته‌های با براکت (مثل "{$obj->$prop}") رسماً Deprecated شد. جانشین، جدا کردن پویایی از رشته است:

// Deprecated در PHP 8.2
$name = "$obj->$property";

// درست
$value = $obj->{$property};
$name = "$value";

utf8_encode و utf8_decode

این دو تابع در PHP 8.2 Deprecated شدند، چون فقط بین ISO-8859-1 و UTF-8 تبدیل انجام می‌دادند و برای کارهای پیچیده‌تر مناسب نبودند. جانشین‌ها: mb_convert_encoding() و iconv().

dynamic properties

در PHP 8.2، استفاده از خواص پویا روی کلاس‌هایی که #[AllowDynamicProperties] ندارند، Deprecated شد. راه‌حل: یا خواص را صریح اعلام کنید، یا از __get() و __set() استفاده کنید. این Deprecated در پروژه‌هایی که از ORMهای قدیمی یا کدهای بازتابی استفاده می‌کنند، شایع است.

${var} syntax در رشته‌ها

نحوه‌ی نوشتن متغیر در رشته‌ها با ${var} در PHP 8.2 Deprecated شد. جانشین، استفاده از {$var} یا $var ساده است. این مورد در پروژه‌های قدیمی که کد آن‌ها پیش از PHP 7 نوشته شده، بسیار دیده می‌شود.

optional parameter before required

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

mysqli::ping()

در نسخه‌های اخیر PHP، استفاده از mysqli::ping() برای بررسی اتصال Deprecated شد. جانشین پیشنهادی، اجرای یک query سبک یا استفاده از mysqli::query() برای بررسی سلامت اتصال است. اگر با Warningهای دیتابیس هم مواجه هستید، مقاله‌ی خطای Warning در PHP را ببینید.

APIهای تاریخ و زمان

در PHP 8.1 و 8.2، بعضی از APIهای قدیمی تاریخ و زمان مانند strftime() و gmstrftime() Deprecated شدند. جانشین‌ها: توابع مبتنی بر DateTime یا IntlDateFormatter. این تغییر در پروژه‌های چندزبانه که از قالب‌بندی تاریخی محلی استفاده می‌کنند، اهمیت زیادی دارد.

علاوه بر این‌ها، Deprecatedهای دیگری نیز در هر نسخه ظاهر می‌شوند. کلید کار این است که هر بار پروژه را با نسخه‌ی جدید PHP اجرا می‌کنید، فهرست Deprecatedهای جدید را مرور کنید و در همان دوره رفع کنید.

چگونه Deprecatedهای پروژه را شناسایی کنیم؟

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

  • تنظیم error_reporting آن‌ها را پنهان می‌کند
  • پیام‌ها در لاگ‌های حجیم گم می‌شوند
  • پیام‌ها به‌طور پیش‌فرض نمایش داده نمی‌شوند
  • در محیط production، نمایش خطاها خاموش است

برای شناسایی کامل، سه لایه را اجرا کنید:

لایه‌ی اول: اجرا با E_ALL

در محیط توسعه، تنظیمات زیر را اعمال کنید:

error_reporting = E_ALL
display_errors = On
display_startup_errors = On
log_errors = On

این تنظیمات باعث می‌شود همه‌ی Deprecatedها روی خروجی ظاهر شوند. در PHP 8، E_ALL شامل E_DEPRECATED هم می‌شود.

لایه‌ی دوم: تحلیل ایستا

ابزارهایی مثل PHPStan، Psalm، یا Rector می‌توانند بسیاری از Deprecatedها را قبل از اجرا شناسایی کنند. این ابزارها روی سطح تحلیل نحوی کار می‌کنند و فهرست دقیقی از Deprecatedهای احتمالی می‌دهند. مزیت اصلی: می‌توانید آن‌ها را در CI قرار دهید و از ورود Deprecatedهای جدید به کد جلوگیری کنید.

PHPStan با سطح 5 به بالا، و Rector با ruleهای مخصوص ارتقای نسخه، دو ابزار اصلی در این حوزه هستند. این ابزارها در پروژه‌های مدرن، بخش بزرگی از Deprecatedها را قبل از اجرا کشف می‌کنند.

لایه‌ی سوم: لاگ‌گیری اختصاصی

در محیط production، Deprecatedها را با لاگر ساختارمند ثبت کنید. یک الگوی ساده:

set_error_handler(function ($severity, $message, $file, $line) {
    if ($severity === E_DEPRECATED || $severity === E_USER_DEPRECATED) {
        error_log(sprintf(
            "[DEPRECATED] %s in %s:%d",
            $message, $file, $line
        ));
    }
    return false;
});

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

لایه‌ی چهارم: اجرا با PHP نسخه بعدی

یکی از تکنیک‌های موثر در مهاجرت، اجرای پروژه با نسخه‌ی جدید PHP در staging است. اگر پروژه با PHP 8.1 روی staging اجرا شود، Deprecatedهایی که در نسخه‌ی بعدی به Warning تبدیل می‌شوند، زودتر ظاهر می‌شوند. این تکنیک که به آن Forward Compatibility Test گفته می‌شود، به شما امکان می‌دهد پیش از موعد، برای آینده آماده شوید.

دسته‌بندی Deprecatedها بر اساس علت

بعد از شناسایی، مرحله‌ی بعدی دسته‌بندی است. Deprecatedها بر اساس علت به چند دسته تقسیم می‌شوند و هر دسته راهبرد رفع متفاوتی دارد.

دسته‌ی اول: توابع و متدهای حذف‌شده

مثل create_function() یا each(). راهبرد رفع: یافتن جانشین رسمی و جایگزینی صریح. این دسته‌ها معمولاً ساده‌ترین رفع را دارند چون تیم PHP جانشین مشخصی معرفی کرده است.

دسته‌ی دوم: رفتارهای منسوخ‌شده

مثل رفتار قدیمی توابع substr() با پارامترهای غیرمعمول، یا رفتار قدیمی تبدیل نوع در عملیات مقایسه. راهبرد رفع: بازنویسی صریح منطق کد، به‌جای تکیه بر رفتار ضمنی PHP.

دسته‌ی سوم: نحوهای قدیمی

مثل استفاده از ${var} در رشته‌ها یا قرار دادن پارامتر اختیاری قبل از اجباری. راهبرد رفع: بازنویسی نحو بر اساس استاندارد جدید PHP. این دسته‌ها را می‌توان با ابزارهایی مثل Rector به‌طور خودکار رفع کرد.

دسته‌ی چهارم: کتابخانه‌ها و پکیج‌های جانبی

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

دسته‌ی پنجم: کد خودتان

Deprecatedهایی که از کد شما می‌آیند، در دسته‌ی خودتان قرار می‌گیرند. این‌ها اولویت بالاتری دارند چون کاملاً در کنترل شما هستند. برای این دسته، یک roadmap مشخص تعیین کنید و در همان بازه رفع کنید.

این دسته‌بندی در تجربه‌ی من کمک جدی کرده که به هر Deprecated، با راهبرد مناسب خودش نگاه کنم، نه با یک رویکرد یکسان. Deprecatedهایی که از کتابخانه‌های جانبی می‌آیند، اگر با رویکرد دستی رفع شوند، به‌سرعت برمی‌گردند. Deprecatedهای کد خودتان اگر به تأخیر بیفتند، در نسخه‌های بعدی به بحران تبدیل می‌شوند.

راهبردهای رفع اصولی خطای Deprecated

حالا به بخش عملی می‌رسیم. رفع Deprecated سه رویکرد اصلی دارد که بر اساس سناریو انتخاب می‌شود. در تجربه‌ی من، ترکیب این سه رویکرد، بهترین نتیجه را می‌دهد.

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

ساده‌ترین رویکرد برای Deprecatedهایی که جانشین رسمی دارند. مثال: جایگزینی create_function() با anonymous function:

// کد Deprecated
$fn = create_function("$a, $b", "return $a + $b;");

// جایگزین درست
$fn = function ($a, $b) {
    return $a + $b;
};

این رویکرد، سریع‌ترین رفع را دارد و خطر جانبی کمتری به همراه دارد. روش تشخیص این رویکرد: پیام Deprecated معمولاً نام API جانشین را ذکر می‌کند یا شما می‌توانید در مستندات رسمی PHP پیدا کنید.

رویکرد دوم: بازنویسی منطق

بعضی از Deprecatedها نشانه‌ی منطق کد شما هستند، نه فقط یک تابع خاص. مثال: استفاده از متغیر پویا در رشته، اگر در عمق منطق قرار گرفته باشد، نیاز به بازنویسی ساختاری دارد:

// Deprecated
$msg = "User: $user->$fieldName";

// درست
$value = $user->{$fieldName};
$msg = "User: $value";

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

رویکرد سوم: استفاده از Rector

Rector ابزاری است که می‌تواند بسیاری از Deprecatedها را به‌طور خودکار رفع کند. با تعریف ruleهای مخصوص هر نسخه، Rector کل کد پروژه را می‌گردد و جایگزینی‌ها را اعمال می‌کند.

# نمونه اجرای Rector
vendor/bin/rector process src/ --set php82

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

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

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

if (PHP_VERSION_ID < 80200) {
    $value = utf8_encode($input);
} else {
    $value = mb_convert_encoding($input, "UTF-8", "ISO-8859-1");
}

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

رویکرد پنجم - که در ادامه می‌بینید - سرکوب هدفمند است. استفاده‌ی حرفه‌ای از سرکوب، در حد یک تا دو درصد کد قابل توجیه است، ولی هرگز به‌عنوان راه‌حل عمومی.

بهترین رفع Deprecated، رفع است؛ نه پنهان‌کردن. پنهان‌کردن موقت، بدهی فنی امروز و بحران فردا است.

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

در موارد خاص - مثل کدهای backward compatibility یا کتابخانه‌های جانبی که در کوتاه‌مدت قابل به‌روزرسانی نیستند - می‌توانید Deprecatedها را به‌طور هدفمند سرکوب کنید:

// فقط برای یک بلوک مشخص
$previous = error_reporting(E_ALL & ~E_DEPRECATED);
// ... کد Deprecated
error_reporting($previous);

نکته‌ی حرفه‌ای: هر بار که از این رویکرد استفاده می‌کنید، یک یادداشت در backlog تیم ثبت کنید تا در دوره‌های بعدی، رفع شود. سرکوب بدون برنامه‌ریزی، تبدیل به سرکوب دائمی می‌شود.

Deprecated در وردپرس و افزونه‌ها

در پروژه‌های وردپرسی، Deprecatedها از دو منبع اصلی می‌آیند: هسته‌ی وردپرس و افزونه‌ها.

Deprecated در هسته وردپرس

وردپرس سیاست محافظه‌کارانه‌ای برای Deprecated دارد. توابع و هوک‌ها به‌ندرت حذف می‌شوند، ولی وقتی حذف شوند، جانشین واضحی دارند. مثال: تابع get_theme_data() در نسخه‌های اخیر Deprecated شد و جانشین آن wp_get_theme() است. برای پروژه‌های وردپرسی جدید، بررسی این Deprecatedها در ابتدای پروژه، بدهی فنی را کم می‌کند. اگر تازه با وردپرس شروع می‌کنید، وردپرس چیست و چگونه شروع کنیم نقطه‌ی شروع مناسبی است.

Deprecated در افزونه‌ها

در افزونه‌های وردپرسی، Deprecatedها بیشتر دیده می‌شوند، چون افزونه‌ها برای نسخه‌های قدیمی PHP نوشته شده‌اند و در نسخه‌های جدید، برخی از APIهای آن‌ها Deprecated شده است. مثلاً:

  • استفاده از create_function() در افزونه‌های قدیمی
  • استفاده از توابع منسوخ‌شده‌ی تاریخ و زمان
  • خواص پویا روی کلاس‌های افزونه

راهبرد: اول افزونه را به آخرین نسخه به‌روز کنید. اگر Deprecated باقی ماند، در مخزن افزونه گزارش دهید. افزونه‌های رهاشده که دیگر به‌روز نمی‌شوند، تصمیم سخت‌تری دارند - یا باید یافتن جانشین فعال را در نظر بگیرید، یا کد را خودتان به‌صورت سفارشی اصلاح کنید. در فرآیند انتخاب افزونه، فاکتور به‌روزرسانی فعال را جدی بگیرید. راهنمای دانلود افزونه مطمئن معیارهای این انتخاب را باز کرده است.

Deprecated در قالب‌های وردپرس

قالب‌های قدیمی هم می‌توانند منبع Deprecated باشند، به‌خصوص اگر از کتابخانه‌های جانبی مثل jQuery نسخه‌ی قدیمی استفاده کنند. بررسی دوره‌ای قالب با ابزارهای debug مثل Query Monitor، منبع Deprecated را دقیق نشان می‌دهد.

مهاجرت نسخه PHP با رویکرد Deprecated-محور

مهاجرت نسخه PHP - مثلاً از 7.4 به 8.2 - می‌تواند یک پروژه‌ی هفته‌ها یا ماه‌ها باشد، یا می‌تواند در چند روز به‌شکل امن انجام شود. تفاوت این دو مسیر، در رویکرد Deprecated-محور است. در تجربه‌ی من، پروژه‌هایی که قبل از مهاجرت، Deprecatedها را به‌طور کامل رفع می‌کنند، مهاجرت را در یک‌دهم زمان انجام می‌دهند.

گام اول: ارزیابی میزان Deprecated

قبل از هر اقدامی، فهرست کاملی از Deprecatedهای فعلی تهیه کنید. اجرای پروژه با error_reporting = E_ALL و ثبت همه‌ی پیام‌ها در لاگ، اولین گام است. تعداد Deprecatedها، تخمین خوبی از حجم کار می‌دهد.

گام دوم: اولویت‌بندی

Deprecatedها را بر اساس سه معیار اولویت‌بندی کنید:

  1. Deprecatedهایی که در نسخه‌ی هدف به Warning تبدیل می‌شوند
  2. Deprecatedهایی که در کد خودتان هستند (نه کتابخانه‌های جانبی)
  3. Deprecatedهایی که در مسیرهای بحرانی برنامه قرار دارند

گام سوم: رفع تدریجی در شاخه‌ی جدا

رفع Deprecatedها را در یک شاخه‌ی مخصوص انجام دهید و در انتها با master ادغام کنید. این کار اجازه می‌دهد در صورت بروز مشکل، به‌راحتی rollback کنید.

گام چهارم: تست روی staging

بعد از رفع، پروژه را روی staging با نسخه‌ی هدف PHP اجرا کنید. اگر Deprecated جدیدی ظاهر شد، به مرحله‌ی رفع برگردید. این چرخه را تا رسیدن به صفر Deprecated تکرار کنید.

گام پنجم: انتشار تدریجی

در production، مهاجرت را به‌صورت تدریجی انجام دهید - مثلاً درصدی از ترافیک را روی PHP جدید بفرستید. این تکنیک که به آن Canary Deployment گفته می‌شود، ریسک را به‌شدت کاهش می‌دهد.

نکته‌ی مهم در همه‌ی این گام‌ها: برنامه‌ریزی زمانی کافی برای رفع Deprecatedهایی که نیاز به بازنویسی منطق دارند. بعضی از Deprecatedها فقط با بازنویسی ساختاری رفع می‌شوند و این فرآیند نمی‌تواند در یک شب انجام شود.

اشتباهات رایج در برخورد با Deprecated

در طول سال‌ها کار روی پروژه‌های مختلف، الگوهای تکراری از اشتباهات در برخورد با Deprecated دیده‌ام. این‌ها شش اشتباه پرتکرارند:

اشتباه اول: نادیده گرفتن Deprecated در محیط development

اگر Deprecatedها در محیط development پنهان باشند، در محیط production هم به‌طور ناگهانی ظاهر می‌شوند. این دقیقاً زمانی رخ می‌دهد که پروژه به نسخه‌ی جدید PHP مهاجرت می‌کند و برنامه‌ریزی قبلی وجود ندارد.

اشتباه دوم: استفاده از @ برای پنهان کردن همه

استفاده‌ی بی‌محابای @ برای سرکوب Deprecated، تفاوت بین کد حرفه‌ای و کد آماتور است. این رویکرد، نه‌فقط مشکل را حل نمی‌کند، بلکه آن را برای آینده‌ی دور انبار می‌کند. در محیط production، Deprecatedها را لاگ کنید، نه suppress.

اشتباه سوم: تمرکز بر Deprecatedهای کتابخانه‌های جانبی

بعضی از تیم‌ها وقت زیادی صرف می‌کنند تا Deprecatedهای کتابخانه‌های جانبی را رفع کنند، در حالی که کد خودشان Deprecatedهای مهم‌تری دارد. اولویت درست: اول کد خودتان، بعد کتابخانه‌های جانبی.

اشتباه چهارم: نبود تحلیل ایستا در CI

بدون PHPStan یا Rector در CI، هر commit می‌تواند Deprecatedهای جدید وارد کند. این انباشت تدریجی، در ماه‌های بعد به بحران تبدیل می‌شود. حرفه‌ای‌ها از روز اول این ابزارها را در CI قرار می‌دهند.

اشتباه پنجم: تعویق مهاجرت به بهانه‌ی Deprecated

بعضی از تیم‌ها چون Deprecatedها را رفع نکرده‌اند، به نسخه‌ی جدید PHP ارتقا نمی‌دهند. این تعویق، در بلندمدت هزینه‌ی سنگینی دارد چون نسخه‌های قدیمی PHP پشتیبانی امنیتی ندارند و ریسک امنیتی جدی ایجاد می‌کنند. مبانی کامل امنیت در PHP این ریسک را با جزئیات باز کرده است.

اشتباه ششم: نبود مستندسازی

Deprecatedهایی که به‌طور هدفمند سرکوب می‌شوند، بدون مستندسازی در backlog تیم گم می‌شوند. هر Deprecated که سرکوب می‌شود، باید در یک فایل مشخص - مثلاً TECH_DEBT.md - ثبت شود و در retrospectives دوره‌ای مرور شود.

اشتباه هفتم که کمتر صحبتش می‌شود: اجرای مهاجرت نسخه PHP بدون تست‌های خودکار کافی. اگر تست‌های integration و unit test پروژه‌ی شما ضعیف باشند، رفع Deprecatedها می‌تواند باگ‌های پنهان ایجاد کند. سرمایه‌گذاری روی تست، پیش‌نیاز مهاجرت امن است.

پرسش‌های پرتکرار درباره خطای Deprecated در PHP

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

آیا Deprecated همان Warning است؟

نه، این دو متفاوتند. Warning می‌گوید یک عملیات در حال حاضر با مشکل مواجه شده است؛ Deprecated می‌گوید یک API در آینده حذف می‌شود، ولی در حال حاضر کار می‌کند. تفاوت دیگر: Warning در لحظه‌ی اجرا رخ می‌دهد؛ Deprecated یک پیام زمان‌دار است. اگر در PHP 8.2 کد Deprecated اجرا کنید، PHP هشدار می‌دهد که در نسخه‌ی 9 یا 10 این کد کار نخواهد کرد. اگر با خطای Warning هم دست‌وپنجه نرم می‌کنید، مقاله‌ی خطای Warning در PHP تفاوت‌ها را کامل باز کرده است.

آیا Deprecated روی performance تأثیر دارد؟

خود Deprecated نه، ولی کدی که Deprecated شده ممکن است کندتر یا ناکارآمدتر باشد. مثلاً تابع create_function() که Deprecated شد، در هر فراخوانی، کد جدید کامپایل می‌کرد و از نظر performance به‌شدت ناکارآمد بود. جانشین آن - anonymous functions - چند برابر سریع‌تر است. بنابراین رفع Deprecated معمولاً به بهبود performance هم منجر می‌شود. این اثر در مباحث بهینه‌سازی کدهای PHP بررسی شده است.

چرا در وردپرس Deprecatedها این‌قدر زیاد هستند؟

چون وردپرس اکوسیستم عظیمی از افزونه‌ها و قالب‌های شخص ثالث دارد. هر افزونه، کد خودش را دارد و سیاست به‌روزرسانی یکسانی ندارد. افزونه‌های رهاشده، Deprecatedهای قدیمی را نگه می‌دارند. وردپرس هسته‌ی خودش در این زمینه محافظه‌کار است و به‌ندرت APIها را Deprecated می‌کند، ولی بقیه‌ی اکوسیستم این‌طور نیست.

چگونه بفهمم Deprecated از کد من است یا از کتابخانه؟

در پیام Deprecated، مسیر فایل (file path) ذکر می‌شود. اگر مسیر داخل vendor/، wp-content/plugins/ یا node_modules/ باشد، از کتابخانه است. اگر مسیر داخل src/ یا پوشه‌های پروژه‌ی خودتان باشد، از کد شماست. با Xdebug می‌توانید stack trace کامل را ببینید و منبع دقیق را شناسایی کنید.

آیا می‌توان Deprecatedها را به‌طور کامل نادیده گرفت؟

در کوتاه‌مدت، بله - اگر پروژه‌ی شما به نسخه‌ی بعدی PHP ارتقا نمی‌یابد. ولی در بلندمدت، نه. PHP هر سال نسخه‌ی جدید منتشر می‌کند و هر نسخه، پشتیبانی امنیتی نسخه‌های قبلی را کاهش می‌دهد. تیمی که Deprecatedها را نادیده می‌گیرد، در نهایت مجبور می‌شود یا روی نسخه‌ی قدیمی بماند (ریسک امنیتی) یا مهاجرت اضطراری انجام دهد (ریسک تجاری).

آیا Rector می‌تواند همه‌ی Deprecatedها را رفع کند؟

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

آیا استفاده از @ برای رفع Deprecated اشتباه است؟

در موارد محدود - مثل کتابخانه‌های جانبی که در کوتاه‌مدت قابل به‌روزرسانی نیستند - @ می‌تواند توجیه داشته باشد. ولی در 95 درصد موارد، استفاده از @ یک راه‌حل ظاهری است که ریشه را حل نمی‌کند. اصول درست: اول سعی کنید کد را بازنویسی کنید، سپس به لایه‌ی سازگاری پناه ببرید، و در نهایت - فقط در موارد نادر - از سرکوب هدفمند استفاده کنید.

آیا Deprecated در PHP 8.2 به بعد بیشتر شده است؟

بله، تیم PHP در نسخه‌های اخیر سیاست سخت‌گیرانه‌تری در پیش گرفته و APIهای بیشتری را Deprecated می‌کند. این روند در PHP 8.3، 8.4 و نسخه‌های بعدی هم ادامه خواهد داشت. دلیل این سیاست: تمیز کردن اکوسیستم و پشتیبانی از زبان‌های مدرن‌تر. برنامه‌ریزی برای مواجهه با Deprecatedهای جدید، بخشی از کار نگهداری پروژه‌های PHP در سال‌های آینده است.

آیا Deprecated روی امنیت اثر دارد؟

غیرمستقیم بله. بعضی از APIهایی که Deprecated می‌شوند، به‌دلیل مسائل امنیتی حذف می‌شوند. مثال کلاسیک: create_function() که به‌دلیل امکان code injection از یک رشته، امنیت پروژه را به‌شدت تهدید می‌کرد. جانشین آن - anonymous functions - این مشکل را حل می‌کند. بنابراین رفع Deprecated در این موارد، به‌طور مستقیم به بهبود امنیت پروژه منجر می‌شود.

چگونه در تیم، فرهنگ رفع Deprecated را جا بیندازیم؟

سه حرکت عملی: اول، هر Deprecated در code review به‌عنوان یک یافته در نظر گرفته شود. دوم، در CI یک rule مخصوص Deprecated داشته باشید که اگر پروژه بیش از حد مشخصی Deprecated داشته باشد، fail شود. سوم، در داشبورد تیم، trend Deprecatedها به‌صورت هفتگی مرور شود. فرهنگ در آمار روزانه ساخته می‌شود، نه در توصیه‌های کلی.

آیا باید هر Deprecated را بلافاصله رفع کرد؟

نه همه‌ی Deprecatedها اولویت یکسان دارند. بعضی از آن‌ها ممکن است در چند نسخه‌ی بعدی هم منسوخ نشوند (فاصله‌ی طولانی‌تری از Deprecation تا Removal). بعضی دیگر در نسخه‌ی بعدی مستقیماً حذف می‌شوند. اولویت‌بندی بر اساس مسیر زمانی هر Deprecated، روش حرفه‌ای است. اطلاعات دقیق در changelog رسمی PHP و در RFCهای deprecation موجود است.

چرا وقتی Deprecated را suppress می‌کنم، باز هم در لاگ ظاهر می‌شود؟

چون @ یا error_reporting() سطح خطا را فقط در لحظه‌ی اجرا کاهش می‌دهد. اگر لاگر مخصوصی نصب کرده باشید که مستقل از تنظیمات PHP عمل می‌کند - مثلاً Monolog یا Sentry - Deprecated همچنان در لاگ ظاهر می‌شود. این یک مزیت است، نه باگ. برای اینکه Deprecatedها در لاگ هم suppress شوند، باید به‌طور صریح در لایه‌ی لاگر تصمیم بگیرید.

آیا PHPStan می‌تواند Deprecatedها را پیدا کند؟

PHPStan در سطح‌های بالاتر و با افزونه‌های تخصصی مثل phpstan-deprecation-rules می‌تواند Deprecatedهای PHP را شناسایی کند. این افزونه‌ها بر اساس فهرست رسمی Deprecatedهای هر نسخه PHP کار می‌کنند. ترکیب PHPStan با Rector در CI، یک pipeline قوی برای جلوگیری از ورود Deprecatedهای جدید به کد ایجاد می‌کند.

آیا Deprecated در PHP و در سایر زبان‌ها یکسان است؟

مفهوم کلی Deprecated در همه‌ی زبان‌های برنامه‌نویسی مشابه است: یک قابلیت به‌عنوان منسوخ علامت‌گذاری می‌شود. ولی رفتار PHP منحصر به خودش است: در PHP، سطح خطای اختصاصی E_DEPRECATED وجود دارد، چرخه‌ی سه‌مرحله‌ای (Deprecated به Warning به Fatal) به‌طور مرتب اجرا می‌شود، و تیم PHP سیاست‌های دقیقی برای این چرخه دارد. در پایتون مثلاً هشدار DeprecationWarning معمولاً توسط ابزارهای توسعه دیده می‌شود ولی در runtime معمولی نمایش داده نمی‌شود. اگر با پایتون هم کار می‌کنید، تفاوت‌ها در مباحث مربوط به خطاهای پایتون مثل خطای RuntimeError در پایتون مشهود است.

کدام Deprecatedها در PHP بحرانی‌تر هستند؟

سه دسته اولویت بالاتری دارند:

  1. Deprecatedهایی که در PHP نسخه‌ی بعدی مستقیماً حذف می‌شوند (بر اساس roadmap رسمی)
  2. Deprecatedهایی که روی امنیت تأثیر می‌گذارند (مثل create_function())
  3. Deprecatedهایی که در مسیرهای پرترافیک اجرا می‌شوند و روی performance اثر دارند

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

آیا بعد از رفع Deprecated، کد سریع‌تر می‌شود؟

در بسیاری از موارد، بله. Deprecatedها معمولاً به APIهای قدیمی اشاره دارند که به‌طور کلی ناکارآمدتر از جانشین‌هایشان هستند. با این حال، بهبود performance تضمین‌شده نیست - گاهی Deprecated یک API جدیدتر است که فقط به‌دلیل تغییر فلسفه طراحی، منسوخ شده. برای اندازه‌گیری، همیشه قبل و بعد از رفع، benchmark کنید. الگوهای اندازه‌گیری در مباحث عملکرد PHP موجود است.

پایان‌بندی متفاوت: Deprecated به‌عنوان دوست آینده‌نگر PHP

Deprecated، برخلاف تصور رایج، دشمن نیست. یک دوست آینده‌نگر است که یک هشدار منظم از سمت تیم توسعه‌ی PHP به شما می‌دهد. اگر به آن گوش کنید، پروژه‌ی شما در سال‌های آینده سالم می‌ماند. اگر نادیده بگیرید، Deprecated از یک دوست خوب به یک طلبکار سرسخت تبدیل می‌شود که در بدترین زمان ممکن، مطالبه می‌کند.

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

یک: Deprecated را در همان دوره‌ای که ظاهر می‌شود رفع کن. نه بعد از آن. تفاوت این دو، تفاوت بین پروژه‌ای است که همیشه به‌روز است و پروژه‌ای که در نسخه‌ی بحران گیر می‌افتد.

دو: ابزارهای خودکار را جایگزین چشم انسان نکن. PHPStan و Rector بخش بزرگی از کار را انجام می‌دهند، ولی تصمیم‌های مهم - مثل بازنویسی منطق، یا انتخاب بین لایه‌بندی سازگاری و بازنویسی - نیاز به قضاوت انسانی دارد.

سه: Deprecated را در ویکی یا مستندات تیم ثبت کن. تیمی که بدهی فنی Deprecated را مستند می‌کند، در مهاجرت‌های بعدی چند برابر سریع‌تر عمل می‌کند. اطلاعات مشترک، مهم‌ترین دارایی تیم در نگهداری بلندمدت پروژه است.

در نهایت، Deprecated نشانه‌ی سلامت اکوسیستم PHP است. اگر PHP APIهایش را Deprecated نمی‌کرد و همه‌چیز را برای همیشه نگه می‌داشت، این زبان به یک اکوسیستم پوسیده تبدیل می‌شد. Deprecated یعنی PHP خودش را بازسازی می‌کند و این بازسازی، به‌نفع همه‌ی توسعه‌دهنده‌هایی است که در این اکوسیستم می‌سازند. وظیفه‌ی ما این است که با این بازسازی، همراه شویم، نه مقابل آن.

اگر Deprecatedی در پروژه‌ی شما بوده که به‌طور غیرمنتظره بزرگ شده و وقت زیادی از شما گرفته، برای من جالب است بدانم کدام Deprecated بود. تجربه‌ی خودتان را در دیدگاه‌ها بنویسید؛ به‌ویژه اگر راهبرد متفاوتی برای رفع پیدا کرده‌اید که هنوز در این مقاله نیست. 🛠️