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

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

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

گمشده‌ای به نام PHP v6

همین اول کار ممکن است این سوال برای شما هم پیش بیاید که چرا تغییرات نسخه ۷ را نسبت به نسخه ۵ بررسی می‌کنیم، به خاطر این است که نسخه ۶ تغییر مهمی نداشته است؟

نمی‌دانم تا به حال دقت کرده‌اید که گاهی شرکت‌ها در نام‌گذاری نسخه‌های محصولات خود از روی بعضی از اعداد می‌پرند. مثلا هیچ وقت Windows 9 ،Samsung Galaxy Note 6 ،iPhone 9 و IPv5 به بازار ارائه نشدند. به غیر از دلیل seven ate nine که برای پرش از «ورژن ۹ها» آورده می‌شود، دلایل بازاریابی هم گاهی اوقات در این پرش موثر است.

درباره ورژن ششم PHP هم یکی از این پرش‌ها اتفاق افتاد. PHP v6 قرار بود تا سال ۲۰۰۵ عرضه شود و پشتیبانی ذاتی از Unicode اصلی‌ترین ویژگی‌ای بود که توسعه‌دهندگان نوید آن را در نسخه جدید می دادند.

در واقع هرچند در PHP ارسال یا دریافت اطلاعات Unicode امکان‌پذیر است اما خود PHP درک درستی از آن‌ها ندارد. تعریف string در PHP رشته‌ای از کارکتر‌های یک بایتی است؛ بنابراین موقع اعمال توابعی مانند substring و strlen با رشته‌های حاوی کاراکترهای Unicode نباید انتظار خروجی‌‌های دلخواه را داشته باشیم.

در نهایت تمامی تلاش‌ها در راستای پیاده‌سازی Unicode بی‌نتیجه ماند. البته بسیاری از ویژگی‌های ریز و درشتی که برای این نسخه در نظر گرفته شد بود به branch نسخه 5.3 منتقل و در آن‌جا پیاده‌سازی شد. همین حالا هم عده‌ای معتقدند که PHP v5.3 در واقع همان PHP v6 است؛ ولی به هر حال PHP نتوانست نسل ششم خود را عرضه کند.

با اینکه PHP v6 شکست خورد و هرگز به نتیجه نرسید اما خوشبختانه PHP v7 دارای یک جدول زمانی مشخص و واضح است. در ۱۲ آذر ۱۳۹۴ حاصل تلاش‌های دو ساله تیم توسعه با نام PHP v7 ارائه شد که یکی از موفق‌ترین تجربیات این تیم بود. در واقع از نظر آن‌ها انتشار PHP v7 نه فقط منتشر شدن یک نسخه جدید، بلکه ظهور نسل جدیدی از PHP با پتانسیلی بالا بود.

اولین ریلیز نسل؛ PHP v7.0

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

- پشتیبانی از Anonymous Class

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

// Pre PHP 7 code
class Logger{
        public function log($msg){
                echo $msg;
        }
}

$util->setLogger(new Logger());


// PHP 7+ code
$util->setLogger(new class {
        public function log($msg){
                echo $msg;
        }
});

- اضافه شدن تابع تقسیم عدد صحیح

تابع Intdiv یک راه امن برای تقسیم دو عدد صحیح است؛ حتی زمانی که تقسیم بر صفر داشته باشیم. این تابع ورودی اول را بر ورودی دوم تقسیم می‌کند و حاصل تقسیم را به صورت یک عدد صحیح برمی‌گرداند. در صورتی که مقسوم علیه صفر باشد، یک E_WARNING تولید می‌شود و مقدار خروجی False است.

var_dump(intdiv(3, 2)); \\int(1)

var_dump(intdiv(-3, 2)); \\int(-1)

var_dump(intdiv(PHP_INT_MIN, -1));\\Fatal error: Uncaught ArithmeticError: Division of PHP_INT_MIN by -1 is not an integer

