سلام.
در این مقاله میخوام قوانین ساخت یک autoloader استاندارد رو بر اساس PSR-4 توضیح بدم.
سعی شده، مطالب بصورت ساده و روان بهمراه نکات و مثال هایی روی دیگر پروژه های استاندارد گفته بشه.در مورد namespace ها هم نکاتی آورده شده تا درک مناسب و بهتری در این موضوع پیدا کنید.
1. قوانین این لیست برای class
ها ، interface
ها ، trait
ها و دیگر ساختارهای مشابه قابل پیاده سازی است و هر جا کلمه ی class
یا کلاس اومد اشاره به همین ها دارد.
2. یک namespace
کامل به شکل زیر ذکر میشود:
\<NamespaceName>(\<SubNamespaceNames>)*\<ClassName>
++ بک اسلش (\
) ابتدایی، به Global Space
اشاره میکند. اگر خواستید کلاسی رو که در یک space دیگر قرار دارد داخل یک namespace بیاورید میبایست ابتدای آن یک بک اسلش(\
) بگذاریم:
<?php
namespace A\B\C;
$dt = new DateTime();
echo $dt->getTimestamp() . PHP_EOL;
در کد بالا یک Fatal Error
رخ میدهد:
Fatal error: Uncaught Error: Class 'A\B\C\DateTime' not found
علتش اینه که ما در فضای A\B\C
هستیم و در این فضا دنبال کلاس DateTime میگرده. در صورتی که این تابع جزو توابع خود PHP هست و در فضای global قرار دارد. پس یک \
اضافه میکنیم تا بگیم از global space
دنبال کلاس DateTime بگرده:
<?php
namespace A\B\C;
$dt = new \DateTime();
echo $dt->getTimestamp() . PHP_EOL;
// output: 1591927525
در غیر اینصورت نیازی به آوردنش نیست. اگر با استفاده از use
کلاس را import میکنید هم فرقی نمیکند بیاورید یا نه.
با توجه به توضیحات، این مثال رو میتونم به دو شکل بنویسم و صحیح است:
<?php
namespace A\B\C;
$a = (new \classes\base\Hello)->sayHelloWorld();
یا بوسیله ی use
کلاس رو import کنم:
namespace A\B\C;
use classes\base\Hello;
$a = (new Hello)->sayHelloWorld();
تذکر: به یک نکته توجه کنید، سینتکس تعریف namespace میبایست بعنوان اولین دستور در فایل PHP نوشته شود و قبل از تگ php هم هیچ کاراکتر و whitespace
ای نباید در خروجی قرار بگیره وگرنه خطای زیر تولید میشه:
[whitespace]
<?php
namespace A\B\C;
Fatal error: Namespace declaration statement has to be the very first statement or after any declare call in the script in ...
UTF-8 with BOM
نباشه وگرنه باز هم این خطا نمایش داده میشه.(بخاطر وجود byte order mark در ابتدای فایل)++ namespace
ها میتونن چندین level تو در تو داشته باشن ولی اگر هیچ sub-level ای در نظر نگرفتید، حداقل باید یک top-level
داشته باشند تا یک namespace در فضای global قرار نگیرد. برای مثال classes\Hello.php
در اینجا classes یک top-level محسوب میشود.
++ پس با توجه به جمله ی قبلی، SubNamespace
ها میتونه یک یا چنتا بیاد. classes\base\App.php
++ ساختار namespace با اسم کلاس خاتمه پیدا میکنه. App.php
++ در این ساختار Underscore
ها(_
) هیچ معنی خاصی ندارن.
++ حروف الفبا میتونه ترکیبی از حروف کوچک و بزرگ باشد.
++ نام کلاس باید به حروف کوچک و بزرگ حساس باشد. یعنی در ساختار تابع autoload
نام کلاس رو همونطوری که هست بگیرید و در تابع strtolower
نگذارید.
3. بعد از رعایت قوانین گفته شده در ادامه، نکات مهمی از PSR-4
رو میخوام توضیح بدم تا انتها بهمراه مثال هاش بخونید که متوجه بشید، موضوع چیه:
++ برای جلوگیری از دایرکتوری های تو در تو (اصطلاحاً nested directories
) ترجیحاً میتونید بخشی از namespace رو بعنوان یک namespace prefix
در نظر بگیرید که این prefix به مسیری اشاره میکند که فایل های پروژه داخلش قرار گرفتن(base directory
)
پروژه میتونه prefix های مختلفی بهمراه مسیرهای متفاوتی باشه.
برای اینکه این مطلب بهتر جابیوفته، نکته ی بعدی رو هم بیارم بعد مثال میزنم.
++ اسامی هر sub-namespace
که بعد از prefix
اومده مطابق با یک sub directory
درون base directory
خواهد بود که این sub directory
باید همنام(از نظر کوچک و بزرگی هم مطابقت داشته باشد) با اسامی sub-namespace
ها باشد. ضمناً جداکننده namespace (یعنی \
بک اسلش ها) در مسیردهی آدرس فایل روی سیستم با ثابت DIRECTORY_SEPARATOR باید replace شوند. (مسیر دایرکتوری ها در لینوکس با /
از هم جدا میشوند.)
++ نام کلاسی که در انتهای namespace آمده است باید با یک فایل با پسوند .php
هم نام باشد.(از نظر کوچک و بزرگی هم مطابقت داشته باشد)
چنتا مثال از خود داک و جاهای دیگه میارم.
داک PHPFig:
FULLY QUALIFIED CLASS NAME NAMESPACE PREFIX BASE DIRECTORY RESULTING FILE PATH
\Acme\Log\Writer\File_Writer Acme\Log\Writer ./acme-log-writer/lib/ ./acme-log-writer/lib/File_Writer.php
\Aura\Web\Response\Status Aura\Web /path/to/aura-web/src/ /path/to/aura-web/src/Response/Status.php
\Symfony\Core\Request Symfony\Core ./vendor/Symfony/Core/ ./vendor/Symfony/Core/Request.php
\Zend\Acl Zend /usr/includes/Zend/ /usr/includes/Zend/Acl.php
فریمورک Yii:
برای مثال در این فریمورک namespace ای با عنوان yii\base\Application
یا yii\web\Application
یا yii\BaseYii
و ... وجود دارد. این فریمورک طبق این مواردی که گفتیم، path رو در یک آرایه به اسم aliases نگه میداره. در این namespace هایی که گذاشتم top-level یعنی yii رو در آرایه ای با نام aliases مسیردهی میکنه به این شکل که ایندکسش رو @yii
قرار داده و مقدارش آدرسی مطابق با مسیر دایرکتوری yii2 در نظر گرفته میشه path/to/project_directory/vendor/yiisoft/yii2
ست میشه.
بنابراین اگر بنویسیم:
* use yii\base\Application
منظورمان فایل Application.php
مثلا در همچین مسیری است:
/var/www/html/yiiblog/vendor/yiisoft/yii2/base/Application.php
* use yii\web\Application
:
/var/www/html/yiiblog/vendor/yiisoft/yii2/web/Application.php
* use yii\BaseYii
:
/var/www/html/yiiblog/vendor/yiisoft/yii2/BaseYii.php
البته در فریمورک Yii، مسیر کلاس های داخل فریمورک در آرایه ی classMap تعریف شده. بخشی ازین آرایه رو ببینید:
return [
'yii\base\Action' => YII2_PATH . '/base/Action.php',
'yii\base\ActionEvent' => YII2_PATH . '/base/ActionEvent.php',
'yii\base\ActionFilter' => YII2_PATH . '/base/ActionFilter.php',
'yii\base\Application' => YII2_PATH . '/base/Application.php',
// ....
// ....
// etc....
];
ثابت YII2_PATH
و yii alias
یعنی @yii
مسیر مشترکی دارند و به مسیر دایرکتوری yii2 اشاره میکنند.
اگر namespace ای در classMap نبود Autoload میره از prefix namespace هایی که مسیرشون در آرایه ی aliases ست شده، کمک میگیره برای پیدا کردن فایل ها و ...
Composer:
پکیج هایی که در vendor قرار دارند رو اگر باز کنید، هر کدوم فایل composer.json دارند که داخل این فایل و در قسمت autoload psr-4
مسیر prefix namespace ها مشخص شده. یعنی مشخص شده فایل های مرتبط با prefix در چه مسیری قرار گرفته.
برای مثال اگر پکیج doctrine
رو باز کنید: path/to/project_name/vendor/doctrine/instantiator
در فایل composer.json آمده:
"autoload": {
"psr-4": {
"Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
}
},
* در \\
بک اسلش اول برای escape
کردن بک اسلش دوم (در داخل رشته) اومده.
با توجه به مسیر هر پکیج خود composer هم یک map داره که مسیردهی پکیج هارو در فایل path/to/project_name/vendor/composer/autoload_psr4.php
مپ کرده. بخشی ازین فایل:
<?php
// autoload_psr4.php @generated by Composer
$vendorDir = dirname(dirname(__FILE__));
$baseDir = dirname($vendorDir);
return array(
'yii\\composer\\' => array($vendorDir . '/yiisoft/yii2-composer'),
'yii\\bootstrap\\' => array($vendorDir . '/yiisoft/yii2-bootstrap/src'),
'yii\\' => array($vendorDir . '/yiisoft/yii2'),
'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'),
// ....
// ....
// etc....
);
خب بند آخر PHPFig
رو هم بیارم و تمام.
4. autoloader
شما نباید هیچ exception
یا خطایی تولید کنه و توصیه میشه که هیچ مقداری هم return
نشه.
(البته من دیدم توو پروژه ها exception تولید میکنن)
این مثال رو برای پیاده سازی یک autoload
ببینید.