freeman99
سه شنبه 19 اسفند 1393, 21:48 عصر
من یه فایل دارم که توی صفحات مختلفی اینکلود میشه (اسمش رو بذاریم inc.php). مثلا در فایل somepage.php که در ریشهء پروژه قرار داره، و همچنین در فایل somepage2.php که در یک ساب دایرکتوری (مثلا به اسم subdir) تحت ریشهء پروژه قرار داره.
حالا توی inc.php یک لینک دارم که به فایلی در دایرکتوری ریشهء پروژه اشاره میکنه (مثلا به target.php).
حالا طبیعتا این فایل inc.php وقتی توی somepage.php اینکلود میشه آدرس href لینک HTML که تولید میکنه اینه: target.php
اما وقتی توی somepage2.php اینکلود میشه باید آدرس لینک رو به ../target.php تغییر بده.
پس ما باید در فایل inc.php کدی داشته باشیم که این رو تشخیص بده که الان فاصلهء صفحه تا ریشهء پروژه چقدره، و به اون تعداد از .. برای بالا رفتن در سلسله مراتب دایرکتوری ها استفاده کنه.
میخوایم روشی باشه که تاحد ممکن خودکار باشه و نیاز به نوشتن کد دستی و آپدیت دستی درصورت تغییر مکان صفحات پروژه نداشته باشه. وگرنه من مثلا قبلا در ابتدای صفحاتی که در subdir قرار داشتن متغییری تعریف کرده بودم به این شکل:
$index_dir='../';
و برای تولید لینک ها ازش استفاده میکردم.
در صفحاتی که در ریشهء پروژه بودن مقدار این متغییر خالی بود چون نیازی نبود هیچ چیزی به ابتدای آدرس نسبی لینک ها اضافه بشه و فقط نام فایل هدف واقع در ریشهء پروژه کافی بود.
من در این مورد سرچ کردم اما در صفحات پیدا شده نتایج یا اینطور بگم که روشهای چندان مرتبطی پیدا نکردم. شایدم باید کیوردها و روش سرچ خودم رو اصلاح میکردم. بهرحال خودم تعدادی از متغییرهای PHP رو که ممکنه بشه ازشون برای این کار استفاده کرد بررسی و تست کردم. تست ها چندتا فایل داره که براتون ضمیمه کردم. چون کدهای نمایش داده شده در این فروم بعضا خراب میشن دیگه اونا رو درج نمیکنم و در فایل ضمیمه میتونید مشاهده کنید.
اما خروجی این اسکریپت های تست بطور مثال به این شکله:
البته قبلش بگم که آدرسی که در مرورگر دادیم به این شکله:
http://localhost/project1/subdir/test.php/ooo/?p
و خروجی اسکریپت:
ROOT: D:/Program Files/EasyPHP-5.3.9/www/project1/
REQUEST_URI: /project1/subdir/test.php/ooo/?p
PHP_SELF: /project1/subdir/test.php/ooo/
DOCUMENT_ROOT: D:/Program Files/EasyPHP-5.3.9/www
SCRIPT_FILENAME: D:/Program Files/EasyPHP-5.3.9/www/project1/subdir/test.php
SCRIPT_NAME: /project1/subdir/test.php
ما از اجزای اضافه در انتهای آدرس استفاده کردیم تا بفهمیم در کدوم متغییرها کدامیک از این اجزای اضافه وجود دارن یا ندارن، چون این اجزای اضافه از نظر کارکرد صحیح برنامه در تمام شرایط مشکل ایجاد میکنن.
البته اینو بگم که تمام این متغییرها از $_SERVER هستن بجز ROOT که از طریق این کد بدست اومده:
define('ROOT', str_replace('\\', '/', __DIR__).'/');
و این کد در فایلی بنام root.php که در ریشهء پروژه قرار داره هست که در ابتدای تمام صفحات اینکلود میشه.
خب، همونطور که مشاهده میکنید REQUEST_URI شامل آدرس مسیر کاملی میشه که در مرورگر وارد شده (البته این آدرس دامین رو شامل نمیشه و بخاطر همین گفتم آدرس مسیر)، منجمله تمام اجزای بعد از اسکریپت (مثلا Query string). پس این متغییر برای کار ما مناسب نیست.
در متغییر PHP_SELF هم با اینکه Query string نمیاد اما اون اطلاعاتی که به شکل آدرس دایرکتوری های مجازی در انتهای آدرس اسکریپت اضافه میشن میان (ooo). پس این متغییر هم برای کار ما مناسب نیست.
در متغییر DOCUMENT_ROOT مشاهده میکنیم که آدرس ریشه کل وب سایت رو میده و نه آدرس ریشهء پروژه و باید به این نکته دقت کرد (اگر پروژه رو در ریشهء وب خودمون قرار داده بودیم متوجه این مسئله نمیشدیم، اما به ذهنم رسید و بخاطر همین پروژه رو در یک پوشه خاص خودش در ریشهء وب گذاشتم)، و دائم دارم میگم ریشهء پروژه و نه ریشهء وب یا ریشه، به همین خاطر.
پس این متغییر هم عملا به درد ما نمیخوره.
در متغییر SCRIPT_FILENAME مشاهده میکنیم که آدرس کامل اسکریپت ما، و بدون اجزای اضافی URL، وجود داره. بنابراین این متغییر برای کار ما مناسب بنظر میاد.
در متغییر SCRIPT_NAME مشاهده میکنیم که آدرس تحت وب اسکریپت بصورت کامل و بدون اجزای جانبی آمده. شاید فکر کنیم که از این متغییر هم برای هدف ما (تعیین فاصله تا ریشهء پروژه) میشه استفاده کرد، ولی بعد توضیح میدم که چه مشکلی داره.
روشی که من بهش رسیدم اینه:
echo 'Number of \'..\'s needed: ';
echo substr_count($_SERVER['SCRIPT_FILENAME'], '/', strlen(ROOT));
یعنی ما در SCRIPT_FILENAME به میزان طول ROOT جلو میریم و تعداد اسلش های رو که از اونجا تا انتهای رشته وجود دارن میشماریم. به تعداد این اسلش ها باید در آدرس لینک خودمون از ../ استفاده کنیم. البته من نمیگم لزوما از عین همین کد به همین شکل شمارش کردن استفاده میکنیم، بلکه روش کلی کار رو دارم به یک شکل نشون میدم و شاید بشه کد مربوطه رو با توابع دیگر و به شکلهای دیگری هم نوشت (اما بهرحال ما با همین دو متغییر خاص کار میکنیم).
البته در این راه حل ما بر روی وجود متغییر SCRIPT_NAME در محیط اتکا کردیم، که باید مطمئن بشیم به خوبی همه جا پشتیبانی میشه. من روی این مسئله مطمئن نیستم چون چند سال قبل سر اینطور متغییرها افرادی میگفتن و در نت هم در منابعی آمده بود که این متغییرها در بعضی نسخه ها/(وب)سرورها/محیط ها/OS ها/... ممکنه در دسترس نباشن (حالا شاید به بعضی شرایط و کانفیگ های خاص اونا هم بستگی داشته باشه). پس اگر اطلاعاتی در این زمینه دارید بفرماید.
راستی بعد میگم که چرا استفاده از SCRIPT_NAME میتونه موجب ایجاد یک باگ پنهان بشه.
یه مورد دیگه هم که باید بگم اینه که شاید بگید خب شما که بهرحال مثلا در صفحه ای که در سابدایرکتوری قرار داره اومدی root.php رو با کد require '../root.php'; اینکلود کردی، یعنی بهرحال مجبور بودی از .. در این صفحه استفاده کنی و یه مشخصه ای از اینکه این صفحه در این سطح در سابدایرکتوری تحت دایرکتوری ریشه قرار داره توش قرار دادی، پس صفحهء شما بازم نیاز به تعیین دستی در این زمینه داره و کاملا خودکار و مستقل و تاثیرناپذیر از تغییر مکان نیست، پس میتونستی از همون روش متغییر index_dir که خودت گفتی استفاده کنی. اما باید بگم در اون صورت مجبور بودم متغییر index_dir رو در صفحاتی که در ریشه قرار دارن هم اضافه کنم، و این کمی کثیف و کار اضافی بنظر میرسه. البته نه دقیقا مجبور هم نبودم! میتونستم در صفحات ریشه این متغییر رو اصلا درج و تعریف نکنم و بجاش در inc.php از تابع isset برای تشخیص وجود این متغییر استفاده کنم. خب اگر فکر کنیم شاید هم دست آخر ببینم این روش هم میشه و حتی ساده تر و مناسب تره! ولی بهرحال من تا اینجا این متغییرها و خواصشون رو تست کردم و یک روش خودکار برای بخشی از کاری طراحی کردم که شاید بعضی مزایایی داشته باشه و یا در بعضی موارد دیگر هم به درد بخوره و فکر میکنم شاید بعضی وقتا اینکه مسائل رو از هم جدا نگه داریم از نظر اصول و آینده نگری بهتر باشه (یعنی حالا چکار داریم به root.php و با این مسئله قاطیش نکنیم شاید در آینده اصلا یه داستان دیگری داشتیم).
حالا توی inc.php یک لینک دارم که به فایلی در دایرکتوری ریشهء پروژه اشاره میکنه (مثلا به target.php).
حالا طبیعتا این فایل inc.php وقتی توی somepage.php اینکلود میشه آدرس href لینک HTML که تولید میکنه اینه: target.php
اما وقتی توی somepage2.php اینکلود میشه باید آدرس لینک رو به ../target.php تغییر بده.
پس ما باید در فایل inc.php کدی داشته باشیم که این رو تشخیص بده که الان فاصلهء صفحه تا ریشهء پروژه چقدره، و به اون تعداد از .. برای بالا رفتن در سلسله مراتب دایرکتوری ها استفاده کنه.
میخوایم روشی باشه که تاحد ممکن خودکار باشه و نیاز به نوشتن کد دستی و آپدیت دستی درصورت تغییر مکان صفحات پروژه نداشته باشه. وگرنه من مثلا قبلا در ابتدای صفحاتی که در subdir قرار داشتن متغییری تعریف کرده بودم به این شکل:
$index_dir='../';
و برای تولید لینک ها ازش استفاده میکردم.
در صفحاتی که در ریشهء پروژه بودن مقدار این متغییر خالی بود چون نیازی نبود هیچ چیزی به ابتدای آدرس نسبی لینک ها اضافه بشه و فقط نام فایل هدف واقع در ریشهء پروژه کافی بود.
من در این مورد سرچ کردم اما در صفحات پیدا شده نتایج یا اینطور بگم که روشهای چندان مرتبطی پیدا نکردم. شایدم باید کیوردها و روش سرچ خودم رو اصلاح میکردم. بهرحال خودم تعدادی از متغییرهای PHP رو که ممکنه بشه ازشون برای این کار استفاده کرد بررسی و تست کردم. تست ها چندتا فایل داره که براتون ضمیمه کردم. چون کدهای نمایش داده شده در این فروم بعضا خراب میشن دیگه اونا رو درج نمیکنم و در فایل ضمیمه میتونید مشاهده کنید.
اما خروجی این اسکریپت های تست بطور مثال به این شکله:
البته قبلش بگم که آدرسی که در مرورگر دادیم به این شکله:
http://localhost/project1/subdir/test.php/ooo/?p
و خروجی اسکریپت:
ROOT: D:/Program Files/EasyPHP-5.3.9/www/project1/
REQUEST_URI: /project1/subdir/test.php/ooo/?p
PHP_SELF: /project1/subdir/test.php/ooo/
DOCUMENT_ROOT: D:/Program Files/EasyPHP-5.3.9/www
SCRIPT_FILENAME: D:/Program Files/EasyPHP-5.3.9/www/project1/subdir/test.php
SCRIPT_NAME: /project1/subdir/test.php
ما از اجزای اضافه در انتهای آدرس استفاده کردیم تا بفهمیم در کدوم متغییرها کدامیک از این اجزای اضافه وجود دارن یا ندارن، چون این اجزای اضافه از نظر کارکرد صحیح برنامه در تمام شرایط مشکل ایجاد میکنن.
البته اینو بگم که تمام این متغییرها از $_SERVER هستن بجز ROOT که از طریق این کد بدست اومده:
define('ROOT', str_replace('\\', '/', __DIR__).'/');
و این کد در فایلی بنام root.php که در ریشهء پروژه قرار داره هست که در ابتدای تمام صفحات اینکلود میشه.
خب، همونطور که مشاهده میکنید REQUEST_URI شامل آدرس مسیر کاملی میشه که در مرورگر وارد شده (البته این آدرس دامین رو شامل نمیشه و بخاطر همین گفتم آدرس مسیر)، منجمله تمام اجزای بعد از اسکریپت (مثلا Query string). پس این متغییر برای کار ما مناسب نیست.
در متغییر PHP_SELF هم با اینکه Query string نمیاد اما اون اطلاعاتی که به شکل آدرس دایرکتوری های مجازی در انتهای آدرس اسکریپت اضافه میشن میان (ooo). پس این متغییر هم برای کار ما مناسب نیست.
در متغییر DOCUMENT_ROOT مشاهده میکنیم که آدرس ریشه کل وب سایت رو میده و نه آدرس ریشهء پروژه و باید به این نکته دقت کرد (اگر پروژه رو در ریشهء وب خودمون قرار داده بودیم متوجه این مسئله نمیشدیم، اما به ذهنم رسید و بخاطر همین پروژه رو در یک پوشه خاص خودش در ریشهء وب گذاشتم)، و دائم دارم میگم ریشهء پروژه و نه ریشهء وب یا ریشه، به همین خاطر.
پس این متغییر هم عملا به درد ما نمیخوره.
در متغییر SCRIPT_FILENAME مشاهده میکنیم که آدرس کامل اسکریپت ما، و بدون اجزای اضافی URL، وجود داره. بنابراین این متغییر برای کار ما مناسب بنظر میاد.
در متغییر SCRIPT_NAME مشاهده میکنیم که آدرس تحت وب اسکریپت بصورت کامل و بدون اجزای جانبی آمده. شاید فکر کنیم که از این متغییر هم برای هدف ما (تعیین فاصله تا ریشهء پروژه) میشه استفاده کرد، ولی بعد توضیح میدم که چه مشکلی داره.
روشی که من بهش رسیدم اینه:
echo 'Number of \'..\'s needed: ';
echo substr_count($_SERVER['SCRIPT_FILENAME'], '/', strlen(ROOT));
یعنی ما در SCRIPT_FILENAME به میزان طول ROOT جلو میریم و تعداد اسلش های رو که از اونجا تا انتهای رشته وجود دارن میشماریم. به تعداد این اسلش ها باید در آدرس لینک خودمون از ../ استفاده کنیم. البته من نمیگم لزوما از عین همین کد به همین شکل شمارش کردن استفاده میکنیم، بلکه روش کلی کار رو دارم به یک شکل نشون میدم و شاید بشه کد مربوطه رو با توابع دیگر و به شکلهای دیگری هم نوشت (اما بهرحال ما با همین دو متغییر خاص کار میکنیم).
البته در این راه حل ما بر روی وجود متغییر SCRIPT_NAME در محیط اتکا کردیم، که باید مطمئن بشیم به خوبی همه جا پشتیبانی میشه. من روی این مسئله مطمئن نیستم چون چند سال قبل سر اینطور متغییرها افرادی میگفتن و در نت هم در منابعی آمده بود که این متغییرها در بعضی نسخه ها/(وب)سرورها/محیط ها/OS ها/... ممکنه در دسترس نباشن (حالا شاید به بعضی شرایط و کانفیگ های خاص اونا هم بستگی داشته باشه). پس اگر اطلاعاتی در این زمینه دارید بفرماید.
راستی بعد میگم که چرا استفاده از SCRIPT_NAME میتونه موجب ایجاد یک باگ پنهان بشه.
یه مورد دیگه هم که باید بگم اینه که شاید بگید خب شما که بهرحال مثلا در صفحه ای که در سابدایرکتوری قرار داره اومدی root.php رو با کد require '../root.php'; اینکلود کردی، یعنی بهرحال مجبور بودی از .. در این صفحه استفاده کنی و یه مشخصه ای از اینکه این صفحه در این سطح در سابدایرکتوری تحت دایرکتوری ریشه قرار داره توش قرار دادی، پس صفحهء شما بازم نیاز به تعیین دستی در این زمینه داره و کاملا خودکار و مستقل و تاثیرناپذیر از تغییر مکان نیست، پس میتونستی از همون روش متغییر index_dir که خودت گفتی استفاده کنی. اما باید بگم در اون صورت مجبور بودم متغییر index_dir رو در صفحاتی که در ریشه قرار دارن هم اضافه کنم، و این کمی کثیف و کار اضافی بنظر میرسه. البته نه دقیقا مجبور هم نبودم! میتونستم در صفحات ریشه این متغییر رو اصلا درج و تعریف نکنم و بجاش در inc.php از تابع isset برای تشخیص وجود این متغییر استفاده کنم. خب اگر فکر کنیم شاید هم دست آخر ببینم این روش هم میشه و حتی ساده تر و مناسب تره! ولی بهرحال من تا اینجا این متغییرها و خواصشون رو تست کردم و یک روش خودکار برای بخشی از کاری طراحی کردم که شاید بعضی مزایایی داشته باشه و یا در بعضی موارد دیگر هم به درد بخوره و فکر میکنم شاید بعضی وقتا اینکه مسائل رو از هم جدا نگه داریم از نظر اصول و آینده نگری بهتر باشه (یعنی حالا چکار داریم به root.php و با این مسئله قاطیش نکنیم شاید در آینده اصلا یه داستان دیگری داشتیم).