انقلاب پنج به هفت؛ داستان PHP (قسمت دوم)

در قسمت اولِ داستان PHP، درباره تغییرات چشم‌گیر و اساسی نسخه‌های اولیه PHP v7 نسبت به نسل قبل از آن صحبت کردیم. در قسمت دوم این مطلب، ادامه تغییرات این نسل را تا آخرین ورژن آن یعنی نسخه ۷.۴ مرور می‌کنیم و در مطلب آینده هم نهایتا به سراغ آپدیت‌های مورد انتظار برای PHP v8 خواهیم رفت.

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

شیب ملایم؛ PHP v7.2

دومین آپدیت سری هفتم PHP در تاریخ ۹ آذر ۱۳۹۶ منتشر شد که در ادامه برخی از ویژگی‌های جدید آن را بررسی خواهیم کرد.

- گسترش type پارامتر‌

در این نسخه می‌توان type مشخص شده برای پارامتر‌های ورودی را در Subclassها حذف کرد. مثلا اگر فرزندی بخواهد یک تابع از پدرش را Override کند، می‌تواند type پارامتر‌های ورودی آن را کلا حذف و تابع جدید را بر روی متغیر با هر typeای فراخوانی کند.

interface FooInterface{
        public function bar(string $str);
}

class FooClass implements FooInterface{
        public function bar($str)
        {
                return $str;
        }
}

$a = new FooClass();
var_dump( $a-> bar(12)); \\ int(12)
var_dump( $a-> bar(“12”)); \\ string(2) &quot12&quot

توجه داشته باشید که type پارامتر‌ها را می‌توان حذف کرد؛ ولی نمی‌توان type جدیدی برای آن‌ها مشخص کرد. هم‌چنین این قانون فقط برای پارامترهای تابع است و type خروجی (اگر مشخص شده باشد) امکان حذف ندارد.

- شمارش اشیاء غیر قابل شمارش

تا قبل از این، فراخوانی تابع ()count بر روی اسکالر‌ها و اشیایی که واسط Countable را پیاده‌سازی نکرده بودند خروجی ۱ می‌داد. اما در این نسخه فراخوانی تابع ()count بر روی موارد بالا و همچنین null باعث ایجاد یک Warning می‌شود.

var_dump(count(12)) \\ PHP Warning: count(): Parameter must be an array or an object that implements Countable

- ویرگول دنباله‌دار در لیست فضای اسمی گروهی

صرفا اضافه کردن مورد جدید راحت تر می‌شود =).

use Foo\Bar\{ Foo, Bar, Baz, };

- دیباگ کردن PDO

با استفاده از تابع جدید activeQueryString می‌توان Queryای را که به پایگاه داده ارسال می‌شود، پس از اجرا به صورت کامل دید. البته این تابع فقط زمانی کارایی دارد که از حالت Emulated Prepared Statements استفاده کنیم. چون در حالت Real Prepared واقعا هیچ‌گاه Query کامل به پایگاه داده ارسال نمی‌شود و دیدن آن به صورت کامل امکان‌پذیر نیست.

$db = new PDO(...);

// works with statements without bound values
$stmt = $db->query('SELECT 1');
var_dump($stmt->activeQueryString()); // => string(8) &quotSELECT 1&quot

$stmt = $db->prepare('SELECT :string');
$stmt->bindValue(':string', 'foo');

// returns unparsed query before execution
var_dump($stmt->activeQueryString()); // => string(14) &quotSELECT :string&quot

// returns parsed query after execution
$stmt->execute();
var_dump($stmt->activeQueryString()); // => string(11) &quotSELECT 'foo'&quot

ستاره دنباله‌دار؛ PHP v7.3

این نسخه از سری هفت در تاریخ ۱۵ آذر ۱۳۹۷ و باز هم با ویژگی‌های جدید بسیاری ارائه شد که برخی از آن‌ها را در ادامه خواهید دید.

- بهبود کنترل خطا در توابع json_encode و json_decode

