PHP - 打开流失败:没有该文件或目录

230
在 PHP 脚本中,无论是调用 include()require()fopen() 还是它们的衍生函数 include_oncerequire_once 甚至是 move_uploaded_file(),通常会遇到以下错误或警告:

Failed to open stream : No such file or directory.

有什么好的方法能快速找出问题的根本原因呢?


5
我已清理了此帖子中的离题评论。请在元区保留元讨论。但请注意,对于标准问题的可行性讨论已经反复进行过很多次了。这里有一个示例链接 - Madara's Ghost
1
我遇到了同样的问题,唯一总是有效的解决方案是:-1 进入要包含的文件,右键单击,选择属性,复制完整路径。例如:C:/......../file.php 2- 包含它。实际上,我看到这个问题已经得到了回答,并且答案已经被验证,但在某些情况下对我不起作用,直到我找到了上述方法。 - Rshad Zhran
@Rash 谢谢你的贡献。不幸的是,你的解决方案是错误的,因为它会提及绝对路径名,而那是错误的。这样做的原因是,一旦你将项目复制到其他地方或在计算机内部移动它,一切都会崩溃。 - Vic Seedoubleyew
11个回答

343

有很多原因会导致出现这个错误,因此一个好的检查清单可以极大地帮助解决问题。

让我们考虑一下我们正在排除以下行:

require "/path/to/file"


清单


1. 检查文件路径是否有错别字

  • either check manually (by visually checking the path)
  • or move whatever is called by require* or include* to its own variable, echo it, copy it, and try accessing it from a terminal:

    $path = "/path/to/file";
    
    echo "Path : $path";
    
    require "$path";
    

    Then, in a terminal:

    cat <file path pasted>
    


2. 检查文件路径是否正确,涉及相对路径和绝对路径的考虑

  • 如果以斜杠“/”开头,则不是指向您网站文件夹(文档根目录)的根目录,而是指向您服务器的根目录。
    • 例如,您的网站目录可能是/users/tony/htdocs
  • 如果没有以斜杠开头,则依靠包含路径(见下文)或路径是相对的。 如果它是相对路径,则PHP将根据当前工作目录的路径进行计算。
    • 因此,与您的网站根目录的路径或您正在输入的文件的路径无关
    • 出于这个原因,请始终使用绝对文件路径

最佳实践:

为了使您的脚本在移动文件位置时更加健壮,并且仍然在运行时生成绝对路径,您有两个选择:

  1. use require __DIR__ . "/relative/path/from/current/file". The __DIR__ magic constant returns the directory of the current file.
  2. define a SITE_ROOT constant yourself :

    • at the root of your web site's directory, create a file, e.g. config.php
    • in config.php, write

      define('SITE_ROOT', __DIR__);
      
    • in every file where you want to reference the site root folder, include config.php, and then use the SITE_ROOT constant wherever you like :

      require_once __DIR__."/../config.php";
      ...
      require_once SITE_ROOT."/other/file.php";
      
这两种做法还可以使你的应用程序更加便携,因为它不依赖于ini设置,例如包含路径。


3. 检查您的包含路径

另一种包含文件的方式,既不是相对路径也不是纯绝对路径,是依赖于包含路径。这通常适用于像Zend框架这样的库或框架。

这样的包含看起来像这样:

include "Zend/Mail/Protocol/Imap.php"

在这种情况下,您需要确保“Zend”所在的文件夹是包含路径的一部分。
您可以使用以下命令检查包含路径:
echo get_include_path();

你可以使用以下代码将文件夹添加到其中:

set_include_path(get_include_path().":"."/path/to/new/folder");


4. 检查您的服务器是否可以访问该文件

可能是因为运行服务器进程(Apache或PHP)的用户根本没有权限读取或写入该文件。

要检查服务器在哪个用户下运行,您可以使用 posix_getpwuid

$user = posix_getpwuid(posix_geteuid());

var_dump($user);

要查看文件的权限,请在终端中输入以下命令:

ls -l <path/to/file>

并查看权限符号表示法


5. 检查PHP设置

如果以上方法都不起作用,那么问题可能是某些PHP设置禁止访问该文件。