var_dump(intdiv(1, 0));\\Fatal error: Uncaught DivisionByZeroError: Division by zero in

- اضافه شدن عمل‌گر ??

این عمل‌گر در صورتی که اولین متغیرش null باشد مقدار دومین متغیر و در غیر این صورت مقدار همان اولین متغیر را بر می‌گرداند. اصولا از آن برای تعریف مقدار پیش‌فرض برای یک متغیر در صورتی که تعریف نشده باشد استفاده می‌شود. از این عمل‌گر به صورت زنجیره‌ای هم می‌توان استفاده کرد.

// Pre PHP 7 code
$username = isset($_GET['user']) ? $_GET['user'] : 'nobody';

// PHP 7+ cod
$username = $_GET['user'] ?? 'nobody';
$x = NULL;
$y = NULL;
$z = 3;
var_dump($x ?? $y ?? $z); // int(3)

- اضافه شدن عمل‌گر Spaceship

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

// Pre PHP 7 code
order_func($a, $b) {
        return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}

// PHP 7+ code
order_func($a, $b) {
        return $a <=> $b;
}

- قابلیت تعیین کردن type اسکالر

در PHP به داده‌های int ،float ،string و boolean اسکالر گفته می‌شود.

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

برای انجام آن دو روش وجود دارد:

دقیق:

در این روش در صورتی که type داده‌های‌ ارجاع شده به تابع با آن چیزی که تابع انتظار دارد هم‌خوانی نداشته باشد، خطا رخ می‌دهد. البته در مورد داده int در صورت لزوم می‌تواند با اضافه کردن 0. به انتهای عدد، آن را float در نظر بگیرد. برای فعال کردن این حالت کافی است مقدار strict_types را ۱ کنید.

declare(strict_types=1);

function getSum(float $a, float $b) {
        return $a + $b;
}

getSum(3, &quot2 week&quot);
// Fatal error: Uncaught TypeError: Argument 2 passed to getSum() must be of the type float, string given

getSum(1.8,  &quot4.5&quot);
// Fatal error: Uncaught TypeError: Argument 2 passed to getSum() must be of the type float, string given

getSum(3.1, 2);
// int(2) change to float(2.0)
//returns float(5.1)

اجباری:

در این روش که حالت پیش‌فرض هم هست، حتی اگر type داده ارجاعی همخوانی نداشته باشد PHP سعی می‌کند آن‌ را به داده مورد نظر تبدیل کند.

function getSum(float $a, float $b) {
        return $a + $b;
}

getSum(6, &quot7 week&quot);
//Here int(6) changed to float(6.0) and string “7 week” changed to float(7.0)
//with a Notice: A non well formed numeric value encountered
//returns float(13)

getSum(1.1, &quot2.2&quot);
//Here string &quot2.2&quot is changed to float(2.2) without any notice
//returns float(3.3)

getSum(3.1, 2);
// changes int(2) to float(2.0)
// returns float(5.1)

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

- قابلیت تعیین کردن type خروجی تابع

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

مانند تعیین type ورودی، برای خروجی هم همان دو حالت دقیق و اجباری وجود دارد.

دقیق:

declare(strict_types=1);

function getSum(float $a, float $b) : int {
        return $a + $b;
}

getSum(3.1, 2); //Fatal error:  Uncaught TypeError: Return value of getSum() must be of the type integer, float returned

اجباری:

function getSum(float $a, float $b) : int {
        return $a + $b;
}

getSum(3.1, 2); // changes int(2) to float(2.0) and returns int(5)

- قابلیت مشخص کردن use به صورت گروهی

این قابلیت زمانی به کار می‌آید که بخواهیم از یک فضای اسمی مشترک (یا حداقل تا قسمتی مشترک) چندین موجودی را import کنیم. به جای اینکه تک‌تک آن‌ها را در خط‌های جداگانه تایپ کنیم، می‌توانیم با استفاده از group use قسمت مشترک آن‌ها را فاکتور بگیریم و نسخه خلاصه‌تری از آن را بنویسیم.

