在PHP脚本中,无论是调用include()
、require()
、fopen()
,还是它们的衍生物如include_once
、require_once
,甚至是move_uploaded_file()
,都经常遇到错误或警告。
打开流失败:没有这样的文件或目录。
有什么好的程序可以快速找到问题的根源?
遇到这种错误的原因有很多,因此有一个好的检查清单,首先要检查什么,这对我们有很大的帮助。 让我们考虑一下,我们正在排除以下行的故障。
require "/path/to/file"
/users/tony/htdocs
。require __DIR__ . "/relative/path/from/current/file"
。__DIR__
魔法常量]magicconst-docs返回当前文件的目录。SITE_ROOT
常量。config.php
。config.php
中,写上
define('SITE_ROOT', DIR);config.php
,然后在你喜欢的地方使用SITE_ROOT
常量。
require_once DIR."/.../config.php"。
...
require_once SITE_ROOT."/other/file.php"。
这2种做法也使你的应用程序更具有可移植性,因为它不依赖于ini设置,如include路径。
另一种包含文件的方式,既不是相对的也不是纯粹绝对的,而是依靠include path。对于库或框架,如Zend框架,通常是这样的情况。 这样的包含将看起来像这样。
include "Zend/Mail/Protocol/Imap.php"
在这种情况下,你将需要确保"Zend"所在的文件夹是包含路径的一部分。 你可以用以下方法检查include路径。
echo get_include_path();
你可以用:{{38834427}}添加一个文件夹到其中。
set_include_path(get_include_path().":"."/path/to/new/folder");
可能总的来说,运行服务器进程(Apache或PHP)的用户根本没有权限读取或写入该文件。 要检查服务器是以什么用户运行的,可以使用 posix_getpwuid 。
$user = posix_getpwuid(posix_geteuid());
var_dump($user);
要了解文件的权限,请在终端键入以下命令。
ls -l <path/to/file>
并查看permission symbolic notation。
如果上述方法都不奏效,那么问题可能是某些PHP设置禁止它访问该文件。 有三个设置可能是相关的。 1.[open_basedir][open_basedir-docs)
phpinfo()
或使用ini_get("open_basedir")
来检查。ini_get("allow_url_include")
检查,用ini_set("allow_url_include", "1")
设置。
如果上面的方法都不能诊断出问题,下面是一些可能发生的特殊情况。
可能发生的情况是,你包含了一个库,例如,Zend框架,使用相对或绝对路径。例如:
require "/usr/share/php/libzend-framework-php/Zend/Mail/Protocol/Imap.php"
但你还是会遇到同样的错误。 这可能是因为你已经(成功地)包含的文件,本身就有另一个文件的include语句,而第二个include语句假定你已经将该库的路径加入到include路径中。 例如,前面提到的Zend框架文件可以有如下的include。
include "Zend/Mail/Protocol/Exception.php"
这既不是一个相对路径的包含,也不是一个绝对路径的包含。它是假设Zend框架目录已经被添加到include路径中。
在这种情况下,唯一实用的解决方案是将该目录添加到你的包含路径中。
如果你运行的是安全增强型Linux,那么它可能是问题的原因,通过拒绝从服务器访问该文件。
要检查你的系统是否启用了SELinux,在终端运行sestatus
命令。如果该命令不存在,那么你的系统上就没有SELinux。如果它确实存在,那么它应该告诉你它是否被执行了。
**为了检查SELinux策略是否是问题的原因,你可以尝试暂时关闭它。但是要小心,因为这将完全禁用保护。请不要在您的生产服务器上这样做。
setenforce 0
如果你在关闭SELinux后不再有问题,那么这就是根本原因了。 要解决这个问题,你必须对SELinux进行相应配置。 以下的上下文类型将是必要的。
httpd_sys_content_t
用于你希望你的服务器能够读取的文件httpd_sys_rw_content_t
用于你希望有读写权限的文件httpd_log_t
用于存放日志文件httpd_cache_t
用于缓存目录。
例如,为了给你的网站根目录分配httpd_sys_content_t
上下文类型,运行:semanage fcontext -a -t httpd_sys_content_t "/path/to/root(/.*)?"
restorecon -Rv /path/to/root
如果你的文件在主目录下,你还需要打开httpd_enable_homedirs
布尔值。
setsebool -P httpd_enable_homedirs 1
在任何情况下,根据你的政策,SELinux可能有各种原因拒绝访问一个文件。所以你需要查询一下。这里是一个专门关于为网络服务器配置SELinux的教程。
如果你使用Symfony,并且在上传到服务器时遇到这个错误,那么可能是应用程序的缓存没有被重置,要么是因为app/cache
已经被上传,要么就是缓存没有被清除。
你可以通过运行以下控制台命令来测试和解决这个问题。
cache:clear
显然,当压缩文件中的某些文件的文件名中含有非 ASCII 字符,如 "é"时,这个错误也会在调用 zip->close()
时发生。
一个潜在的解决方案是在创建目标文件之前用utf8_decode()
来包裹文件名。
归功于 Fran Cano 发现并提出了这个问题的解决方案。
作为对现有答案(非常好)的补充
open_basedir
是一个可以让你感到困惑的问题,因为它可以在网络服务器配置中被指定。如果你运行自己的专用服务器,这很容易补救,但有一些共享主机软件(如Plesk、cPanel等)会在每个域的基础上配置一个配置指令。因为该软件建立了配置文件(即httpd.conf
),你不能直接改变该文件,因为托管软件在重新启动时将直接覆盖它。
对于Plesk,他们提供了一个覆盖所提供的httpd.conf
的地方,称为vhost.conf
。只有服务器管理员可以写这个文件。Apache的配置看起来像这样
<Directory /var/www/vhosts/domain.com>
<IfModule mod_php5.c>
php_admin_flag engine on
php_admin_flag safe_mode off
php_admin_value open_basedir "/var/www/vhosts/domain.com:/tmp:/usr/share/pear:/local/PEAR"
</IfModule>
</Directory>
让你的服务器管理员查阅他们使用的主机和网络服务器软件的手册。
需要注意的是,通过你的网络服务器执行一个文件与命令行或cron job的执行有很大不同。最大的区别是,你的网络服务器有自己的用户和权限。由于安全原因,这个用户是相当受限制的。例如,Apache通常是apache
、www-data
或httpd
(取决于你的服务器)。一个cron job或CLI的执行具有运行它的用户所具有的任何权限(例如,以root身份运行PHP脚本将以root的权限执行)。
很多时候,人们会通过以下方式解决权限问题(以Linux为例)
chmod 777 /path/to/file
这不是一个聪明的主意,因为现在的文件或目录是可写的。如果你拥有服务器,并且是唯一的用户,那么这并不是什么大问题,但如果你是在共享主机环境下,你就等于给了服务器上的每个人以权限。
你需要做的是确定需要访问的用户,并只给他们访问权。一旦你知道哪些用户需要访问,你要确保
1.该用户拥有该文件和可能的父目录(特别是父目录,如果你想写文件)。在大多数共享主机环境中,这不会是一个问题,因为你的用户应该拥有你根目录下的所有文件。一个Linux的例子显示如下
chown apache:apache /path/to/file
2.该用户,而且只有该用户有权限。在Linux中,一个好的做法是chmod 600
(只有所有者可以读和写)或chmod 644
(所有者可以写,但所有人都可以读)。