有三个设置可能会影响:

  1. open_basedir
    • 如果设置了此项,PHP将无法访问指定目录之外的任何文件(即使是通过符号链接)。
    • 但是,默认行为是不设置它,因此没有限制。
    • 可以通过调用phpinfo()或使用ini_get("open_basedir")来检查此设置。
    • 您可以通过编辑php.ini文件或httpd.conf文件来更改设置。
  2. safe mode
    • 如果打开此选项,则可能会应用限制。但是,在PHP 5.4中已删除此功能。如果您仍在使用支持安全模式的版本,请升级到仍在受支持的PHP版本
  3. allow_url_fopen和allow_url_include
    • 这仅适用于通过网络进程(如http://)包括或打开文件,而不是尝试在本地文件系统上包括文件时。
    • 可以使用ini_get("allow_url_include")进行检查,并使用ini_set("allow_url_include", "1")进行设置。


边角情况

如果以上方法都不能诊断问题,以下是一些可能发生的特殊情况:


1. 依赖于包含路径的库的引入

有时您会使用相对或绝对路径来引入库,例如Zend框架。例如:

require "/usr/share/php/libzend-framework-php/Zend/Mail/Protocol/Imap.php"

但是你仍然会得到相同类型的错误。
这可能是因为您已经成功包含的文件本身有一个包含语句指向另一个文件,并且该第二个包含语句假定您已将该库的路径添加到了包含路径中。
例如,之前提到的Zend框架文件可能有以下包含语句:
include "Zend/Mail/Protocol/Exception.php" 

这不是相对路径或绝对路径的包含方式。它假定Zend框架目录已添加到包含路径中。
在这种情况下,唯一实际的解决方案是将该目录添加到您的包含路径中。


2. SELinux

如果您正在运行安全增强型 Linux(SELinux),可能会因为服务器拒绝访问文件而导致问题。

要检查系统是否启用了 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拒绝访问文件可能有多种原因,具体取决于您的策略。因此,您需要进行查询。这里是一个专门介绍为Web服务器配置SELinux的教程。


3. Symfony

如果您正在使用Symfony,并在上传到服务器时遇到此错误,则可能是应用程序的缓存尚未重置,原因是app/cache已被上传或者缓存没有被清除。

您可以通过运行以下控制台命令来测试和修复此问题:

cache:clear


4. Zip文件中的非ASCII字符

显然,当压缩包中的某些文件名包含非ASCII字符(例如“é”)时,在调用zip->close()时也可能会出现此错误。

一个潜在的解决方案是在创建目标文件之前使用utf8_decode()包装文件名。

感谢Fran Cano识别并提出了解决此问题的方法。


4
我认为在这里提到 selinux 可能是个好主意。至少您需要在所包含的文件上拥有 httpd_sys_content_t 权限(Apache 使用的只读目录和文件)。 - bansi
非常感谢您的建议。由于我对SELinux不熟悉,所以我进行了一些阅读并尝试回答这个问题。如果有任何不正确的地方,请随时提出反馈或建议修改。再次感谢您的评论! - Vic Seedoubleyew
chcon 是临时的,不会在 restorecon 或重启后生效。您可能需要使用 semanage 来更改文件的上下文。这里有一个适用于网站的好的简单教程 - bansi
@chrishiestand 非常感谢!那篇文章真的很有趣!你还记得导致那个错误的事件顺序是什么吗?最初用户没有读取文件的访问权限,然后更改了它,但缓存仍然认为它不可读,因此在打开文件时抛出了该错误吗? - Vic Seedoubleyew
@VicSeedoubleyew 是的,在我的情况下,该文件是在多个符号链接的基础上构建的。在符号链接的背后,一个新文件被链接到旧文件被删除的位置。但是PHP试图使用旧realpath并由于过期的缓存而失败。笨拙的解决方法是在此错误上清除缓存。 - chrishiestand
显示剩余4条评论

17

在这个已经很好的答案基础上,需要补充的是:

共享主机软件

open_basedir 是一个可能会让你困扰的问题,因为它可以在Web服务器配置中指定。如果您运行自己的专用服务器,则可以轻松解决此问题,但是有一些共享主机软件包(例如Plesk、cPanel等)会按域配置一个配置指令。由于软件生成配置文件(即 httpd.conf),因此您无法直接更改该文件,因为主机软件在重新启动时会覆盖它。

对于Plesk,他们提供了一个名为vhost.conf的地方来覆盖提供的httpd.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>

请让您的服务器管理员查阅所使用的主机和Web服务器软件的手册。

文件权限

需要注意的是,通过 Web 服务器执行文件与通过命令行或 cron job 执行完全不同。最大的区别在于,您的 Web 服务器具有自己的用户和权限。出于安全原因,该用户的权限非常受限制。例如,Apache 经常是 apachewww-datahttpd(取决于您的服务器)。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(所有者可以写但每个人都可以读)

您可以在这里阅读更详细的Linux / Unix权限和用户讨论


10
  1. 查看确切的错误信息

我的代码在所有机器上都能正常运行,但在这台机器上开始出现问题(我想以前似乎是可以工作的)。使用echo命令来调试"document_root"路径,并仔细查看错误信息,发现了这个:

警告: include(D:/MyProjects/testproject//functions/connections.php): 打开流失败:

你可以很容易地看出问题所在。问题出在functions之前的“//”符号。

$document_root = $_SERVER['DOCUMENT_ROOT'];
echo "root: $document_root";
include($document_root.'/functions/connections.php');

所以只需从包含中简单地删除/符号,它就应该正常工作。有趣的是这种行为在不同版本上是不同的。我在笔记本电脑、Macbook Pro和这台电脑上运行相同的代码,一切都运行良好,直到出现问题。希望这能帮助到某个人。

2. 复制并粘贴文件位置到浏览器中,以确保该文件存在。有时文件会意外删除(这也发生在我的情况下),这也是我的问题所在。


这与下面清单的第一步有何不同? - Vic Seedoubleyew
第二步是一项额外的检查,与第一步无关。只需在浏览器中导航到所建议的路径,并查看是否可以看到文件(不是在Windows资源管理器中而是在浏览器中)。 - TheTechGuy

5

Samba分享

如果你有一台Linux测试服务器并且你从Windows客户端进行工作,Samba分享会影响到chmod命令。因此,即使你使用了:

chmod -R 777 myfolder

在Linux系统中,Unix组\www-data可能仍然没有写权限。一种可行的解决方案是,如果您的共享设置为将Windows管理员映射到root,则可以从Windows打开权限设置,在副本中禁用继承,然后授予www-data完全访问权限。


2

除了其他优秀的答案,当我在Windows上编写一个简单的脚本时,我忽略了一件事情:当尝试使用Windows不支持的字符打开文件时,将显示此错误。

例如:

$file = fopen(date('Y-m-d_H:i:s'), 'w+');

将会得到:

fopen(2022-06-01_22:53:03):无法打开流:在...中没有这个文件或目录

Windows不喜欢在文件名中使用:,还有一些其他字符。


2

使用查询参数添加脚本

这是我的情况。实际上,它链接到问题#4485874,但我将在此简要解释。
当您尝试要求path/to/script.php?parameter=value时,PHP会查找名为script.php?parameter=value的文件,因为UNIX允许您拥有此类路径。
如果您确实需要向已包含的脚本传递某些数据,请将其声明为$variable=...$GLOBALS[]=...或其他您喜欢的方式。


1

PHP - 打开流失败:在Mac上没有此文件或目录

例如,我要上传一张图片。但是我遇到了这个错误。首先我会右键单击该图像并获取信息。

$thePathOfMyPicture = "/Users/misstugba/Desktop/";使用该函数

if(move_uploaded_file($_FILES["file"]["tmp_name"],$thePathOfMyPicture.$_FILES["file"]["name"])){
echo "image uploaded successfully";

} enter image description here


1
以下 PHP 设置在 php.ini 中,如果设置为不存在的目录,也会引发以下警告:

PHP Warning: Unknown: failed to open stream: Permission denied in Unknown on line 0

sys_temp_dir
upload_tmp_dir
session.save_path

0

对我来说,我遇到了这个错误是因为我试图读取一个需要HTTP身份验证的文件,需要输入用户名和密码。希望这能帮助其他人。可能还有其他情况。

编辑

您可以通过检查标头来确定是否存在此类型的身份验证:

$file_headers = get_headers($url);
if (!$file_headers) echo 'File headers missing';
else if (strpos($file_headers[0], '401 Unauthorized') > -1) echo '401 Unauthorized';

你能分享一下你使用的确切代码行吗?它使用了 includerequire 还是 fopen - Vic Seedoubleyew
@VicSeedoubleyew $fp = fopen($file, 'wb'); 不,我没有使用include或require。您可以使用以下网址进行复制:http://12345:12345@datatransfer.cj.com/datatransfer/files/products.zip - Sean H
用户名和密码在这种类型的身份验证中无法使用,我已经尝试过了。修复方法是检查标头并查看是否设置了AUTH。 - Sean H
非常感谢!不过我不确定我理解了。您添加的代码是用来检查问题的代码吗?那么修复问题的代码呢? - Vic Seedoubleyew
@VicSeedoubleyew 我向用户返回了一条消息,说明访问该资源需要用户名和密码。 - Sean H
显示剩余9条评论

-1
在 PHP 中,启动 Apache 然后写入你的数据库名称和密码(如果存在于你的环境中 .env 文件)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接