// Pre PHP 7
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;

//  PHP 7+
use Symfony\Component\Console\{ Helper\Table, Input\ArrayInput, Output\NullOutput,};

- بهبود Performance

همانطور که از دو نمودار زیر پیدا است PHP v7 در خصوص کارایی و میزان استفاده از حافظه بهبود قابل توجهی نسبت به نسخه قبل از خود پیدا کرده است؛ به طوری که عملکرد آن در خصوص صفحه‌ای شامل درخواست‌های متنوع از پایگاه داده، با opcمقاache فعال حدود ۳ برابر و بدون opcache حدود ۲.۷ برابر سریعتر از PHP v5.6 است. از منظر حافظه هم می‌توان دید که تفاوت معناداری بین این دو نسخه وجود دارد.

مقایسه کارایی در نسخه ۵.۶ و ۷.۰ PHP
مقایسه کارایی در نسخه ۵.۶ و ۷.۰ PHP
مقایسه مصرف حافظه در نسخه ۵.۶ و ۷.۰  PHP
مقایسه مصرف حافظه در نسخه ۵.۶ و ۷.۰ PHP

- قابلیت کنترل کردن خطاها

کنترل کردن fatal errorها در نسخه‌های قبلی PHP غیرممکن بود. این دست خطاها هیچ‌گونه handlerای را فراخوانی نمی‌کنند و صرفا اجرای اسکریپت را متوقف می‌کنند.

در PHP v7، هنگام رخ دادن خطاهای E_RECOVERABLE_ERROR و E_ERROR به جای متوقف کردن اجرای اسکریپت، یک Exception ایجاد می‌شود. اما fatal errorها هنوز هم برای شرایط خاص مانند run out of memory وجود دارند و مانند گذشته عمل می‌کنند. این قانون برای Exceptionای که catch نشده باشد هم برقرار است.

علاوه بر این، Exceptionهای ایجاد شده توسط fatal errorها برخلاف دیگر Exceptionها به جای ارث‌بری از کلاس Exception، از کلاس Error ارث‌بری می‌کنند. بنابراین برای راحتی کار و یکی کردن دو شاخه استثنای Exception و Error، هر دو interface جدیدی به نام Throwable را پیاده‌سازی می‌کنند.

به این ترتیب، سلسله مراتب Exception جدید در PHP v7 به شکل زیر است:

interface Throwable
   | - Exception implements Throwable
           | - ...
   | - Error implements Throwable
           | - TypeError extends Error
           | - ParseError extends Error
           | - ArithmeticError extends Error
                   | - DivisionByZeroError extends Arithmet

- ویژگی Unicode برای نقطه‌کد‌های Escape Syntax

در Unicode به هر کاراکتر یک «نقطه‌کد» (Code Point) تخصیص داده می‌شود. برای نمونه A به نقطه‌کد U+0041 نگاشت می‌شود که هگزادسیمال‌شده‌ همان 65 در سیستم دَه‌دَهی است. تا قبل از PHP v7 استفاده از نقطه‌کدها در رشته‌ها کار آسانی نبود و باید از توابعی مانند mb_convert_encoding استفاده می‌شد؛ اما PHP v7 با معرفی یک Escape Character برای این نقطه‌کد‌ها، کار را بسیار راحت کرده است.

echo &quotHi \u{1f606}&quot//Hi ?

- مجاز بودن تعیین Keys در ()list

تکنولوژی PHP از ساختاری زبانی برای مقدار‌دهی چندین متغیر توسط عناصر یک آرایه به نام ()list استفاده می‌کند.

list($first, $second, $third) = $someArray;

