در مبحث شیئگرایی در زبان پیاچپی گاهی نیاز داریم تا برخی پراپرتیها و متدها را به گونهای طراحی کنیم که بدون نیاز به ساخت آبجکت از روی یک کلاس بهبخصوص، بتوان آنها را فراخوانی کرد که به این دست پراپرتیها و متدها اصطلاحاً استاتیک گفته میشود و در این آموزش قصد داریم این مفهوم را به طور مفصل مورد بررسی قرار دهیم.
به خاطر داشته باشید برخی توسعهدهندگان بر این باورند که استفاده از پراپرتیها و متدهای استاتیک اصلاً روش خوبی نیست و تا حد ممکن از انجام این کار میباید جلوگیری کرد.برای همین منظور، داخل پوشهٔ 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
در زبان پیاچپی آشنا شده و دیدیم که با استفاده از این دستور به چه شکل میتوانیم پراپرتیها و متدهایی بسازیم که بدون نیاز به ساخت آبجکت از روی کلاس مذکور، امکان فراخوانی آنها را داشته باشیم.