只有变量应该通过引用传递

320
// Other variables
$MAX_FILENAME_LENGTH = 260;
$file_name = $_FILES[$upload_name]['name'];
//echo "testing-".$file_name."<br>";
//$file_name = strtolower($file_name);
$file_extension = end(explode('.', $file_name)); //ERROR ON THIS LINE
$uploadErrors = array(
    0=>'There is no error, the file uploaded with success',
    1=>'The uploaded file exceeds the upload max filesize allowed.',
    2=>'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form',
    3=>'The uploaded file was only partially uploaded',
    4=>'No file was uploaded',
    6=>'Missing a temporary folder'
);

有什么想法吗?两天过去了还是卡住了。


7
提供更好的解释原因:https://vijayasankarn.wordpress.com/2017/08/28/php-only-variables-should-be-passed-by-reference/只有变量应该通过引用传递,因为PHP函数中的参数默认情况下是按值传递的。这意味着将变量作为参数传递时,将创建一个新的副本,其值与原始变量相同。当您尝试在函数内部更改参数的值时,实际上只是更改了该副本的值,而不会对原始变量产生任何影响。为了避免出现这种情况,可以通过引用传递变量,从而使函数能够直接操作原始变量,而不是副本。 - Anant
警告:自 PHP 7.4(可能是早期版本),将变量内联分配为通过引用传递的函数参数会导致此通知级别错误。例如:https://onlinephp.io/c/4c871 - Troy Niemeier
14个回答

654

explode 的结果分配给一个变量,并将该变量传递给 end

$tmp = explode('.', $file_name);
$file_extension = end($tmp);
问题是,end需要一个引用,因为它修改数组的内部表示方式(即使当前元素指针指向最后一个元素)。

explode('.', $file_name)的结果不能转换为引用。这是PHP语言中的限制,可能是为了简化原因而存在的。


1
@Oswald,我们可以使用error_reporting关闭警告。这样做安全吗? - Pacerier
10
关闭 error_reporting 是安全的。但盲目忽略错误是不安全的。关闭 error_reporting 是向盲目忽略错误迈出的重要一步。在生产环境中,应该关闭 display_errors 并将错误写入日志文件。 - Oswald
不起作用。 下面的答案 - 双括号 - 有效。 - bbe
如何关闭这些通知呢?我尝试忽略 E_NOTICE 但它们仍然出现。 - NaturalBornCamper
每一天我都有点讨厌PHP... :-( - MarcoS

67

其他人已经告诉了您为什么会出现错误,但是这是实现您想要的最佳方法:

$file_extension = pathinfo($file_name, PATHINFO_EXTENSION);

4
我同意。如果有适当的API可以处理文件路径,那么使用字符串操作来解析文件路径是没有意义的。 - gd1
2
这对我来说是最好的答案,字符串操作只会在代码中增加一些混乱。 - oussama benounnas

59

Php 7兼容的正确用法:

$fileName      = 'long.file.name.jpg';
$tmp           = explode('.', $fileName);
$fileExtension = end($tmp);

echo $fileExtension;
// jpg

3
奇怪,那样做可行,但是为什么?它是否抑制了警告,类似于@前缀的作用? - Nigel Alderton
6
为什么加一个额外的括号会消除错误? - Nigel Alderton
9
我研究了这个问题,似乎是 php解析器 中的一个 **bug?**,其中双括号 "(())" 导致引用被转换为普通值。更多信息请参见此链接 - Callistino
32
我喜欢这个..但同时也不喜欢。谢谢你毁了我的一天:-) - But those new buttons though..
6
在PHP 7中仍将发出警告。http://php.net/manual/en/migration70.incompatible.php#migration70.incompatible.variable-handling.parentheses - kosta
显示剩余2条评论

31
将explode()函数的结果保存到一个变量中,然后对该变量调用end()函数。
$tmp = explode('.', $file_name);
$file_extension = end($tmp);

顺便说一下:我使用这段代码来获取文件扩展名:

$ext = substr( strrchr($file_name, '.'), 1);

strrchr 函数提取最后一个 . 后面的字符串,而 substr 函数则把 . 前面的内容截取掉。


23

在其他地方给出的答案:

$tmp = explode('.', $fileName);
$file_extension = end($tmp);

是正确且有效的。它实现了您想要做的事情。

为什么?

end() 函数并不完全执行您想要的操作, 这与PHP中的array数据结构的工作原理有关,通常情况下看不见,但PHP中的数组包含指向一个当前元素的指针,该指针用于迭代(例如使用 foreach)。

要使用 end(),必须拥有一个实际的数组,其带有(通常是隐式的)当前元素指针。 end() 函数物理上修改该指针。

explode() 的输出不是实际的数组。它是一个函数输出。因此,您不能运行 end(explode()),因为这会违反语言要求。

简单地将 explode() 的输出设置为一个变量 创建了您要查找的数组。该创建的数组具有当前元素指针。现在,一切又恢复正常了。

那么括号呢?

这不是一个错误。再次说明,这是语言要求。

额外的括号(如 end((explode())))不仅仅是分组。它们创建了一个内联实例变量,就像将函数输出设置为变量一样。您可以将其视为立即执行的lambda函数。

这是另一个正确且有效的解决方案。它可能是更好的解决方案,因为它占用的空间更少。良好的审核员或维护人员应该能够理解当他们看到额外的括号时,您要做的事情。

如果您使用类似 PHPCS 的 linter 或 SCA 程序,那么可能会不喜欢额外的括号,具体取决于您使用的 linting 配置文件。这是您的linter,请告诉它为您做什么。