تا قبل این نسخه از ()list فقط به همین صورت می‌شد استفاده کرد؛ یعنی اولین متغیری که در آن قرار می‌گرفت برابر با مقدار اولین خانه آرایه و دومین متغیر دومین خانه آرایه و الی آخر.

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

class ElePHPant{
        private $name, $colour, $age, $cuteness;

        public function __construct(array $attributes) {

                // Pre PHP 7 code
                $this->name = $attributes[&quotname&quot];
                $this->colour = $attributes[&quotcolour&quot];
                $this->age = $attributes[&quotage&quot];
                $this->cuteness = $attributes[&quotcuteness&quot];

                // PHP 7+ code
                 list(
                        &quotname&quot => $this->name,
                        &quotcolour&quot => $this->colour,
                        &quotage&quot => $this->age,
                        &quotcuteness&quot => $this->cuteness
                ) = $attributes;
        }
}

همچنان انقلابی؛ PHP v7.1

نسخه جدید سری ۷ در ۱۱ آذر ۱۳۹۵ منتشر شد. این نسخه نیز شامل تعداد زیادی تغییر و تحول بود که سعی می‌کنیم بعضی از مهم‌ترین‌هایشان را بررسی کنیم.

- اضافه شدن Nullable Types

در بسیاری از زبان‌های برنامه نویسی از جمله PHP معمول است که به متغیر اجازه دهیم یک type داشته باشد یا تهی بماند. این تهی‌بودن غالباً حاکی از خطا یا نبود چیزی برای برگرداندن است. تا قبل از این، بدون مشخص کردن type متغیر و یا دادن مقدار پیشفرض null به پارامتر‌های ورودی، می‌شد اینکار را انجام داد. مشکل زمانی است که type خروجی تابع مشخص می‌شود؛ در این صورت دیگر اجازه نداریم null را به عنوان خروجی بدهیم. اما در این نسخه با اضافه کردن ? قبل از type متغیر می‌توان اجازه داد خروجی null هم باشد.

function answer(): ?int  { 
        return null; //ok
}

function answer(): ?int  {
        return 42; // ok
}

function answer(): ?int  {} // error -- none returned

function answer(): ?int {
        return new stdclass(); // error
}

function say(?string $msg) {
        if ($msg) {
                echo $msg;
        }
}

say('hello'); // ok -- prints hello
say(null); // ok -- does not print
say(); // error -- missing parameter
say(new stdclass); //error -- bad type

- اضافه شدن خروجی Void

function should_return_nothing(): void {
        return 1; // Fatal error: A void function must not return a value
}

function lacks_return(): void {
        // valid
}

function returns_nothing(): void {
        return; // valid
}

function returns_null(): void {
        return null; // Fatal error: A void function must not return a value
}

برخلاف دیگر typeهای خروجی که هنگام فراخوانی تابع چک می‌شوند، Void در زمان کامپایل بررسی می‌شود؛ به این معنی که بدون نیاز به فراخوانی تابع، خطا ایجاد می‌شود.

- اضافه شدن شبهِ Type قابل تکرار (Pseudo Iterable Type)

برای یک تابع در PHP این اتفاق معمول است که یک آرایه و یا یک شیء (که واسط Traversableرا پیاده‌سازی کرده است) به عنوان ورودی بپذیرد تا بتواد با استفاده از foreach اعضای آن را پیمایش کند. اما از آن‌جا که آرایه Primitive Type و Traversableیک واسط هستند، تا قبل از این نمی‌شد type ورودی یا خروجی را طوری تعیین کرد که نشان‌دهنده قابل تکرار (Iterable) بودنش باشد. PHP v7.1 با معرفی شبهِ type قابل تکرار (Iterable Pseudo-Type) این مشکل را حل کرده است.

function foo(iterable $iterable) {
        foreach ($iterable as $value) {
                // ...
        }
}


function bar(): iterable {
        return [1, 2, 3];
}

مقدار پیش‌فرض Iterable می‌تواند یک آرایه و یا null باشد.