نداشتن روشی مناسب برای کنترل کردن خطاها هنگام استفاده از JSON برای مدت‌ها مشکلی غیر قابل حل بود؛ به طوری که از نظر توسعه‌دهندگان وب، این مسئله یک نقطه ضعف بزرگ در PHP به شمار می‌رفت.

مشکل اینجا بود که تابع ()json_decode در صورت بروز خطا مقدار null را برمی‌گرداند. در حالی که برخی اوقات null ممکن است واقعا مقداری معتبر برای یک JSON باشد.

تا قبل از PHP v7.2 برای دریافت خطایی از JSON از یک‌سری راه حل‌ها استفاده می‌شد که آن‌ها هم خیلی قابل اعتماد و کارآمد نبودند.

به عنوان مثال:

json_decode(&quot{&quot);
json_last_error() === JSON_ERROR_NONE // the result is false
json_last_error_msg() // The result is &quotSyntax error&quot

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

use JsonException;

try {
        $json = json_encode(&quot{&quot, JSON_THROW_ON_ERROR);
        return base64_encode($json);
} catch (JsonException $e) {
        throw new EncryptException('Could not encrypt the data.', 0, $e);
}

در واقع با افزوده شدن flag جدید JSON_THROW_ON_ERROR به این توابع، از این پس می‌توانید با استفاده از آن هنگام کار با توابع JSON برای خود Exception Message تعریف کنید.

- اضافه شدن تابع is_countable

در نسخه قبلی این قابلیت اضافه شد که اگر تابع ()count بر روی متغیر غیر قابل شمارشی فراخوانی شد، یک Warning ایجاد شود. در این نسخه با اضافه شدن تابع is_countable می‌توان از قبل مطمئن شد که متغیر، قابل شمارش هست یا نه.

$count = is_countable($variable) ? count($variable) : null;

- اضافه شدن توابع array_key_first و array_key_last

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

$array = [
        'a' => 'sth1',
        'b' => 'sth2',
        'c' => 'sth3',
];

array_key_first($array); // 'a'
array_key_last($array); // 'c'

- ویرگول دنباله‌دار در فراخوانی تابع

unset(
        $foo,
        $bar,
        $baz,
);

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

- اضافه شدن قابلیت انتساب ارجاعی در ()list

در این نسخه می‌توان با استفاده از لیست علاوه بر مقادیر خانه‌های یک آرایه، رفرنس‌ آن‌ها را نیز به یک سری متغیر Assign کرد.

$array = [1, 2];
list($a, &$b) = $array;

تکه کد بالا در واقع برابر است با:

$array = [1, 2];
$a = $array[0];
$b =& $array[1];

- پشتیبانی ذاتی از کوکی‌های Same Site

در دنیای امنیت از Same Site Flag برای جلوگیری از حملات CSRF استفاده می‌شود.

کوکی‌های Same Site با اعمال این قانون که «کوکی فقط در صورتی ارسال می‌شود که درخواست‌کننده در همان دامنه کوکی باشد»، باعث می‌شوند خطر حمله CSRF و نشت اطلاعات در سرورها کاهش یابد. Frameworkهای اصلی PHP قبلاً این کار را از طریق فراخوانی یک Set-Cookie Header انجام ‌می‌دادند.

در این نسخه با افزودن Flag جدید Same Site در تابع setcookie می‌توان به راحتی یک کوکی Same Site ساخت.

setcookie ( string $name [, string $value = "" [, int $expire = 0 [, string $path = "" [, string $domain = "" [, bool $secure = false [, bool $httponly = false [, string $samesite = "" ]]]]]]] )

- نمایش بهتر TypeErrorها

تا قبل از این در TypeError برای Integer و Boolean از اسم کامل‌شان استفاده می‌شد اما در این نسخه می‌توان نام کوتاه‌شده‌شان را به کار برد. این نام کوتاه، همان نامی است که در هنگام مشخص کردن type از آن استفاده می‌شود.

// Pre PHP 7.3 code
Argument 1 passed to foo() must be of the type integer/boolean

// PHP 7.3+ code
Argument 1 passed to foo() must be of the type int/bool

آخرین وارث؛ PHP v7.4

جدیدترین نسخه منتشر شده تا به امروزِ سری هفت یعنی PHP v7.4 در تاریخ ۷ آذر ۱۳۹۸ ارائه شد. این نسخه آخرین نسخه سری ۷ نیز خواهد بود و قرار است بعد از آن PHP 8 میدان را به دست بگیرد. به هر حال تا آمدن سری هشت بیایید برخی از ویژگی‌های جدید آخرین وارث نسل هفتم را بررسی کنیم.

- پشتیبانی از Arrow Functionها

تابع‌های Arrow در واقع نمایشی کوتاه شده از Anonymous Functionها هستند که دقیقا همان کارایی را دارند. البته با این تفاوت کوچک که استفاده از متغیر‌های محدوده Parent به صورت خودکار انجام می‌شود و دیگر نیازی به استفاده از کلیدواژه Use نیست. فرم کلی آن به شکل زیر است:

fn (argument_list) => expr.
$y = 1;
$fn1 = fn($x) => $x + $y;

// equivalent to using $y by value:
$fn2 = function ($x) use ($y) {
        return $x + $y;
};

$factor = 10;
$nums = array_map(fn($n) => $n * $factor, [1, 2, 3, 4]);
// $nums = array(10, 20, 30, 40);

- اضافه شدن عملگر انتسابی ??

در این نسخه می‌توان از عملگر ?? که در قسمت قبل توضیح داده شد، همانند عملگر‌های -، + و * به صورت انتسابی هم استفاده کرد. با این کار دیگر نیاز نیست اسم متغیر را در هر دو طرف تساوی بنویسیم و نوشتار کوتاه‌تری خواهیم داشت.

// The following lines are doing the same
$this->request->data['comments']['user_id'] = $this->request->data['comments']['user_id'] ?? 'value';

// Instead of repeating variables with long names, the equal coalesce operator is used
$this->request->data['comments']['user_id'] ??= 'value';

- قابلیت جدید Preloading (پیش‌بارگذاری)

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

در آن اوایل، PHP برای اجرای هر Request همه فایل‌ها را یک‌بار تجزیه و کامپایل می‌کرد ولی نتیجه این کار که به آن Opcodes می‌گوییم جایی ذخیره نمی‌شد. بنابراین نمی‌شد در درخواست بعدی از آن استفاده کرد و به این ترتیب یک پروسه‌ی تکراری بارها و بارها به ازای هر Request تکرار می‌شد.

برای حل این مشکل OPCache معرفی شد. OPCache که مخفف Opcodes Cache است با Cache کردن Opcodeهای تولید شده، سعی در افزایش سرعت اجرای درخواست‌ها دارد. فرض کنید دو Request به یک کد PHP برای اجرا شدن نیاز داشته باشند. در این صورت می‌توان از Opcodesای که Cache شده است استفاده کرد و عملیات تجزیه/کامپایل را دوباره انجام نداد. با این‌کار عملکرد برنامه‌ها در مجموع بین ۲ تا ۱۵ برابر بهبود پیدا خواهد کرد.

این کار هنوز هم یک سری هزینه اجرایی را از بین نمی‌برد. قبل از این PHP مجبور بود چک کند که اگر Source File تغییر کرده باشد، قسمت تغییر یافته را از حافظه مشترک در حافظه Process کپی کند. علاوه بر آن، از آنجایی که هر فایل PHP کاملا مستقل و بدون در نظر گرفتن دیگر فایل‌ها کامپایل و Cache می‌شود، PHP نمی‌توانست وابستگی‌های بین کلاس‌هایی که در فایل‌های متفاوتی ذخیره شده‌اند را برطرف کند. به همین خاطر مجبور بود به ازای هر Request وابستگی‌های کلاس را در زمان اجرا مجدداً پیوند دهد.

به لطف Preloading (پیش‌بارگذاری) PHP v7.4 می‌تواند بسیاری از این هزینه‌ها را از بین ببرد. در هنگام راه‌اندازی سرور و قبل از اجرای هر اپلیکیشنی، PHP می‌تواند مجموعه‌ای از فایل‌های PHP‌ را (که از قبل مشخص شده‌اند) در حافظه بارگذاری کند؛ به صورتی که محتوای آن‌ها به طور همیشگی توسط همه‌ Request‌های بعدی در دسترس باشند.

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

تمام توابع و کلاس‌های تعریف شده در این فایل‌ها که از قبل Load شده‌اند، برای همه Requestهای خارجی در دسترس خواهند بود. دقیقاً مانند Entitieyهای داخلی؛ مثل Strlen یا Exception. در حقیقت، PHP می‌تواند همه یا بخشی از Frameworkها مانند Symfony و حتی کل کتابخانه مورد نیاز برنامه را از قبل بارگذاری کند که این کار به وضوح باعث افزایش سرعت می‌شود.

برای این که Preload کار کند باید فایل‌هایی که می‌خواهید از قبل بارگذاری شوند را برای سرور مشخص کنید. به این ترتیب که ابتدا با استفاده از تابع opcache_compile_file فایل‌هایی که می‌خواهید preload شوند را در یک فایل PHP مشخص و سپس در فایل PHP.ini در قسمت opcache.preload آدرس آن را ذکر کنید.

آدرس دهی در فایل PHP.ini:

opcache.preload=/path/to/project/preload.PHP

یک پیاده‌سازی اولیه‌ برای فایل preload.php:

$files = /* An array of files you want to preload */;
foreach ($files as $file) {
        opcache_compile_file($file);
}

Typed Properties

با توجه به اضافه شدن ویژگی‌های جدیدی مثل تعیین type ورودی و خروجی در تابع در نسخه‌های پیشین، اینکه بگوییم PHP سعی دارد خودش را هر چه بیشتر به یک زبان Strong Type نزدیک کند خیلی هم گزاره اشتباهی نیست. بنابراین در راستای حرکت به سمت این هدف، قابلیت تعیین type برای Propertyهای یک کلاس نیز در این نسخه اضافه شده است.

class Example {

        // All types with the exception of &quotvoid&quot and &quotcallable&quot are supported
        public int $scalarType;
        protected ClassName $classType;
        private ?ClassName $nullableClassType;

        // Types are also legal on static properties
        public static iterable $staticProp;

        // Types can also be used with the &quotvar&quot notation
        var bool $flag;

        // Typed properties may have default values (more below)
        public string $str = &quotfoo&quot
        public ?string $nullableStr = null;

        // The type applies to all properties in one declaration
        public float $x, $y;
        // equivalent to:
        public float $x;
        public float $y;
}

اضافه شدن عملگر Spread به آرایه‌ها

در نسخه ۵.۶ PHP قابلیتی به نام Argument Unpacking معرفی شد که به کمک آن می‌شد اعضای یک آرایه را بدون اشاره به تک‌تک عضو‌ها و به عنوان آرگومان‌های ورودی یک تابع معرفی کرد. این کار با افزودن «» قبل از نام آرایه در تعریف آرگومان‌های تابع قابل انجام است.

function test(...$args) { var_dump($args); }
test(1, 2, 3);

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

$parts = ['apple', 'pear'];
$fruits = ['banana', 'orange', ...$parts, 'watermelon'];
// ['banana', 'orange', 'apple', 'pear', 'watermelon'];

تا قبل از معرفی این ویژگی، برای انجام عملیات بالا مجبور بودیم از تابع array_merge استفاده کنیم که این راه حل جدید به مراتب سریع تر از فراخوانی تابع array_merge است.

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

Numeric Literal Separator

در این نسخه برای بهتر خوانده شدن اعداد می‌توان از underline (_) برای جداسازی ارقام استفاده کرد. نگران نباشید؛ این کاراکتر به طور کامل توسط مفسر نادیده گرفته می‌شود!

6.674_083e-11; // float
299_792_458;   // decimal
0xCAFE_F00D;   // hexadecimal
0b0101_1111;   // binary

بهبود تابع strip_tags

از این تابع برای حذف تگ‌های HTML استفاده می‌شود. در این نسخه، PHP استفاده از این تابع را آسان‌تر کرده است. تا قبل از این، از این تابع به صورت زیر استفاده می‌شد:

strip_tags($str, '<a><p>');

از این به بعد می‌توان آرگومان دوم تابع را به صورت یک آرایه از تگ‌های مورد نظر نوشت:

strip_tags($str, ['a', 'p']);

اضافه شدن تابع mb_str_split

در این نسخه، PHP تابع جدید mb_str_split را معرفی کرده‌ است که دقیقا همان کاربرد تابع str_split را دارد. با این تفاوت که تابع str_split، رشته‌ را به آرایه‌ای از کاراکتر‌های یک بایتی تبدیل می‌کند؛ اما mb_str_split برای کار با Code Pointها ساخته شده است. یعنی لزومی ندارد طول کاراکتر‌ها حتما یک بایت باشند و طول کاراکتر را می‌توان در آن مشخص کرد.

mb_str_split ( string $string [, int $split_length = 1 [, string $encoding = mb_internal_encoding() ]] ) : array

مقایسه پرفورمنس

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

نتایج زیر از اجرای یک کد ساده PHP بر روی یک Macbook Pro, 2.5 GHz Intel Core i7 بدست آمده است.

PHP version : 5.6.40
--------------------------------------
test_math                 : 1.101 sec.
test_stringmanipulation   : 1.144 sec.
test_loops                : 1.736 sec.
test_ifelse               : 1.122 sec.
Mem: 429.4609375 kb Peak mem: 687.65625 kb
--------------------------------------
Total time:               : 5.103


PHP version : 7.0.33
--------------------------------------
test_math                 : 0.344 sec.
test_stringmanipulation   : 0.516 sec.
test_loops                : 0.477 sec.
test_ifelse               : 0.373 sec.
Mem: 421.0859375 kb Peak mem: 422.2109375 kb
--------------------------------------
Total time:               : 1.71


PHP version : 7.1.28
--------------------------------------
test_math                 : 0.389 sec.
test_stringmanipulation   : 0.514 sec.
test_loops                : 0.501 sec.
test_ifelse               : 0.464 sec.
Mem: 420.9375 kb Peak mem: 421.3828125 kb
--------------------------------------
Total time:               : 1.868


PHP version : 7.2.17
--------------------------------------
test_math                 : 0.264 sec.
test_stringmanipulation   : 0.391 sec.
test_loops                : 0.182 sec.
test_ifelse               : 0.252 sec.
Mem: 456.578125 kb Peak mem: 457.0234375 kb
--------------------------------------
Total time:               : 1.089


PHP version : 7.3.4
--------------------------------------
test_math                 : 0.233 sec.
test_stringmanipulation   : 0.317 sec.
test_loops                : 0.171 sec.
test_ifelse               : 0.263 sec.
Mem: 459.953125 kb Peak mem: 460.3984375 kb
--------------------------------------
Total time:               : 0.984


PHP version : 7.4.0-dev
--------------------------------------
test_math                 : 0.212 sec.
test_stringmanipulation   : 0.358 sec.
test_loops                : 0.205 sec.
test_ifelse               : 0.228 sec.
Mem: 459.6640625 kb Peak mem: 460.109375 kb
--------------------------------------
Total time:               : 1.003

انتظار برای نسل هشتم

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

ترجمه شده بر اساس:

wiki.PHP.net/rfc

"Evolution of PHP — v5.6 to v8.0", by Martynas Eskis

"5 New Features In PHP 7 That You Should Have A Look At" @ StarTutorial

"Throwable Exceptions and Errors in PHP 7" @ Trowski

"New in Symfony 4.4: Preloading Symfony Applications in PHP 7.4"

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