در مبحث شیئگرایی در زبان پیاچپی گاهی نیاز داریم تا برخی پراپرتیها و متدها را به گونهای طراحی کنیم که بدون نیاز به ساخت آبجکت از روی یک کلاس بهبخصوص، بتوان آنها را فراخوانی کرد که به این دست پراپرتیها و متدها اصطلاحاً استاتیک گفته میشود و در این آموزش قصد داریم این مفهوم را به طور مفصل مورد بررسی قرار دهیم.
به خاطر داشته باشید برخی توسعهدهندگان بر این باورند که استفاده از پراپرتیها و متدهای استاتیک اصلاً روش خوبی نیست و تا حد ممکن از انجام این کار میباید جلوگیری کرد.برای همین منظور، داخل پوشهٔ oop پروژهای تحت عنوان static-keyword ایجاد کرده و با مد نظر قرار دادن ساختاری که این دورهٔ آموزشی مورد استفاده قرار دادهایم، شروع به تکمیل این پروژه میکنیم. برای شروع، داخل فولدر classes فایلی به نام User.php ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class User
{
public static $usersNo = 0;
}
داخل بدنهٔ کلاس User یک پراپرتی تعریف کردهایم تحت عنوان usersNo$ و مقدار اولیهٔ ۰ را برای آن در نظر گرفتهایم. آنچه در ارتباط با این پراپرتی نسبت به پراپرتیهایی که تا این مرحله از دوره مورد استفاده قرار دادهایم متفاوت است، بهکارگیریِ کلیدواژهٔ static قبل از نام پراپرتی است. در ادامه، وارد فایل index.php شده و آن را به صورت زیر تکمیل میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
echo SokanAcademy\User::$usersNo;
همانطور که میبینیم، به منظور فراخوانی پراپرتیهایی که از جنس static هستند، میباید نام کلاس را نوشته سپس علائم :: را قرار داد و نام پراپرتی با در نظر گرفتن علامت $ را وارد کرد.
اگر بخواهیم کدهای فوق را به شکل بهتری ریفکتور کنیم نیز خواهیم داشت:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\User;
echo User::$usersNo;
میبینیم که ابتدا کلاس User به همراه نِیماِسپیس آن را به اصطلاح use کرده سپس در حین استفاده از این کلاس فقط و فقط نام آن را درج نمودهایم. حال مجدد به کلاس User بازگشته و آن را به صورت زیر تکمیلتر میکنیم:
<?php
namespace SokanAcademy;
class User
{
public static $usersNo = 0;
public static function addToUsersNo()
{
self::$usersNo++;
}
}
متدی نوشتهایم به نام ()addToUsersNo و داخل بدنهٔ این متد به منظور فراخوانی تنها پراپرتی موجود در این کلاس، دستور self را نوشته سپس علائم :: را قرار داده و در نهایت نام کامل پراپرتی userNo$ با در نظر گرفتن علامت $ را نوشتهایم سپس با درج علائم ++ مقدار موجود در پارامتر no$ را یک واحد افزودهایم.
به منظور آشنایی با نحوهٔ فراخوانی این متد، مجدد به فایل index.php بازگشته و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\User;
echo User::$usersNo;
echo "\n";
User::addToUsersNo();
echo User::$usersNo;
echo "\n";
به عنوان خروجی هم خواهیم داشت:
/var/www/oop/static-keyword$ php index.php
0
1
همانطور که میبینیم، همچون روشی که به منظور فراخوانی یک پراپرتی از جنس static از علائم :: استفاده نمودیم، برای فراخوانی متد ()addToUsersNo نیز پس از درج نام کلاس User علائم :: را نوشته سپس متد مذکور را به همراه آرگومانی از جنس عدد صحیح وارد کردهایم.
نکتهٔ مهمی که در ارتباط با کیورد static وجود دارد آن است که داخل متدهای استاتیک هرگز نمیتوان به پراپرتیهای غیراستاتیک دست یافت. برای روشنتر شدن این موضوع، کلاس User را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class User
{
public static $usersNo = 0;
public $someProperty = "Some Property";
public static function addToUsersNo()
{
self::$usersNo++;
}
public static function doSomething()
{
return $this->someProperty;
}
}
همانطور که میبینیم، یک پراپرتی معمولی تحت عنوان someProperty$ ساخته سپس متدی از جنس static ساختهایم به نام ()doSomething و خواستهایم تا مقدار پراپرتی جدیدی که ساختهایم را داخل آن ریترن کنیم. جهت تست، وارد فایل index.php شده و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\User;
// echo User::$usersNo;
// echo "\n";
// User::addToUsersNo();
// echo User::$usersNo;
// echo "\n";
User::doSomething();
پس از کامنت کردن کدهای قبلی، متد ()doSomething را فراخوانی کردهایم اما در خروجی خواهیم داشت:
/var/www/oop/static-keyword$ php index.php
PHP Fatal error: Uncaught Error: Using $this when not in object context in /var/www/oop/static-keyword/classes/User.php:16
همانطور که در متن ارور ملاحظه میکنیم، گفته شده که امکان دسترسی به دستور this$ را نداریم و این مشکل از آنجا ناشی میشود که گفتیم متدهای static بدون نیاز به ساخت آبجکت میتوانند فراخوانی شوند پس از همین روی ما با هیچ گونه آبجکتی سروکار نداریم و به همین دلیل هم میباشد که به دستور this$ نیز دسترسی نداریم.
اساساً یکی از کاربردهای پراپرتیهای static آن است از آنها به عنوان شمارنده (Counter) استفاده نماییم زیرا پراپرتیهایی از این دست توانایی آن را دارند تا آخرین مقداری که به آنها اختصاص یافته را در خود ذخیره سازند. برای درک بهتر این موضوع، کدهای داخل فایل index.php را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\User;
echo User::$usersNo;
echo "\n";
User::addToUsersNo();
User::addToUsersNo();
User::addToUsersNo();
User::addToUsersNo();
User::addToUsersNo();
User::addToUsersNo();
User::addToUsersNo();
echo User::$usersNo;
echo "\n";
همانطور که میبینیم، ابتدا یک بار مقدار اولیهٔ پراپرتی usersNo$ را چاپ کردهایم که برابر با ۰ است سپس چند بار پشت سر هم متد ()addToUsersNo را فراخوانی کردهایم که وظیفهٔ افزودن یک واحد به مقدار قبلی پراپرتی مذکور را دارا است و در نهایت هم مجدد مقدار این پراپرتی را چاپ کردهایم به طوری که در خروجی خواهیم داشت:
0
7
همچنین توجه داشته باشیم که متدهای استاتیک را میتوان در داخل سایر متدها نیز فراخوانی نمود؛ به طور مثال، کلاس User را به صورت زیر میتوانیم تغییر دهیم:
<?php
namespace SokanAcademy;
class User
{
public static $usersNo = 0;
public $someProperty = "Some Property";
public static function addToUsersNo()
{
self::$usersNo++;
}
public static function doSomething()
{
return $this->someProperty;
}
public function runAddToUsersNoMethod()
{
self::addToUsersNo();
}
}
همانطور که میبینیم، متدی معمولی ساختهایم تحت عنوان ()runAddToUsersNoMethod که داخلش به همان روشی که پراپرتیهای استاتیک را فراخوانی میکردیم، با استفاده از علائم :: متد استاتیک ()addToUsersNo را کال کردهایم. حال به فایل index.php مراجعه کرده و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\User;
$user = new User();
$user->runAddToUsersNoMethod();
echo User::$usersNo;
ابتدا آبجکتی از روی کلاس User ساخته سپس با استفاده از آن متد ()runAddToUsersNoMethod را فراخوانی کردهایم که داخل این متد، متد استاتیک ()addToUsersNo فراخوانی شده است. سپس در خط هشتم پراپرتی usersNo$ را چاپ کردهایم.
در بحث وراثت نیز کلاس فرزند میتواند به پراپرتیها و متدهای استاتیک کلاس والد دست یابد. برای روشنتر شدن این موضوع، داخل پوشهٔ classes فایلی به نام Author.php ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class Author extends User
{
public function getStaticUserNoProperty()
{
return User::$usersNo;
}
public function runStaticAddToUsersNoMethod()
{
return User::addToUsersNo();
}
}
کلاسی ساختهایم تحت عنوان Author که از کلاس User ارثبری میکند و داخل این کلاس دو متد نوشتهایم به طوری که متد اول تحت عنوان ()getStaticUserNoProperty وظیفهٔ فراخوانی پراپرتی استاتیک usersNo$ را دارا است که داخل کلاس والد User تعریف شده است و داخل متد ()runStaticAddToUsersNoMethod نیز خروجی متد استاتیک ()addToUsersNo ریترن شده است. حال وارد فایل index.php شده و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\Author;
$author = new Author();
echo $author->getStaticUserNoProperty();
$author->runAddToUsersNoMethod();
echo $author->getStaticUserNoProperty();
ابتدا به ساکن یک آبجکت از روی کلاس Author تحت عنوان author$ ساختهایم سپس متد ()getStaticUserNoProperty را فراخوانی کردهایم به طوری که این متد مقدار اولیهٔ پراپرتی استاتیک usersNo$ که برابر با ۰ است را ریترن میکند. سپس متد ()runAddToUsersNoMethod را کال کردهایم که داخلش متد استاتیک ()addToUsersNo فراخوانی شده که این وظیفه را دارا است تا یک واحد به مقدار پراپرتی usersNo$ بیفزاید و در نهایت مجدد متد ()getStaticUserNoProperty را فراخوانی کردهایم به طوری که در خروجی خواهیم داشت:
01
در واقع، ابتدا یک بار مقدار پراپرتی usersNo$ که به صورت پیشفرض ۰ است چاپ شده سپس از طریق متد ()runStaticAddToUsersNoMethod یک واحد به آن افزوده شده و در نهایت مجدد مقدار پراپرتی مذکور چاپ شده است. در عین حال توجه داشته باشیم که کلاس Author را به صورت زیر نیز میتوان نوشت:
<?php
namespace SokanAcademy;
class Author extends User
{
public function getStaticUserNoProperty()
{
return parent::$usersNo;
}
public function runStaticAddToUsersNoMethod()
{
return parent::addToUsersNo();
}
}
همانطور که ملاحظه میشود، کلاس User با کیورد parent جایگزین شده است. در حقیقت، با توجه به این که کلاس User به عنوان والدِ کلاس Author قملداد میگردد، از همین روی کلیدواژهٔ parent برای همین منظور در هستهٔ زبان پیاچپی گنجانده شده تا به جای درج نام کلاس والد، از این کیورد استفاده نمود.
یکی دیگر از کاربردهای متدهای static در پروسهٔ ساخت کلاسهایی است که اصطلاحاً Utility یا Helper گفته میشوند. به عبارت بهتر، این دست کلاسها مصارفی همچون تبدیل واحد، نمایش تاریخ، ریدایرکت کردن کاربر، اعتبارسنجی دادهها و ... دارند که برای درک بهتر این موضوع، داخل پوشهٔ classes فایل جدیدی به نام Utility.php ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class Utility
{
public static function redirect(string $url)
{
header("Location: $url");
exit;
}
}
همانطور که میبینیم، متدی تحت عنوان ()redirect از جنس static ساخته که یک پارامتر ورودی تحت عنوان url$ میگیرد که تایپ هینت آن string است؛ به عبارتی، فقط و فقط دیتایی از جنس استرینگ میتوان به این متد پاس داد. سپس داخل این متد از فانکشن ()header زبان پیاچپی استفاده نمودهایم که این وظیفه را دارا است تا یک هِدِر سِت کند و همانطور که میبینیم هِدِر Location را در نظر گرفتهایم که وظیفهٔ ریدایرکت کردن را دارد و به عنوان مقدار آن هم پارامتر ورودی url$ را در نظر گرفتهایم و در نهایت هم به منظور پایان دادن به این اسکریپت، از دستور exit استفاده کردهایم. حال وارد فایل index.php شده و آن را به صورت زیر تغییر میدهیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
SokanAcademy\Utility::redirect('https://sokanacademy.com');
پس از فراخوانی متد ()redirect از کلاس Utility و اختصاص یک لینک، میبینیم که به محض اجرای این فایل در مرورگر، به وبسایت سکان آکادمی ریدایرکت خواهیم شد.
حال برای آن که با کاربردهای متدهای استاتیک در یک به اصطلاح Use Case واقعیتر آشنا شویم، در ادامهٔ این آموزش قصد داریم تا ببینیم به چه شکل میتوان یک کلاس مدیریت سِشِن با استفاده از متدهای استاتیک ساخت.
آموزش ساخت یک کلاس مدیریت Session
اساساً برای آن که بتوانیم کاربری که در سیستم لاگین کرده را رصد کنیم، نیاز به استفاده از مفهومی تحت عنوان Session داریم. در همین راستا، در این قسمت از آموزش قصد داریم کلاسی بسازیم که با استفاده از متدهای استاتیک قابلیت ذخیرهٔ دیتا در سِشِن را برایمان فراهم میکند. برای همین منظور، داخل پوشهٔ classes فایلی به نام Session.php ساخته و آن را به صورت زیر تکمیل میکنیم:
<?php
namespace SokanAcademy;
class Session
{
private static $sessionIsStarted = false;
public static function init()
{
if (self::$sessionIsStarted == false) {
session_start();
self::$sessionIsStarted = true;
}
}
public static function set(string $name, $value)
{
$_SESSION[$name] = $value;
}
public static function get($name)
{
return @$_SESSION[$name];
}
public static function destroyByName($name)
{
unset($_SESSION[$name]);
}
public static function destroy()
{
if (self::$sessionIsStarted == true) {
// destroy the Session, not just the data stored!
session_destroy();
// delete the session contents, but keep the session_id and name:
session_unset();
}
}
}
این کلاس تنها یک پراپرتی از جنس static دارد که نامی همچون sessionIsStarted$ برایش در نظر گرفته و مقدار پیشفرض false را نیز درج نمودهایم. در ادامه، پنج متد از جنس static نوشتهایم که به ترتیب برای شروع سِشِن، سِت کردن مقداری در سِشِن، فراخوانی آن مقدار، از بین بردن یک سِشِن خاص و در نهایت از بین کردن کل سِشنها مورد استفاده قرار خواهند گرفت.
داخل متد ()init با استفاده از یک دستور شرطی چک کردهایم ببینیم که آیا مقدار پراپرتی sessionIsStarted$ برابر با false است یا خیر که اگر این گونه بود، وارد بلوک if شده و فانکشن ()session_start را فراخوانی کرده سپس مقدار پراپرتی فوقالذکر را برابر با true قرار میدهیم تا دفعهٔ بعد که این متد اجرا شد، فانکشن ()session_start مجدداً کال نشود (در صورت فراخوانی مجدد ()session_start با یک هشدار از طرف مفسر پیاچپی روبهرو خواهیم شد.)
متد ()set این وظیفه را دارا است تا کلیدی را به همراه مقدارش در فضای سِشِن ذخیره سازد و برای همین منظور دو پارامتر ورودی تحت عناوین name$ و value$ میگیرد که از طریق Type Hinting دستور دادهایم که مقدار پراپرتی name$ حتماً میباید استرینگ باشد. سپس از سوپرگلوبال SESSION_$ استفاده کرده و به عنوان کلید آن از پراپرتی name$ استفاده کرده و مقدارش را نیز برابر با value$ قرار دادهایم.
متد ()get این وظیفه را دارا است تا سِشِن متناظر با پارامتر name$ این متد را ریترن کند. همانطور که ملاحظه میشود، قبل از سوپرگلوبال SESSION_$ از علامت @ استفاده کردهایم بدین دلیل که اگر متد ()get با کلیدی فراخوانی شد که پیش از این سِت نشده بود، جلوی نمایش هشدار مفسر پیاچپی گرفته شود.
متد ()destroyByName این وظیفه را دارا است تا کلیدی که متناظر با پارامتر ورودی name$ باشد را حذف کند که این کار از طریق فراخوانی متد ()unset و پاس دادن سوپرگلوبال SESSION_$ به همراه کلید مد نظر عملی میگردد.
در نهایت هم به متد ()destroy میرسیم که این وظیفه را دارا است تا اگر مقدار پراپرتی sessionIsStarted$ برابر با true بود (یا به عبارتی، پیش از این متد ()init حداقل یک بار فراخوانی شده بود.) با استفاده از دستورات ()session_destroy و ()session_unset کلیهٔ سِشِنها را از بین ببرد.
حال برای این که عملکرد این کلاس مدیریت سِشِن را تست کنیم، وارد فایل index.php شده و تغییرات زیر را داخل آن اِعمال میکنیم:
<?php
ini_set('display_errors', '1');
require_once 'vendor/autoload.php';
use SokanAcademy\Session;
Session::init();
Session::set('username', 'admin123456');
echo Session::get('username');
Session::destroyByName('username');
echo Session::get('username');
ابتدا اسکریپت فوق را در محیط کامندلاین اجرا کرده سپس به تفسیر کدها خواهیم پرداخت:
/var/www/oop/static-keyword$ php index.php
admin123456
در خط ششم متد ()init را فراخوانی کردهایم چرا که برای استفاده از سِشِن پیاچپی حتماً میباید فانکشن ()session_start فراخوانی گردد و در ادامه با استفاده از متد ()set یک جفت Key/Value در نظر گرفتهایم و در خط هشتم هم با استفاده از متد ()get مقدار آن را چاپ کردهایم. در خط نهم با استفاده از متد ()destroyByName کلیدی تحت عنوان username که پیش از این در فضای سِشِن ذخیره کرده بودیم را حذف کردهایم به طوری که وقتی در خط بعد مجدد متد ()get را فراخوانی کردهایم، چیزی چاپ نشده است چرا که اساساً کلیدی تحت عنوان username دیگر وجود خارجی ندارد.
جمعبندی
در این آموزش با یک سری اصطلاحاً Use Case برای کلیدواژهٔ static در زبان پیاچپی آشنا شده و دیدیم که با استفاده از این دستور به چه شکل میتوانیم پراپرتیها و متدهایی بسازیم که بدون نیاز به ساخت آبجکت از روی کلاس مذکور، امکان فراخوانی آنها را داشته باشیم.