function foo(iterable $iterable = [ ]) {
        // ...
}

- قابلیت جدید Closure from Callable

قابلیت Closure برای نمایش توابع anonymous به کار می‌رود. در واقع متغیر‌هایی که شامل این توابع هستند اشیای کلاس Closureاند.

$a = function(){ echo “hi”;}; \\ a is a object of Closure class

به علاوه این، Callable شبهِ typeای است که با استفاده از آن می‌توان توابع را با استفاده از نام‌شان به صورت داینامیک در هر کجای برنامه فراخوانی کرد.

در این نسخه یک Static Method به کلاس Closure اضافه شده است که با استفاده از آن می‌توان یک Callable را مستقیما به یک Closure در همان Scope تبدیل کرد.

class Closure {
         ...
        public static function fromCallable(callable $callable) : Closure {...}
         ...
}

این تابع بررسی می‌کند که آیا Callable ورودی، در Scope فعلی قابل فراخوانی هست یا خیر. اگر بود closure ساخته شده را بر می‌گرداند؛ در غیر این صورت یک TypeError خروجی می‌دهد. به عنوان مثال تلاش برای ایجاد Closure به Private Method یک شی، اگر در خارج از scope شی باشد غیرممکن است ولی از درون شی موفقیت‌آمیز خواهد بود.

برای مثال تکه‌کد زیر مشکلی ندارد؛ چون از تابع fromCallable در درون شی استفاده شده است.

class Validator {

        public function getValidatorCallback($validationType) {
                if ($validationType == 'email') {
                        return Closure::fromCallable([$this, 'emailValidation']);
                }
                return Closure::fromCallable([$this, 'genericValidation']);
        }

        private function emailValidation($userData) {...}
        private function genericValidation($userData) {...}
}

$validator = new Validator();
$callback = $validator->getValidatorCallback('email');
$callback($userData);

- جایگزینی ()list با [ ]

با افزوده شدن این قابلیت بدون استفاده از تابع ()list می‌توان خانه‌های یک آرایه را بین چندین متغیر پخش کرد (شبیه همان چیزی که در بخش تغییرات نسخه ۷ گفته شد).

$array = [&quota&quot => 1, &quotb&quot => 2, &quotc&quot => 3];

// Assigns to $a, $b and $c the values of their respective array elements in $array with keys
numbered from zero
[$a, $b, $c] = $array;

// Assigns to $a, $b and $c the values of the array elements in $array with the keys &quota&quot, &quotb&quot and &quotc&quot, respectively
[&quota&quot => $a, &quotb&quot => $b, &quotc&quot => $c] = $array;

- اضافه شدن قابلیت تعیین سطوح دسترسی برای Constantها‌ی یک کلاس

class Token {
        // Constants default to public
        const  PUBLIC_CONST = 0;

        // Constants then also can have a defined visibility
        private const  PRIVATE_CONST = 0;
        protected const  PROTECTED_CONST = 0;
        public const  PUBLIC_CONST_TWO = 0;

        //Constants can only have one visibility declaration list
        private const  FOO = 1, BAR = 2;
}

- پشتیبانی از اندیس منفی در String

var_dump(&quotabcdef&quot[-2]); \\ string (1) &quote&quot
var_dump(strpos(&quotaabbcc&quot, &quotb&quot, -3)); \\ int(3)

- چندین نوع استثناء، یک بلوک Catch

تا قبل از این در شرایطی که چند نوع مختلف از Exceptionها مانند هم کنترل می شدند مجبور بودیم یک بلوک ثابت Catch را به ازای هر Exception تکرار کنیم؛ اما در این نسخه این مشکل حل شده است.

try {
        // Some code...
} catch (ExceptionType1 | ExceptionType2 $e) {
        // Code to handle the exception
} catch (\Exception $e) {
        // ...
}

تغییرات دنباله‌دار

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


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

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

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