其他答案还列出了像展开操作符或 array_key_last() 这样的东西,这些也是合理的解决方案。它们可能完全有效,但使用和阅读起来比较复杂。

我只想使用前缀@

这个解决方案是有效的,但不正确。这是有效的,因为它解决了问题。这是它的全部优点。

抑制错误总是不好的做法。 有许多原因。一个非常大的原因是,您试图抑制一个特定的错误条件(一个您创建的),但错误抑制前缀抑制所有错误。

在这种情况下,您可能会摆脱这个问题。然而,从事不良编程习惯是欺骗行为,并且很可能会导致您今后更多和更大的欺骗行为。您将对糟糕的代码负责。但我不是代码警察,这是您的代码。它有效,因为它解决了问题。

好吧,那么最好的解决方案呢?

按照@ryeguy的建议做。不要对已经为您解决


13

由于它已经引起了超过10年的注意,但工作正常并返回预期值,所以一点stfu运算符是你们所有人都在寻找的最佳不良实践:

$file_extension = @end(explode('.', $file_name));

但是要注意,由于性能问题,不要在循环中使用。PHP 7.3+的最新版本提供了array_key_last()array_key_first()方法。

https://www.php.net/manual/en/function.array-key-last.php

                 uuuuuuu
             uu$$$$$$$$$$$uu
          uu$$$$$$$$$$$$$$$$$uu
         u$$$$$$$$$$$$$$$$$$$$$u
        u$$$$$$$$$$$$$$$$$$$$$$$u
       u$$$$$$$$$$$$$$$$$$$$$$$$$u
       u$$$$$$$$$$$$$$$$$$$$$$$$$u
       u$$$$$$"   "$$$"   "$$$$$$u
       "$$$$"      u$u       $$$$"
        $$$u       u$u       u$$$
        $$$u      u$$$u      u$$$
         "$$$$uu$$$   $$$uu$$$$"
          "$$$$$$$"   "$$$$$$$"
            u$$$$$$$u$$$$$$$u
             u$"$"$"$"$"$"$u
  uuu        $$u$ $ $ $ $u$$       uuu
 u$$$$        $$$$$u$u$u$$$       u$$$$
  $$$$$uu      "$$$$$$$$$"     uu$$$$$$
u$$$$$$$$$$$uu    """""    uuuu$$$$$$$$$$
$$$$"""$$$$$$$$$$uuu   uu$$$$$$$$$"""$$$"
 """      ""$$$$$$$$$$$uu ""$"""
           uuuu ""$$$$$$$$$$uuu
  u$$$uuu$$$$$$$$$uu ""$$$$$$$$$$$uuu$$$
  $$$$$$$$$$""""           ""$$$$$$$$$$$"
   "$$$$$"                      ""$$$$""
     $$$"                         $$$$"

10

尝试这个:

$parts = explode('.', $file_name);
$file_extension = end($parts);

原因在于end的参数是通过引用传递的,因为end修改了数组,使其内部指针指向最后一个元素。如果您没有传入变量,则没有引用指向任何东西。

有关更多信息,请参见PHP手册中的end


9

end(...[explode('.', $file_name)])自PHP 5.6以来就可以使用。这在RFC中有记录,但在PHP文档中没有详细说明。


我喜欢这个一行解决方案。 - Luciano Fantuzzi

9

PHP提示出错,因为end()需要一个引用来改变其想要更改的内容(只能是变量)。然而,您直接将explode()的结果传递给end(),而没有先将其保存到变量中。当explode()返回您的值时,它仅存在于内存中,没有变量指向它。您无法创建对不存在的事物(或对未知于内存中的事物)的引用。

换句话说:PHP不知道您提供的值是直接值还是值的指针(指针也是变量(整数),它存储实际值所在的内存偏移量)。因此,PHP始终期望这里是一个指针(引用)。

但由于这在 PHP 7 中仍然只是一个通知(甚至不过时),因此您可以安全地忽略通知并使用忽略运算符,而不是完全停用错误报告。

$file_extension = @end(explode('.', $file_name));

3
@OskarCalvo 这也是我的哲学。但这不是一个错误——PHP将其视为“注意事项”。这是其他答案的替代“解决方案”,但没有人直接提到它。更好的方法是将explode的值保存到临时变量中,就像其他人在这里写的一样。但再说一遍:这不是一个错误,所以使用这个操作符是可以的。PHP通常处理错误很差。因此,我建议使用 set_error_handlerset_exception_handler 来进行错误处理,并作为最干净的解决方案。 - wizard
1
这是一个糟糕的答案。错误可能随处可见 - 例如,$file_name 可能不适合用于 explode 或不存在。但是 @ 将会压制它们。 - Your Common Sense
我不会给这个答案点踩,因为它算是一个有效的回答,但这绝对是一个非常糟糕的建议。如果发生了什么事情,你以后可能会很生气地调试代码…… - Luciano Fantuzzi
@YourCommonSense 你可能不喜欢所给的建议,我同意。但是答案解释了消息的_起源_,这在SO答案中经常缺失。当我理解发生了什么时,我可以自行决定是否采纳该建议。我自己不想使用@,但我认为这个答案很好。 - Lutz Prechelt

4

就像你不能立即索引数组一样,也不能立即调用它的end方法。先将其赋值给一个变量,然后再调用end方法。

$basenameAndExtension = explode('.', $file_name);
$ext = end($basenameAndExtension);

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