با سلام، بنا به درخواست شما عزیزان قصد داریم به سراغ مبحث namespace ها در برنامه نویسی شیء گرای PHP برویم.
پیش نیاز: برای همراهی با این مقاله باید دانش قابل قبولی نسبت به برنامه نویسی شیء گرای PHP داشته باشید. اگر با برنامه نویسی شیء گرا آشنا نیستید می توانید از دوره آموزشی برنامه نویسی شیء گرای PHP استفاده کنید.
اولین سوالی که در این زمینه برای ما پیش می آید این است که namespace چیست و چرا باید از آن ها استفاده کنیم. اجازه بدهید سوالتان را جواب بدهم:
همانطور که در برنامه نویسی پیشرفت می کنید و کدهای خصوصی خودتان را می نویسید، این کدها را روی هم انباشته می کنید. در برخی اوقات نیز برخی از پروژه ها آنقدر بزرگ هستند که حجم کدها در آن ها بسیار بالا می رود. کار کردن در این حجم از کد از نقطه نظر خاصی، سخت تلقی می شود و آن، حفظ کردن تمام نام هایی است که روی توابع، کلاس ها و ... گذاشته اید.
همانطور که می دانید زمانی که نامی را روی یک کلاس یا تابع می گذارید دیگر نمی توانید آن نام را روی تابع یا کلاس دیگری قرار دهید؛ در صورتی که چنین کاری انجام دهید به خطا برخورد خواهید کرد.
باید اعتراف کرد که به یاد داشتن نام توابع، کلاس ها، ثابت ها و انواع کدهایی که نوشته ایم کار سختی است.
حال پلاگین ها و کتابخانه ها را نیز به این وضعیتِ در هم ریخته اضافه کنید! باید هم مراقب کدهای خود باشیم، هم مراقب کلاس ها، توابع و... رزرو شده در خودِ PHP و هم مراقب کدهایی که در یک کتابخانه یا پلاگین استفاده شده اند!
namespace ها در واقع روشی برای کپسوله سازی (encapsulation) هستند تا از این مشکل جلوگیری شود. گرچه این جمله ممکن است قلمبه و سلمبه جلوه کند اما ما هر روزه با مبحث namespace روبرو هستیم.
کامپیوتر یا تلفن همراه خود را در نظر بگیرید (هر سیستم عاملی که داشته باشد)؛ در این سیستم ها، directory هایی که داریم نقش namespace را بازی می کنند. همه می دانیم که فایل foo.txt را می توانیم در دو پوشه جداگانه داشته باشیم. به طور مثال:
C:\Users\Roxo\Downloads\Documents\foo.txt
C:\Users\Roxo\Downloads\Music\foo.txt
ما یک فایل با یک نام واحد را به طور همزمان در دو مسیر (directory) جداگانه داریم اما می دانیم که این دو فایل به دلیل یکسان بودن نامشان نمی توانند در یک مسیر باشند. اگر بخواهید فایلی را با نام file.txt در مسیری قرار بدهید که فایلی با نام file.txt دارد، فایل دوم فایل اول را جایگزین می کند. این یک پروسه عادی برای همه ماست و همه از آن مطلع هستیم.
در PHP نیز همینطور است و هنگامی که یک تابع با نام function داشته باشیم دیگر نمی توانیم تابعی با همین نام تعریف کنیم. وب سایت رسمی PHP می گوید:
In the PHP world, namespaces are designed to solve two problems that authors of libraries and applications encounter when creating re-usable code elements such as classes or functions:
- Name collisions between code you create, and internal PHP classes/functions/constants or third-party classes/functions/constants.
- Ability to alias (or shorten) Extra_Long_Names designed to alleviate the first problem, improving readability of source code.
ترجمه:
namespaces ها در دنیای PHP برای حل دو مشکل اساسی طراحی شده اند که نویسندگان برنامه ها و کتابخانه ها، هنگام ساخت کدهایی با قابلیت استفاده چند باره، با آن برخورد می کنند:
- تصادم نام ها (مشکل یکی بودن نام دو تابع، کلاس و ...) بین کدهایی که خودتان می نویسید و کلاس ها، توابع و ثابت های داخلیِ PHP یا کلاس ها، توابع و ثابت هایی که مربوط به کدهای خارجی (پلاگین، کتابخانه و ...) هستند.
- ایجاد قابلیت کوتاه تر کردنِ نام گذاری های بسیار طولانی؛ این نام گذاری ها معمولا برای این استفاده می شوند که از مشکل اول (تصادم نام ها) جلوگیری کنند و یا خوانایی سورس کد را بهبود ببخشند.
قبل از به وجود آمدن namespace ها راه حل خاصی وجود نداشت.
به طور مثال WordPress برای جلوگیری از این مشکل، به نام تمامی فایل ها حروف "_WP" را اضافه می کند. فریم ورک Zend نیز برای دور زدن این مشکل از ساختار خاص و بسیاری طولانی برای نام گذاری فایل هایش استفاده می کند که شبیه "Zend_Search_Lucene_Analysis_Analyzer_Common_Text_CaseInsensitive" است.
ما می توانیم ثابت ها، کلاس ها و توابع PHP را در namespace ها قرار دهیم.
تمامی ثابت ها، توابع و کلاس های PHP به صورت پیش فرض در فضای global قرار می گیرند. این همان فضایی است که قبل از ساخته شدن namespace ها وجود داشت و نام ها در آن قرار می گرفتند. برای تعریف یک namespace باید از دستور namespace
در بالای فایل PHP خود استفاده کنید. توجه کنید که این دستور باید اولین خط در فایل PHP شما باشد (البته به استثناء دستور declare
) و اجازه ندارید قبل از آن هیچ whitespace (فضای خالی مانند اسپیس یا اینتر) یا کدهایی به غیر از کد PHP قرار دهید. مثال از کد صحیح:
<?php // define this code in the MyProject namespace namespace MyProject;
هر کدی که پس از این کد نوشته شود مربوط به namespace ای به نام MyProject خواهد بود.
مثال از کد غلط:
<html> <?php namespace MyProject; // fatal error - namespace must be the first statement in the script ?>
در این کد قبل از کدهای PHP از کدِ HTML استفاده شده است که باعث بروز خطا می شود.
نکته: ما نمی توانیم برای یک گروه از کدها دو یا تعداد بیشتری namespace داشته باشیم. همچنین نمی توانیم به شیوه عادی از namespace ها به صورت "تو در تو" (nested) استفاده کنیم.
کد زیر در PHP غلط است:
<?php namespace my\stuff { namespace nested { class foo {} } } ?>
اگر بخواهید از namespace های تو در تو استفاده کنید باید آن را به شکل زیر پیاده کنید:
<?php namespace my\stuff\nested { class foo {} } ?>
در واقع این یک کد nest شده (تو در تو) نیست، بلکه یک نظام سلسله مراتبی از namespace ها است که در عمل همان کار nest کردن را انجام می دهد.
همچنین می توانید namespace های مختلفی را در یک فایل داشته باشید:
<?php namespace MyProject1; // PHP code for the MyProject1 namespace namespace MyProject2; // PHP code for the MyProject2 namespace // Alternative syntax namespace MyProject3 { // PHP code for the MyProject3 namespace } ?>
اما طبق گفته متخصصین و خود وب سایت PHP این کار اصلا استاندارد نبوده، توصیه نمی شود و تا حد امکان باید از آن دوری کرد. سعی کنید هر namespace را در یک فایل جداگانه داشته باشید.
به نظر من برای نمایش این دستورات بهتر است از یک مثال واقعی استفاده کنیم. ابتدا یک فایل به نام lib1.php می سازیم و در آن یک تابع، یک کلاس و یک ثابت تعریف می کنم. نام namespace آن را نیز App\Lib1 می گذاریم؛ App مخفف Application (به معنی برنامه) و Lib مخفف Library (به معنی کتابخانه) است. محتوای این فایل به شکل زیر است:
<?php // application library 1 namespace App\Lib1; const MYCONST = 'App\Lib1\MYCONST'; function MyFunction() { return __FUNCTION__; } class MyClass { static function WhoAmI() { return __METHOD__; } } ?>
می توانیم برای فراخوانی یا صدا زدن این کد به شکل زیر عمل کنیم:
فایلی به نام myapp.php می سازیم که محتوای زیر را داشته باشد:
<?php header('Content-type: text/plain'); require_once('lib1.php'); echo \App\Lib1\MYCONST . "\n"; echo \App\Lib1\MyFunction() . "\n"; echo \App\Lib1\MyClass::WhoAmI() . "\n"; ?>
ما هیچ namespace ای را در فایل myapp.php تعریف نکرده ایم بنابراین کدهای آن در فضای global قرار گرفته اند. از طرفی دیگر، اگر MYCONST یا ()MyFunction یا MyClass را مستقیما صدا بزنید با یک خطا مواجه خواهید شد. چرا؟ به دلیل اینکه آن ها در namespace ای به آدرس App\Lib1 هستند اما کدهای ما در فضای global قرار دارند.
برای حل این مشکل (همانطور که در کد بالا می بینید) می توانیم از آدرس کامل App\Lib1\ استفاده کنیم. اگر فایل myapp.php را اجرا کنیم خروجی آن به این شکل خواهد بود:
App\Lib1\MYCONST App\Lib1\MyFunction App\Lib1\MyClass::WhoAmI
سوال: مگر قرار نبود یکی از مزایای استفاده از namespace ها این باشد که دیگر مجبور به استفاده از نام های طولانی و غیره نشویم؟ با این پسوند های پشت سر هم، نام ها باز هم طولانی می شوند. این که شد همان آش و همان کاسه!
پاسخ: باید توجه کنید فایده اصلی namespace ها این بود که نیازی به حفظ کردن نام توابع، کلاس ها و ... نداشته باشیم و دور شدن از نام های طولانی تنها یکی از فایده های namespace ها است. در عین حال حرف شما کاملا صحیح است! برای حل این مشکل در جلسه بعد با دستور use
آشنا می شویم و اما قبل از آن باید چند اصطلاح را توضیح بدهم تا جلسه بعد مشکلی نداشته باشیم:
نام ها در namespace ها به این شکل هستند:
نام های Fully qualified برای یک بار صدا زدن چیزی کفاف می کنند اما اگر بخواهیم چیزهای زیادی را صدا بزنیم، استفاده از آن ها بسیار سخت می شود و اینجاست که دستور use
وارد می شود.
برای درک بهتر این موضوع، دو فایل جداگانه می سازیم و درونشان کدهای یکسانی قرار می دهیم. تنها فرق این دو فایل این است که namespace متفاوتی دارند.
فایل اول lib1.php
<?php // application library 1 namespace App\Lib1; const MYCONST = 'App\Lib1\MYCONST'; function MyFunction() { return __FUNCTION__; } class MyClass { static function WhoAmI() { return __METHOD__; } } ?>
فایل دوم lib2.php
<?php // application library 2 namespace App\Lib2; const MYCONST = 'App\Lib2\MYCONST'; function MyFunction() { return __FUNCTION__; } class MyClass { static function WhoAmI() { return __METHOD__; } } ?>
حال برای دنبال کردن ادامه بحث باید با مفاهیم Unqualified name (مانند ()MyFunction
) و Qualified name (مانند ()Lib1\
MyFunction
) و Fully qualified name مانند (()App\Lib2\MyFunction
\
) آشنا باشید.
حتما متوجه شده اید که این نام ها مبحث جدیدی نیستند و زمانی که برای فراخوانی namespace ها آن ها را صدا می زدیم از همین سیستم استفاده می کردیم اما آشنایی شما با آن ها برای ما مهم است.
حالا که فایل های lib2.php و lib1.php را تعریف کرده ایم می خواهیم کدی بنویسیم که تابع، ثابت و کلاس ما را فراخوانی کند. نام این فایل را myapp1.php می گذاریم:
<?php namespace App\Lib1; require_once('lib1.php'); require_once('lib2.php'); header('Content-type: text/plain'); echo MYCONST . "\n"; echo MyFunction() . "\n"; echo MyClass::WhoAmI() . "\n"; ?>
خروجی این کد به شکل زیر است:
App\Lib1\MYCONST App\Lib1\MyFunction App\Lib1\MyClass::WhoAmI
اگر به کدهای بالا توجه کنید، می بینید که ما هر دو فایل lib1.php و lib2.php را include کرده ایم اما ثابت ما (MYCONST)، تابع ما (MyFunction) و کلاس ما (MyClass) تنها از کدهای فایل lib1.php فراخوانی می شوند. می دانید چرا؟ سعی کنید چند لحظه فکر کنید تا خودتان به جواب برسید...
دلیل این است که کدهای lib1.php و کدهای myapp1.php دارای namespace یکسانی هستند و هر دو در AppLib1 قرار دارند اما AppLib2 دارای namespace کاملا جداگانه ای است.
برای اینکه مشکل کدهای قبل را حل کنیم می توانیم از دستور use
استفاده کنیم. این بار کدها را در فایلی به نام myapp2.php و به این شکل می نویسیم:
<?php use App\Lib2; require_once('lib1.php'); require_once('lib2.php'); header('Content-type: text/plain'); echo Lib2\MYCONST . "\n"; echo Lib2\MyFunction() . "\n"; echo Lib2\MyClass::WhoAmI() . "\n"; ?>
خروجی این کد به شکل زیر است:
App\Lib2\MYCONST App\Lib2\MyFunction App\Lib2\MyClass::WhoAmI
به کدهای بالا توجه کنید؛ ما AppLib2 را وارد فایل خود کرده ایم اما هنوز هم نمی توانیم مستقیما و بدون هیچ پیشوندی به MYCONST و MyFunction و MyClass اشاره کنیم. چرا؟
به دلیل اینکه کدهایی که نوشتیم (فایل myapp2.php) در فضای global قرار دارند و اگر هیچ پیشوند و نام کاملی به ثابت، تابع و کلاس خود ندهیم، PHP در فضای global به دنبال این موارد خواهد گشت. از کجا می دانیم کد ما در فضای global است؟ اگر به کدها نگاهی بیندازید (فایل myapp2.php) متوجه می شوید که برای این فایل هیچ namespace ای تعریف نکرده ایم، بلکه به آن اجازه داده ایم از namespace ای به نام App\Lib2 استفاده کند.
همانطور که پیش تر گفتیم زمانی که برای فایلی namespace تعریف نکنید، آن فایل در فضای global قرار می گیرد اما پیشوند Lib2 به فایل ما می گوید در فضای global دنبال این تابع، ثابت و کلاس نگرد بلکه فلان namespace را جست و جو کن.
کلمه alias در لغت به معنی «نام مستعار» است یا هر اسمی که واقعی نباشد اما به چیزی واقعی اشاره کند.
به طور مثال تخلص شعرا یک نوع alias محسوب می شود چرا که اسم واقعی شان نیست اما به خودشان اشاره دارد. مثلا خواجه شمسالدین محمد تخلص خود را حافظ گذاشته است و ما همه می دانیم منظور از حافظ چه کسی است.
این کلمه در مبحث namespace ها معنی آن چنان متفاوتی ندارد! alias به ما اجازه می دهند نام های مستعار و کوتاه تری انتخاب کنیم تا راحت تر کدنویسی کنیم. مثال خود را در فایل به نام myapp3.php می نویسیم:
<?php use App\Lib1 as L; use App\Lib2\MyClass as Obj; header('Content-type: text/plain'); require_once('lib1.php'); require_once('lib2.php'); echo L\MYCONST . "\n"; echo L\MyFunction() . "\n"; echo L\MyClass::WhoAmI() . "\n"; echo Obj::WhoAmI() . "\n"; ?>
خروجی این کد به شکل زیر خواهد بود:
App\Lib1\MYCONST App\Lib1\MyFunction App\Lib1\MyClass::WhoAmI App\Lib2\MyClass::WhoAmI
همانطور که متوجه شده اید این کار را با کلمه کلیدی as
(به معنی "به عنوان") انجام می دهیم. در کد بالا اولین دستورِ use
می گوید:
از این به بعد دوست داریم به جای AppLib1
بگوییم L
و هر نام qualified ای که از حرف L استفاده کند، در زمان کامپایل شدن تبدیل به AppLib1
خواهد شد.
بدین صورت می توانیم به جای استفاده از نام های کامل مانند Lib2\MyFunction از نام های کوتاه تری مانند L\MyFunction استفاده کنیم.
دومین دستور use
جالب تر است. این خط از کد می گوید به جای کلاسی که در AppLib2 تعریف کرده ایم (MyClass) میخواهیم بگوییم Obj
. به همین دلیل می توانید از دستوری مانند ()new Obj
استفاده کنید. البته این قابلیت تنها مخصوص کلاس ها است و مثلا برای توابع باید از ورژن PHP 5.6 به بالا و از روش دیگری استفاده کنید:
use function My\Full\functionName as func;
نکته: اگر بخواهیم چند namespace را وارد یک فایل کنیم، دو راه داریم:
زمانی که کد شما کامپایل می شود بسیاری از نام هایی که ما به صورت قرار دادی تعیین کرده ایم (مثل alias ها) تبدیل به نام های اصلی می شوند، به این فرآیند Name resolution می گوییم.
برای درک کردن این بحث باید با مفاهیم Unqualified name (مانند ()MyFunction
) و Qualified name (مانند ()Lib1\
MyFunction
) و Fully qualified name مانند (()App\Lib2\MyFunction
\
) آشنا باشید.
اگر با این موارد آشنایی ندارید لطفا دوباره به قسمت تعریف namespace ها در PHP در همین مقاله مراجعه کنید.
البته یک نوع نامگذاری دیگر نیز وجود دارد که به آن Relative name می گوییم؛ در این نام گذاری namespace نیز ذکر می شود. مثال: namespace\Foo\Bar
چند مورد از قوانین اصلی name resolution به این شرح هستند:
as
به C تبدیل کرده باشیم، فراخوانی C\D\E به A\B\C\D\E تبدیل می شود؛ یعنی به نام کامل و صحیحش برمیگردد.سعی می کنم این قوانین را که بیشتر از دو مورد بالا هستند در یک مثال توضیح دهم تا مطلب کاملا مفهوم شود. من تنها قوانین مهم و کاربردی را ذکر می کنم. می توانید دیگر قوانین را در صفحه رسمی مربوطه در سایت PHP ببینید.
تصور کنید در ابتدای فایل ما این کد وجود دارد:
<?php namespace A; use B\D, C\E as F;
با توجه به کد بالا صدا زدن توابع به این شکل اجرا می شود:
foo(); \foo(); my\foo(); F();
برای تابع ;()foo
برای تابع ;()foo\
سعی می کند این تابع را در فضای گلوبال پیدا کند.
برای تابع ;()my\foo
سعی می کند تابع foo را در namespace به آدرس A\my پیدا کند.
برای تابع ;()F
این دستور همیشه نام namespace فعلی را به صورت یک رشته برمیگرداند:
<?php namespace App\Lib1; echo __NAMESPACE__; // outputs: App\Lib1 ?>
همانطور که در خود کد توضیح دادیم عبارت "App\Lib1" را به ما برمیگرداند.
نکته: اگر کدهای ما در فضای global باشند، یک رشته خالی به ما برگردانده می شود.
جدا از مزایایی که این دستور در هنگام دیباگ کردن سیستم به ما می دهد، می توان از آن برای تولید نام برای کلاس ها به صورت fully qualified و پویا استفاده کرد:
<?php namespace App\Lib1; class MyClass { public function WhoAmI() { return __METHOD__; } } $c = __NAMESPACE__ . '\\MyClass'; $m = new $c; echo $m->WhoAmI(); // outputs: App\Lib1\MyClass::WhoAmI ?>
در اینجا منظور ما مفهوم namespace (به معنی فضای نام) نیست بلکه منظورمان یک دستور برنامه نویسی به نام namespace
است! این دو را اشتباه نگیرید!
می توانیم از دستور namespace
برای اشاره مستقیم به یک namespace یا namespace های زیرین (nested شده ها و ...) استفاده کنیم، بنابراین این دستور معادل همان دستور self
در کلاس ها است.
اگر از دستور self
در کلاس ها چیزی به خاطر ندارید به مقاله زیر مراجعه کنید:
مثال:
<?php namespace App\Lib1; class MyClass { public function WhoAmI() { return __METHOD__; } } $m = new namespace\MyClass; echo $m->WhoAmI(); // outputs: App\Lib1\MyClass::WhoAmI ?>
نکته: در خط دوم از کد بالا با کلمه namespace مواجه می شویم. این کلمه در اینجا برای تعریف کردن namespace ها است (همان چیزی که قبلا خوانده ایم). اما در خط های پایانی با دستور "new namespace\MyClass" مواجه می شویم. این همان دستوری است که به namespace فعلی اشاره دارد؛ اگر دقت کنید خروجی این کد نیز App\Lib1 را به ما برگردانده است.
کلمه Autoload به معنی «بارگذاری خودکار» است. این ویژگی زبان PHP هیچ اختصاصی به namespace ها و غیر آن ندارد بلکه یک قابلیت همگانی در این زبان است. در حالت عادی و فضای global، توابع autoload به این شکل نوشته می شوند:
<?php $obj1 = new MyClass1(); // classes/MyClass1.php is auto-loaded $obj2 = new MyClass2(); // classes/MyClass2.php is auto-loaded // autoload function function __autoload($class_name) { require_once("classes/$class_name.php"); } ?>
اما از PHP 5.3 به بعد شما می توانید یک نمونه از یک کلاس namespace شده را بسازید. در چنین حالتی نام namespace و کلاس، به صورت fully qualified، به تابع autoload__ تحویل داده می شود.
به طور مثال، در کد بالا مقدار class_name$ می تواند App\Lib1\MyClass باشد. شما می توانید تمام فایل های کلاس هایتان را در یک فولدر قرار دهید و namespace را از رشته برگردانده شده حذف کنید اما این کار توصیه نمی شود چرا که ممکن است باعث تصادم نام فایل ها شود (فایل هایی با نام یکسان و در یک محل).
از طرفی نظام سلسه مراتبی در فایل های کلاس های شما، می توانند دارای ساختاری مشابه با ساختار namespace ها باشند؛ به طور مثال میتوانید فایل MyClass.php را در آدرس classes/App/Lib1/ قرار دهید. به مثال زیر توجه کنید:
محتویات classes\App\Lib1\MyClass.php\ به این شکل است:
<?php namespace App\Lib1; class MyClass { public function WhoAmI() { return __METHOD__; } } ?>
حال، فایلی که در مسیر root (مسیر اصلی و ریشه ای) قرار دارد می تواند از کد زیر استفاده کند:
محتویات فایل myapp.php
<?php use App\Lib1\MyClass as MC; $obj = new MC(); echo $obj->WhoAmI(); // autoload function function __autoload($class) { // به آدرس کامل فایل namespace تبدیل $class = 'classes\' . str_replace('\\', '/', $class) . '.php'; require_once($class); } ?>
نکات این کد:
()new MC
در هنگام کامپایل شدن کد، تبدیل به ()new App\Lib1\MyClass
می شود.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.
در این قسمت، به پرسشهای تخصصی شما دربارهی محتوای مقاله پاسخ داده نمیشود. سوالات خود را اینجا بپرسید.