准备好南瓜糖和苹果酒了吗?一年一度的万圣节又到了!虽然全球各地庆祝万圣节的狂热程度不及美国,但我还是想分享一些“恐怖”的PHP技巧来庆祝这个节日。这篇文章轻松有趣,将向您展示PHP自身的一些令人惊讶(但合乎逻辑)的行为,以及一些人利用PHP完成任务时那些令人毛骨悚然(且可能非常不合逻辑)的方法。您可以把它看作我的节日礼物,一点程序员的“精神糖果”——毕竟,为什么只有不给糖就捣蛋的小朋友才能享用所有美味呢?
要点总结
foreach
循环之外保留引用,导致输出结果出乎意料。可以通过使用数组的键将字符串重新赋值来缓解这个问题。闹鬼的数组
从前,在一个不那么遥远的开发工作室里,亚瑟深夜还在编写代码。他不知道自己即将使用的数组闹鬼了!每敲击一次键盘,他都感到一阵寒意从脊椎滑落,但他愚蠢地忽略了这种微妙的预感。
<code class="language-php"><?php $spell = array("double", "toil", "trouble", "cauldron", "bubble"); foreach ($spell as &$word) { $word = ucfirst($word); } foreach ($spell as $word) { echo $word . "n"; }</code>
好吧,这个数组并没有真正闹鬼,但输出结果确实出乎意料:
<code>Double Toil Trouble Cauldron Cauldron</code>
这种“恐怖”行为的原因在于PHP如何在第一个foreach
循环之外保留引用。当第二个循环开始时,$word
仍然是引用,指向数组的最后一个元素。第二个循环的第一次迭代将“double”赋值给$word
,这覆盖了最后一个元素。第二次迭代将“toil”赋值给$word
,再次覆盖最后一个元素。当循环读取最后一个元素的值时,它已经被多次覆盖了。要深入了解这种行为,我建议阅读Johannes Schlüter关于这个主题的博客文章,“References and foreach”。您还可以运行这个稍微修改过的版本并检查其输出,以便更好地了解PHP正在做什么:
<code class="language-php"><?php $spell = array("double", "toil", "trouble", "cauldron", "bubble"); foreach ($spell as &$word) { $word = ucfirst($word); } foreach ($spell as $word) { echo $word . "n"; }</code>
亚瑟那天晚上吸取了一个非常重要的教训,并使用数组的键将字符串重新赋值来修复了他的代码:
<code>Double Toil Trouble Cauldron Cauldron</code>
幽灵数据库连接
PHP越来越多的被要求不仅仅是每天生成网页。用PHP编写的shell脚本数量正在增加,这些脚本执行的任务也越来越复杂,因为开发人员看到了整合开发语言的优点。通常情况下,这些脚本的性能是可以接受的,为了方便而进行的权衡是可以证明的。因此,苏珊正在编写一个并行处理任务,其代码类似于以下代码:
<code class="language-php"><?php $spell = array("double", "toil", "trouble", "cauldron", "bubble"); foreach ($spell as &$word) { $word = ucfirst($word); } var_dump($spell); foreach ($spell as $word) { echo join(" ", $spell) . "n"; }</code>
她的代码fork了子进程来并行执行一些长时间运行的工作,而父进程继续监控子进程,并在所有子进程终止时报告结果。
<code class="language-php"><?php foreach ($spell as $key => $word) { $spell[$key] = ucfirst($word); }</code>
然而,苏珊的领导要求她将状态信息记录到日志中,而不是输出到标准输出。苏珊使用已经包含在公司代码库中的单例模式PDO数据库连接机制扩展了她的代码。
<code class="language-php">#! /usr/bin/env php <?php $pids = array(); foreach (range(0, 4) as $i) { $pid = pcntl_fork(); if ($pid > 0) { echo "Fork child $pid.n"; // record PIDs in reverse lookup array $pids[$pid] = true; } else if ($pid == 0) { echo "Child " . posix_getpid() . " working...n"; sleep(5); exit; } } // wait for children to finish while (count($pids)) { $pid = pcntl_wait($status); echo "Child $pid finished.n"; unset($pids[$pid]); } echo "Tasks complete.n";</code>
苏珊期望看到timings
表中的行被更新;“start time”行应该列出整个进程启动的时间戳,“stop time”行应该列出所有进程完成的时间戳。不幸的是,执行抛出了异常,数据库没有反映她的预期。
<code>Fork child 1634. Fork child 1635. Fork child 1636. Child 1634 working... Fork child 1637. Child 1635 working... Child 1636 working... Fork child 1638. Child 1637 working... Child 1638 working... Child 1637 finished. Child 1636 finished. Child 1638 finished. Child 1635 finished. Child 1634 finished. Tasks complete.</code>
<code class="language-php">#! /usr/bin/env php <?php $db = Db::connection(); $db->query("UPDATE timings SET tstamp=NOW() WHERE name='start time'"); $pids = array(); foreach (range(0, 4) as $i) { ... } while (count($pids)) { ... } $db->query("UPDATE timings SET tstamp=NOW() WHERE name='stop time'"); class Db { protected static $db; public static function connection() { if (!isset(self::$db)) { self::$db = new PDO("mysql:host=localhost;dbname=test", "dbuser", "dbpass"); self::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } return self::$db; } }</code>
像亚瑟的数组一样,苏珊的数据库也闹鬼了吗?好吧,如果我给你以下线索,看看你能不能把这个谜团拼凑起来:1. 当一个进程fork时,父进程会被复制为子进程。然后,这些复制的进程从那时起并行执行。2. 静态成员在类的所有实例之间共享。
PDO连接被包装为单例,因此应用程序中对它的任何引用都指向内存中的相同资源。DB::connection()
首先返回对象引用,父进程fork,子进程继续处理,而父进程等待,子进程终止并PHP清理使用的资源,然后父进程尝试再次使用数据库对象。连接到MySQL的连接已在子进程中关闭,因此最终调用失败。在最终日志记录查询之前天真地尝试再次获取连接不会帮助苏珊,因为会返回相同的失效PDO实例,因为它是一个单例。我建议避免使用单例——它们实际上只不过是花哨的面向对象的全局变量,这会使调试变得困难。即使在我们的例子中,连接仍然会被子进程关闭,但如果在第二个查询之前调用DB::connection()
,如果不用单例,它至少会返回一个新的连接。但更好的方法是理解在fork时执行环境是如何克隆的,以及各种资源如何在所有进程中受到影响。在这种情况下,最好在fork子进程后在父进程中连接到数据库,子进程如有需要则自行连接。连接不应该共享。
<code>PHP Fatal error: Uncaught exception 'PDOException' with message 'SQLSTATE[HY000]: General error: 2006 MySQL server has gone away' in /home/susanbrown/test.php:21 Stack trace: #0 /home/susanbrown/test.php(21): PDO->query('UPDATE timers S...') #1 {main}</code>
弗兰肯斯坦博士的API
玛丽·雪莱的《弗兰肯斯坦》讲述的是一个科学家创造生命的故事,但他对其丑陋感到厌恶,于是抛弃了它。在一些不必要的死亡和破坏之后,弗兰肯斯坦博士追捕他的创造物,直到世界的尽头,试图摧毁它。我们许多人都赋予了如此丑陋的代码以生命,以至于我们后来希望自己能逃离它——代码如此丑陋、如此愚钝、如此混乱,以至于让我们想呕吐,但它只想要爱和理解。几年前,我一直在玩弄一个关于数据库接口的想法,以及如果它们更严格地遵守Unix的“一切都是文件”的哲学,它们会是什么样子:查询将被写入“文件”,结果集将从“文件”中读取。一件事导致另一件事,经过我自己的一些死亡和破坏性编码之后,我编写了以下这个与我最初的想法几乎没有关系的类:
<code class="language-php"><?php $spell = array("double", "toil", "trouble", "cauldron", "bubble"); foreach ($spell as &$word) { $word = ucfirst($word); } foreach ($spell as $word) { echo $word . "n"; }</code>
结果是天才的,但令人厌恶:一个看起来像对象(没有真正的API方法)、数组或字符串的实例……
<code>Double Toil Trouble Cauldron Cauldron</code>
我此后不久写了一篇博客,并将其标记为邪恶的。看到它的朋友和同事几乎都做出了同样的反应:“太棒了!现在把它杀了……用火烧死它。” 但多年以来,我承认我对它有所软化。它真正违反的唯一规则是程序员对query()
和result()
等平淡无奇的命名方法的期望。相反,它使用查询字符串本身作为查询方法,对象是接口,结果集是结果。当然,它并不比过度泛化的ORM接口更糟糕,ORM接口将select()
和where()
方法链接在一起,看起来像SQL查询,但有更多的->
。也许我的类并没有那么邪恶?也许它只是想被爱?我当然不想死在北极!
结束语
我希望您喜欢这篇文章,并且这些例子不会给您带来(太多)噩梦!我相信您也有自己关于闹鬼或可怕代码的故事,无论您身在何处,都不需要让节日的乐趣消失,因此请随时在下面的评论中分享您可怕的PHP故事!图片来自Fotolia
(以下为FAQ,已根据原文内容调整和精简)
关于“Spooky Scary PHP”的常见问题
什么是“Spooky Scary PHP”?
“Spooky Scary PHP”是一种独特的PHP编码方法,它涉及使用非常规或意想不到的方法来实现某些结果。这可能包括使用鲜为人知的函数、利用语言中的特性,甚至使用看起来不应该工作但确实有效的代码。这是一种有趣且令人兴奋的方式,可以探索PHP的深度,并且经常会带来令人惊讶和启迪的发现。
如何开始学习“Spooky Scary PHP”?
学习“Spooky Scary PHP”的最佳方法是对PHP基础知识有扎实的理解。一旦您对基础知识感到满意,就可以开始探索该语言更晦涩的角落。阅读关于“Spooky Scary PHP”的文章、教程和论坛讨论也可能非常有帮助。记住,目标不是编写高效或实用的代码,而是以更深入的方式探索和理解语言。
“Spooky Scary PHP”是一种好的实践吗?
“Spooky Scary PHP”通常不被认为是编写生产代码的良好实践。它通常涉及使用效率低下、不清楚或不可预测的函数或技术。但是,它可能是学习更多关于该语言以及挑战您对PHP的理解的一种好方法。它更像是一种学习工具和有趣的实验,而不是一种实用的编码风格。
“Spooky Scary PHP”有害吗?
虽然“Spooky Scary PHP”既有趣又有教育意义,但务必负责任地使用它。在“Spooky Scary PHP”中使用的一些技术如果在实时环境中使用,可能会造成危害,例如那些利用语言中的特性或错误的技术。务必彻底测试您编写的任何代码,并且永远不要在项目的重要部分使用“Spooky Scary PHP”技术。
以上是怪异的可怕php的详细内容。更多信息请关注PHP中文网其他相关文章!