“注意:未定义变量”,“注意:未定义索引”,“警告:未定义的数组键”和“注意:未定义偏移量”使用PHP

1357

我正在运行一个PHP脚本,但一直收到以下错误:

注意:在C:\wamp\www\mypath\index.php的第10行中未定义变量my_variable_name

注意:在C:\wamp\www\mypath\index.php的第11行中未定义索引my_index

警告:在C:\wamp\www\mypath\index.php的第11行中未定义数组键"my_index"

第10和11行如下所示:

echo "My variable value is: " . $my_variable_name;
echo "My index value is: " . $my_array["my_index"];

这些错误信息的意思是什么?
它们为什么突然出现?我用了这个脚本好多年,从来没有遇到过任何问题。
我该如何修复它们?
这是一个通用参考问题,供人们链接重复问题,而不必一遍又一遍地解释问题。我觉得这是必要的,因为大多数关于这个问题的现实答案都非常具体。
相关的Meta讨论:
- 如何解决重复问题? - “参考问题”有意义吗?

15
可能是 参考资料 - PHP 中这个错误是什么意思? 的重复问题。 - user229044
4
这个变量可能还没有被初始化。你是否正在从POST、GET或任何数组中初始化这个变量?如果是这种情况,你可能没有在访问的数组中拥有相应的字段。 - Anish Silwal
4
@Pekka웃 - 我注意到编辑中添加了“Notice: Undefined offset”这一内容 - 是否使用“PHP:“未定义变量”,“未定义索引”,“未定义偏移”通知”更合理呢?(可以省略PHP,因为它已被标记为“php”。此外,URL在“and-notice-undef”处被截断,建议调整以避免截断。甚至可以删除(过多的)引号,或者改为“PHP:未定义变量/索引/偏移通知”。 - Funk Forty Niner
4
@Fred,我认为两种变体都可以辩解。新手可能会将整个行,包括“注意:”部分输入他们的搜索查询中,我相信这是此问题的主要流量生成器。如果消息完整存在,那么在搜索引擎中的可见性可能会得到提高。 - Pekka
3
我明白了。我之前说那个是因为URL没有被切断,现在它被切断了,只到 and-notice-undef。这只是一些建议。它只是重复自己,并且是“注意:未定义”。 - Funk Forty Niner
显示剩余7条评论
29个回答

1214

这个错误信息的作用是帮助PHP程序员发现访问一个不存在的变量(或数组元素)时产生的拼写错误或其他错误。因此一个好的程序员:

  1. 确保每个变量或数组键在使用之前已经定义。如果需要在函数内部使用变量,则必须将其作为参数传递给该函数。
  2. 注意这个错误并修复它,就像处理其他任何错误一样。这可能表示拼写错误或某些过程没有返回它应该返回的数据。
  3. 仅在极少数情况下,当事情不在程序员的控制之下时,可以添加代码来规避此错误。但绝不能成为机械而盲目的习惯。

Notice / Warning: Undefined variable

虽然PHP不要求变量声明,但建议这么做以避免一些安全漏洞或漏掉给变量赋值的bug,导致在脚本后期使用变量时忘记给予初值等问题。在变量未声明的情况下,PHP会发出E_WARNING级别的错误。

这个警告可以帮助程序员找出拼写错误的变量名称或类似的错误(例如一个变量在计算为假时被赋值)。除此之外,未初始化变量可能存在其他问题。正如 PHP 手册中所的那样:

依赖未初始化变量的默认值在包含一个文件到另一个文件时使用相同的变量名会出现问题。

这意味着变量可能会从被包含的文件中获取值,并且将使用该值代替访问未初始化变量时预期的 null 值,这可能导致不可预测的结果。为避免这种情况,最好在使用之前初始化 PHP 文件中的所有变量。

处理这个问题的方法:

  1. 建议:在使用变量之前先声明。这样,只有在实际犯错并尝试使用不存在的变量时,您才会看到此错误 - 这正是此错误消息存在的原因。

    //初始化一个变量
    $value = ""; //初始化值;int为0,array为[]等
    echo $value; //没有错误
    echo $vaule; //错误指出了拼写错误的变量名
    
  • 一个特殊情况是变量在函数中定义但不可见。PHP 函数拥有自己的 变量作用域,如果你需要在函数中使用外部的变量,那么它的值必须作为函数参数传递:

    function test($param) {
        return $param + 1; 
    }
    $var = 0;
    echo test($var); // 现在 $var 的值通过 $param 变量可以在函数内部访问
    
  • 使用 null 合并运算符来抑制错误。但请记住,以这种方式使用 PHP 将无法向您通知使用错误的变量名。

    // Null 合并运算符
    echo $value ?? '';
    

    对于古老的 PHP 版本(< 7.0),可以使用 isset() 和三元运算符。

    echo isset($value) ? $value : '';
    

    注意:尽管它仅用于屏蔽一个特定的错误,但它仍然实质上是一种错误抑制方法。因此,它可能会防止 PHP 帮助您标记未初始化的变量。

  • 使用 @ 运算符 抑制错误。由于历史原因而保留,但严重来说,这应该不会发生。

  • 注意:强烈建议只实现第1点。

    注意:未定义索引/未定义偏移/警告:未定义数组键

    当您(或 PHP)尝试访问数组的未定义索引时,将出现此提示/警告。

    内部数组

    处理内部数组时,应采取完全相同的态度:在使用之前仅初始化所有键。这样,这个错误会完成其既定任务:通知程序员其代码中的错误。因此,方法相同:

    建议:声明数组元素:

        //Initializing a variable
        $array['value'] = ""; //Initialization value; 0 for int, [] for array, etc.
        echo $array['value']; // no error
        echo $array['vaule']; // an error indicates a misspelled key
    

    一个特殊情况是当某个函数返回数组或其他值,例如nullfalse。在尝试访问数组元素之前,必须进行测试。
    $row = $stmt->fetch();
    if ($row) { // the record was found and can be worked with
        echo $row['name']; 
    }
    

    外部数组

    对于外部数组(例如 $_POST / $_GET / $_SESSION 或 JSON 输入),情况有所不同,因为程序员无法控制这些数组的内容。因此,检查某些键是否存在,甚至为缺失的键分配默认值是可以被证明是合理的。

    • 当一个 PHP 脚本包含一个 HTML 表单时,第一次加载时自然没有表单内容。因此,这样的脚本应该检查是否提交了表单

        // 对于 POST 表单,请检查请求方法
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            // 处理表单
        }
        // 对于 GET 表单/链接,请检查重要字段
        if (isset($_GET['search'])) {
            // 处理表单
        }
      
    • 一些 HTML 表单元素(如复选框)如果未被选中,则不会发送到服务器。在这种情况下,使用空合并运算符分配默认值是合理的

        $agreed = $_POST['terms'] ?? false;
      
    • 可选的查询字符串元素或 cookie 应该以相同的方式处理

        $limit = $_GET['limit'] ?? 20;
        $theme = $_COOKIE['theme'] ?? 'light';
      
    但是赋值应该在脚本的开头完成。 验证所有输入,将其分配给本地变量,并在代码中使用它们。 因此,您要访问的每个变量都会有意存在。
    相关:

    10
    一些想法: "shut-up operator"(@)存在一些性能问题。 另外,由于它在特定范围内抑制了所有错误,如果不小心使用它,可能会掩盖您希望看到的消息。 - IMSoP
    6
    隐藏问题并不是解决问题的方法。第二至第四条只能在生产服务器上使用,而不能普遍适用。 - Salman A
    21
    为什么建议使用第一种方式 $value = isset($_POST['value']) ? $_POST['value'] : '';,而不是第二种方式 $value = @$_POST['value']; - forsvunnet
    2
    我不建议在数组中使用isset(),例如$str = '111';,(我知道它应该是数组)isset($str[0])将返回true。最好使用array_key_exist()而不是isset()。 - M Rostami
    1
    我们也可以使用 $_REQUEST += ['my_index' => null, '...' => null] 在使用之前来填充缺失的索引。当你已经知道索引键时可以这样做。 - ZenithS
    显示剩余6条评论

    171

    尝试这些方法

    问题1:此提示意味着$varname在当前脚本范围内未定义。

    问题2:在使用任何可疑变量之前,先使用isset()、empty()条件可以很好地起到作用。

    // recommended solution for recent PHP versions
    $user_name = $_SESSION['user_name'] ?? '';
    
    // pre-7 PHP versions
    $user_name = '';
    if (!empty($_SESSION['user_name'])) {
         $user_name = $_SESSION['user_name'];
    }
    

    或者,作为一个快速而不太规范的解决方案:

    // not the best solution, but works
    // in your php setting use, it helps hiding site wide notices
    error_reporting(E_ALL ^ E_NOTICE);
    

    关于 sessions 的注意事项:


    如果从php.ini配置文件中使用E_NOTICE,则执行error_reporting = (E_ALL&〜E_NOTICE) - ahmd0
    1
    这个答案正在Meta上讨论。 (https://meta.stackoverflow.com/questions/370050/potential-issues-with-an-old-well-upvoted-wiki-answer-delete-edit-or-leave) - Script47
    从上面的答案中,我尝试了isset、array_key_exists,但它们都不起作用。我尝试了你的答案,.empty(),它起作用了。非常感谢! - user13137957
    非常感谢。我已经添加了"error_reporting(E_ALL ^ E_NOTICE);",这对我很有效。 - Ganesan J

    80

    错误显示 @ 操作符

    为了避免不必要和冗余的通知,可以使用专用的@ 操作符来“隐藏”未定义的变量/索引消息。

    $var = @($_GET["optional_param"]);
    
    • 这通常是不鼓励的。新手往往过度使用它。
    • 它在应用逻辑深处的代码中非常不适当(忽略你不应该声明的变量),例如函数参数或循环中。
    • 然而,与isset?:??相比,有一个优点。提示仍然可以被记录下来。并且可以通过set_error_handler("var_dump");解除隐藏提示。
      • 此外,在初始代码中不应习惯性地使用/推荐if (isset($_POST["shubmit"]))
      • 新手不会注意到这样的拼写错误。这只会让你失去PHP提供的那些提示。在验证功能之后,才添加 @isset
      • 首先修复原因,而不是提示。

    • @主要适用于$_GET/$_POST输入参数,特别是如果他们是可选的

    由于这涵盖了大部分相关问题,让我们扩展最常见的原因:

    $_GET/$_POST/$_REQUEST未定义的输入

    • First thing you do when encountering an undefined index/offset, is check for typos:
      $count = $_GET["whatnow?"];

      • Is this an expected key name and present on each page request?
      • Variable names and array indicies are case-sensitive in PHP.
    • Secondly, if the notice doesn't have an obvious cause, use var_dump or print_r to verify all input arrays for their curent content:

      var_dump($_GET);
      var_dump($_POST);
      //print_r($_REQUEST);
      

      Both will reveal if your script was invoked with the right or any parameters at all.

    • Alternativey or additionally use your browser devtools (F12) and inspect the network tab for requests and parameters:

      browser developer tools / network tab

      POST parameters and GET input will be be shown separately.

    • For $_GET parameters you can also peek at the QUERY_STRING in

      print_r($_SERVER);
      

      PHP has some rules to coalesce non-standard parameter names into the superglobals. Apache might do some rewriting as well. You can also look at supplied raw $_COOKIES and other HTTP request headers that way.

    • More obviously look at your browser address bar for GET parameters:

      http://example.org/script.php?id=5&sort=desc

      The name=value pairs after the ? question mark are your query (GET) parameters. Thus this URL could only possibly yield $_GET["id"] and $_GET["sort"].

    • Finally check your <form> and <input> declarations, if you expect a parameter but receive none.

      • Ensure each required input has an <input name=FOO>
      • The id= or title= attribute does not suffice.
      • A method=POST form ought to populate $_POST.
      • Whereas a method=GET (or leaving it out) would yield $_GET variables.
      • It's also possible for a form to supply action=script.php?get=param via $_GET and the remaining method=POST fields in $_POST alongside.
      • With modern PHP configurations (≥ 5.6) it has become feasible (not fashionable) to use $_REQUEST['vars'] again, which mashes GET and POST params.
    • If you are employing mod_rewrite, then you should check both the access.log as well as enable the RewriteLog to figure out absent parameters.

    $_FILES

    • 对于文件上传和$_FILES["formname"],同样适用于合理性检查。
    • 此外,检查enctype=multipart/form-data
    • 还要在<form>声明中检查method=POST
    • 另请参阅:PHP Undefined index error $_FILES?

    $_COOKIE

    • setcookie()之后,$_COOKIE数组永远不会被填充,只有在任何后续的HTTP请求中才会被填充。
    • 此外,它们的有效期会超时,可能会受到子域或个别路径的限制,用户和浏览器可能会拒绝或删除它们。

    2
    如果你好奇性能影响是什么,这篇文章总结得很好,链接为 http://derickrethans.nl/five-reasons-why-the-shutop-operator-should-be-avoided.html。 - Gajus
    @GajusKuizinas 自2009年以来,已经有相当多的变化,特别是http://php.net/ChangeLog-5.php#5.4.0彻底改变了结果(参见“Zend Engine,performance”和“(silence) operator”)。 - mario
    感谢@mario,很有趣。现在,如果有人足够好来对这两个进行基准测试... http://3v4l.org/CYVOn/perf#tabs http://3v4l.org/FLp3D/perf#tabs 根据这个测试,它们似乎是相同的(注意比例变化)。 - Gajus

    54

    通常是因为“糟糕的编程”而导致的,会出现错误的可能性,无论是现在还是将来。

    1. 如果这是一个错误,请先正确地分配变量:$varname=0;
    2. 如果它确实只有时而存在,请在使用它之前进行测试:if (isset($varname))
    3. 如果是因为拼写错误,请纠正它
    4. 甚至可以在您的PHP设置中关闭警告

    7
    请不要关闭警告。在更严格的编程语言中,警告通常意味着“这一行代码可能存在缺陷,你最好检查两遍”;而在像 PHP 这样宽容的语言中,警告则往往意味着“这段代码很糟糕,可能有很多漏洞;我会试着理解它,但你最好尽快修复它”。为了保持原意,我已经尽量使翻译简明扼要并易于理解,希望对您有所帮助。 - user395760
    5
    虽然我同意前三点,但第四点是完全错误的。掩盖问题并不能使它消失,甚至可能会引发更多后续问题。 - Valentin Flachsel
    2
    @Freek 绝对正确,但在某些情况下(购买脚本,零技术知识,需要明天运行...)这是一种临时解决方案 - 真的很糟糕,这总是需要强调的,但也是一个选择。 - Pekka

    48

    这意味着你正在测试、评估或打印一个还未被赋值的变量。这可能是因为你打错了字,或者需要先检查该变量是否被初始化。请检查逻辑路径,它可能在一个路径中被设置,但在另一个路径中没有。


    38

    我不想禁用警告,因为它很有帮助,但我想避免输入太多的代码。

    我的解决方案是这个函数:

    function ifexists($varname)
    {
      return(isset($$varname) ? $varname : null);
    }
    

    如果我想引用 $name 并在存在时输出,我只需要写:

    <?= ifexists('name') ?>
    

    对于数组元素:

    function ifexistsidx($var,$index)
    {
      return(isset($var[$index]) ? $var[$index] : null);
    }
    

    如果我想在页面中引用 $_REQUEST['name']:

    <?= ifexistsidx($_REQUEST, 'name') ?>
    

    2
    你的ifexists()函数在PHP 5.3中对我不起作用。调用者的变量在函数的本地作用域中不可用(请参见_Variable scope_),除非它们是_Superglobals_或您使用_$GLOBALS_进行操作,因此$foo =“BABAR”; ifexists('foo');通常会返回null。(斜体为php.net章节。) - skierpage
    现在你将得到“来自hello”的信息...这有什么意义吗?只需检查值if( !empty($user) and !empty($location) ) echo "hello $user ..." - gcb

    31

    获取输入字符串的最佳方法是:

    $value = filter_input(INPUT_POST, 'value');
    

    这个一行代码与下面的代码几乎等价:

    if (!isset($_POST['value'])) {
        $value = null;
    } elseif (is_array($_POST['value'])) {
        $value = false;
    } else {
        $value = $_POST['value'];
    }
    
    如果您绝对需要一个字符串值,就像这样:
    $value = (string)filter_input(INPUT_POST, 'value');
    

    31

    '$user_location'变量未被定义。如果你在内部使用任何if循环来声明'$user_location'变量,那么你必须也有一个else循环并定义相同的变量。例如:

    $a = 10;
    if($a == 5) {
        $user_location = 'Paris';
    }
    else {
    }
    echo $user_location;
    

    由于if循环未被满足,而在else循环中并未定义'$user_location'变量,因此上述代码将会出错。尽管如此,PHP被要求输出该变量的值。为修改代码,您必须执行以下操作:

    $a = 10;
    if($a == 5) {
        $user_location='Paris';
    }
    else {
        $user_location='SOMETHING OR BLANK';
    }
    echo $user_location;
    

    30

    回复:“为什么它们突然出现了?我用这个脚本已经很多年了,以前从来没有出过问题。”

    大多数网站都使用“默认”的错误报告,“显示所有错误,但不包括‘注意’和‘废弃’”。这将在php.ini中设置,并适用于服务器上的所有网站。这意味着那些在示例中使用的“注意”将被抑制(隐藏),而其他被认为更关键的错误将被显示/记录。

    另一个关键的设置是错误可以被隐藏(即display_errors设置为“off”或“syslog”)。

    在这种情况下会发生什么是error_reporting可能被改变以显示通知(如示例所示),和/或设置被更改为在屏幕上显示display_errors(而不是禁止它们/记录它们)。

    为什么它们改变了?

    显而易见/最简单的答案是:某人在php.ini中调整了这些设置,或者升级后的PHP正在使用一个与以前不同的php.ini。那是第一个需要查看的地方。

    然而,还有可能在以下位置覆盖这些设置:

    • .htconf(web服务器配置,包括虚拟主机和子配置)*
    • .htaccess
    • 在php代码本身中

    这些设置也可能被更改。

    还有一个额外的复杂性,即Web服务器配置可以启用/禁用.htaccess指令,因此如果您的.htaccess指令突然开始/停止工作,则需要检查该配置。

    (.htconf / .htaccess假定您正在运行为apache。如果运行命令行,则不适用;如果运行IIS或其他Web服务器,则需要相应地检查这些配置)

    总结

    • 检查php.ini中的error_reportingdisplay_errors php指令是否已更改,或者您是否使用与以前不同的php.ini。
  • 检查 .htconf(或虚拟主机等)中的 error_reportingdisplay_errors php 指令是否已更改
  • 检查 .htaccess 中的 error_reportingdisplay_errors php 指令是否已更改
  • 如果您在 .htaccess 中有指令,请检查它们是否仍然允许在 .htconf 文件中
  • 最后检查您的代码;可能是一个不相关的库,检查是否在那里设置了 error_reportingdisplay_errors php 指令。

  • 我们遇到了与这类似的行为变化,这是由于我们代码中的自定义error_handler导致error_reporting表现出意外的行为,而我们之前并不知情。 - xdhmoore

    25

    快速的解决方法是在代码顶部将您的变量赋值为null:

    $user_location = null;
    

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