怪异模式

tech2023-11-14  83

怪异模式

Break out the candy corn and apple cider; it’s that time of year again! The rest of the world may not celebrate Halloween as hog wild as in America, but I thought it’d be fun to share some scary PHP stuff to mark the holiday. This is a fun article, sharing with you some scary (but logical) behavior found in PHP itself, and spooky (and possibly quite illogical) ways in which some have twisted PHP to do their bidding. Think of this as my treat to you, a little bit of geek “mind-candy” – because why should all the trick-or-treaters have all goodies?

取出糖果玉米和苹果酒; 又到了每年的这个时候! 世界其他地方可能不会像美国那样疯狂地庆祝万圣节,但我认为分享一些令人恐惧PHP东西来庆祝这个节日会很有趣。 这是一篇有趣的文章,与您分享一些PHP本身令人恐惧的(但合乎逻辑的)行为,以及一些扭曲PHP进行出价的怪异(甚至很不合逻辑)的方法。 将此视为我对您的请客,有点怪胎的“头脑糖果” –因为为什么所有的“捣蛋鬼”都应该拥有所有好东西?

鬼屋 (The Haunted Array)

Once upon a time in a development shop not so far away, Arthur was up late at night hacking out some code. Little did he know the array he was about to use was haunted! He felt a chill run down his spine as he typed out his statements, keystroke by keystroke, but foolishly brushed off his subtle premonition.

很久以前,Arthur在不远处的一家开发商店里,深夜黑了一些代码。 他几乎不知道他要使用的阵列被困扰了! 当他敲击键盘键入陈述时,他感到一阵阵发冷,但愚蠢地抹去了他微妙的预感。

<?php $spell = array("double", "toil", "trouble", "cauldron", "bubble"); foreach ($spell as &$word) { $word = ucfirst($word); } foreach ($spell as $word) { echo $word . "n"; }

Alright, so the array wasn’t really haunted, but the output certainly was unexpected:

好了,所以该数组并不是真正的困扰,但是输出肯定是出乎意料的:

Double Toil Trouble Cauldron Cauldron

The reason for this spooky behavior lies in how PHP persists the reference outside of the first foreach loop. $word was still a reference pointing to the last element of the array when the second loop began. The first iteration of the second loop assigned “double” to $word, which overwrote the last element. The second iteration assigned “toil” to $word, overwriting the last element again. By the time the loop read the value of the last element, it had already been trampled several times.

出现这种怪异行为的原因在于PHP如何在第一个foreach循环之外保留引用。 $word仍然是第二个循环开始时指向数组最后一个元素的引用。 第二个循环的第一次迭代将“ double”分配给$word ,从而覆盖了最后一个元素。 第二次迭代为$word分配了“人工”,再次覆盖了最后一个元素。 到循环读取最后一个元素的值时,它已被踩踏几次。

For an in-depth explanation of this behavior, I recommend reading Johannes Schlüter’s blog post on the topic, References and foreach. You can also run this slightly modified version and examine its output for better insight into what PHP is doing:

有关此行为的深入说明,我建议阅读JohannesSchlüter的博客文章,主题为References and foreach 。 您还可以运行此经过稍微修改的版本,并检查其输出以更好地了解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"; }

Arthur learned a very important lesson that night and fixed his code using the array’s keys to assign the string back.

那天晚上,Arthur上了一堂非常重要的课,并使用数组的键将字符串分配回了自己的代码。

<?php foreach ($spell as $key => $word) { $spell[$key] = ucfirst($word); }

幻影数据库连接 (The Phantom Database Connection)

More and more, PHP is asked to do more than just generate web pages on a daily basis. The number of shell scripts written in PHP are on the rise, and the duties such scripts perform are increasingly more complex, as developers see merit in consolidating development languages. Often times the performance of these scripts are acceptable and the trade off for convenience can be justified.

越来越多PHP被要求做更多的事情,而不仅仅是每天生成网页。 用PHP编写的Shell脚本的数量正在增加,并且随着开发人员看到合并开发语言的优点,这种脚本执行的功能也越来越复杂。 通常,这些脚本的性能是可以接受的,并且为方便起见可以进行权衡。

And so Susan was writing a parallel-processing task which resembled the following code:

因此,Susan编写了一个类似于以下代码的并行处理任务:

#! /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";

Her code forked children processes to do some long-running work in parallel while the parent process continued on to monitor the children, reporting back when all of them have terminated.

她的代码派生了子进程并行执行一些长时间运行的工作,而父进程继续监视子进程,并在所有子进程都终止时进行报告。

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.

Instead of outputting status messages to stdout though, Susan’s supervisor asked her to log the times when processing started and when all of the children finished. Susan extended her code using the Singleton-ized PDO database connection mechanism that was already part of the company’s codebase.

Susan的主管没有将状态消息输出到stdout,而是要求她记录处理开始的时间以及所有孩子的完成时间。 Susan使用Singleton化的PDO数据库连接机制扩展了她的代码,该机制已经成为公司代码库的一部分。

#! /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; } }

Susan expected to see the rows in the timings table updated; the “start time” row should have listed the timestamp when the whole process was kicked off, and the “stop time” should have when everything finished up. Unfortunately, execution threw an exception and the database didn’t mirror her expectations.

Susan希望看到时序表中的行已更新; “开始时间”行应列出启动整个过程的时间戳,而“停止时间”应列出一切完成的时间。 不幸的是,执行过程引发了异常,并且数据库未达到她的期望。

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} +------------+---------------------+ | name | tstamp | +------------+---------------------+ | start time | 2012-10-13 01:11:37 | | stop time | 0000-00-00 00:00:00 | +------------+---------------------+

Like Arthur’s array, had Susan’s database become haunted? Well, see if you can piece together the puzzle if I give you the following clues:

像亚瑟的阵形一样,苏珊的数据库是否出没了? 好吧,如果我给您以下提示,请看您是否可以拼砌拼图:

When a process forks, the parent process is copied as the child. These duplicate processes then continue executing from that point onward side by site.

进程分叉时,父进程将作为子进程复制。 然后,这些重复的过程将从该点开始并排地继续执行。 Static members are shared between all instances of a class.

静态成员在类的所有实例之间共享。

The PDO connection was wrapped as a Singleton, so any references to it across the application all pointed to the same resource in memory. DB::connection() first returned the object reference, the parent forked, the children continued processing while the parent waited, the children processes terminated and PHP cleaned up used resources, and then the parent tried to use the database object again. The connection to MySQL has been closed in a child process, so the final call failed.

PDO连接被包装为Singleton,因此在应用程序中对它的任何引用都指向内存中的同一资源。 DB::connection()首先返回对象引用,父级派生,子级继续处理,而父级等待,子级进程终止,PHP清除使用的资源,然后父级尝试再次使用数据库对象。 与MySQL的连接已在子进程中关闭,因此最终调用失败。

Naively trying to obtain the connection again before the end logging query wouldn’t help Susan as the same defunct PDO instance would be returned because it’s a Singleton.

天真地尝试在结束日志记录查询之前再次获取连接不会对Susan有所帮助,因为将返回相同的已失效PDO实例,因为它是Singleton。

I recommend avoiding Singletons – they’re really nothing more than fancy OOP’ized global variables which can make debugging difficult. And even though the connection would still be closed by a child process in our example, at least DB::connection() would return a fresh connection if you invoked it before the second query if Singletons weren’t used.

我建议避免使用Singletons –它们实际上只是花哨的OOP化的全局变量,这会使调试变得困难。 即使在我们的示例中该连接仍将由子进程关闭,但如果您在第二个查询之前未使用Singletons,则在至少第二次查询之前调用DB::connection()将返回一个新连接。

But still better would be to understand how the execution environment is cloned when forking and how various resources can be affected across all the processes. In this case, it’s wiser to connect to the database in the parent thread after the children have been forked, and the children would connect themselves if they needed to. The connection shouldn’t be shared.

但是,更好的方法是了解在分叉时如何克隆执行环境,以及如何在所有流程中影响各种资源。 在这种情况下,明智的做法是在孩子被派生之后,在父线程中连接到数据库,并且孩子在需要时会自行连接。 连接不应共享。

#! /usr/bin/env php <?php $pids = array(); foreach (range(0, 4) as $i) { ... } $db = Db::connection(); $db->query("UPDATE timings SET tstamp=NOW() WHERE name='start time'"); while (count($pids)) { ... } $db->query("UPDATE timings SET tstamp=NOW() WHERE name='stop time'");

值得科学怪人博士使用的API (An API Worthy of Dr. Frankenstein)

Mary Shelley’s Frankenstein is a story of a scientist who creates life, but is so repulsed by its ugliness that he abandons it. After a bit of gratuitous death and destruction, Dr. Frankenstein pursues his creation literally to the ends of the earth seeking its destruction. Many of us have breathed life into code so hideous that we later wish we could just run away from it – code so ugly, so obtuse, so tangled that it makes us want to retch, but it only wants love and understanding.

玛丽·雪莱(Mary Shelley)的《 科学怪人》(Frankenstein )讲述了一个创造生命的科学家的故事,但由于其丑陋而被其击退,以至于他放弃了它。 经过一番无故的死亡和毁灭之后,弗兰肯斯坦博士将他的创作逐字地推向了寻求毁灭的世界。 我们中的许多人都将生命付诸实践,以至于如此可怕的代码,以至于我们后来希望我们可以逃避它-如此丑陋,如此晦涩,如此纠结的代码使我们想取而代之,但它只想要爱和理解。

Years ago I was toying around with an idea focused on database interfaces and how might they look if they adhered more closely to Unix’s “everything is a file” philosophy: the query would be written to the “file”, the result set would be read from the “file.” One thing lead to another, and after some death and destruction coding of my own, I had written the following class which has little relevance to my original idea:

几年前,我想出一个关于数据库接口的想法,如果它们更紧密地遵循Unix的“一切都是文件”的哲学,它们的外观如何:查询将被写入“文件”,结果集将被读取来自“文件”。 一件事导致另一件事,在我自己编写了一些关于死亡和破坏的代码后,我写了以下与我的初衷无关的课程:

<?php class DBQuery implements Iterator { protected $db; protected $query; protected $result; protected $index; protected $numRows; public function __construct($host, $dbname, $username, $password) { $this->db = new PDO("mysql:dbname=$dbname;host=$host", $username, $password); } public function __get($query) { $this->query = $query; $this->result = $this->db->query($query); return $this->numRows = $this->result->rowCount(); } public function __call($query, $values) { $this->query = $query; $this->result = $this->db->prepare($this->query); $this->result->execute($values); return $this->numRows = $this->result->rowCount(); } public function clear() { $this->index = 0; $this->numRows = 0; $this->query = ""; $this->result->closeCursor(); } public function rewind() { $this->index = 0; } public function current() { return $this->result->fetch(PDO::FETCH_ASSOC, PDO::FETCH_ORI_ABS, $this->index); } public function key() { return $this->index; } public function next() { $this->index++; } public function valid() { return ($this->index < $this->numRows); } public function __toString() { return $this->query; } }

The result was genius, but repulsive: an instance which looked like an object (with no real API methods), or an array, or a string…

结果是天才,但令人反感:一个看起来像对象(没有真正的API方法),数组或字符串的实例……

<?php $dbq = new DBQuery("localhost", "test", "dbuser", "dbpassword"); // query the database if the user is authorized // (instance behaves like an object) $username = "administrator"; $password = sha1("password"); if (!$dbq->{"SELECT * FROM admin_user WHERE username=? " . "AND password=?"}($username, $password)) { die("Unauthorized."); } // query the database and display some records // (instance is iterable like an array) $dbq->{"SELECT id, first_name, last_name FROM employee"}; foreach ($dbq as $result) { print_r($result); } // casting the object string yields the query echo "Query: $dbq";

I blogged about it shortly thereafter and branded it as evil. Friends and colleagues who saw pretty much reacted the the same: “Brilliant! Now kill it… kill it with fire.”

此后不久, 我在博客上发表了文章 ,并将其冠以邪恶的烙印。 看到非常多的朋友和同事也有同样的React:“太棒了! 现在杀死它……用火杀死它。”

But over the years since, I admit I’ve softened on it. The only rule it really bends is that of the programmer’s expectation of blandly named methods like query() and result(). Instead it uses the query string itself as the querying method, the object is the interface and the result set. Certainly it’s no worse than an overly-generalized ORM interface with select() and where() methods chained together to look like an SQL query but with more ->‘s. Maybe my class really isn’t so evil after all? Maybe it just wants to be loved? I certainly don’t want to die in the Arctic!

但是多年来,我承认我对此有所软化。 它真正弯曲的唯一规则是程序员期望的那些平淡无奇的命名方法,例如query()和result() 。 相反,它使用查询字符串本身作为查询方法,对象是接口和结果集。 当然,这不比将select()和where()方法链接在一起看起来像SQL查询但带有更多->的,过于笼统的ORM接口更糟糕。 也许我的课真的真的不是那么邪恶吗? 也许只是想被爱? 我当然不想死在北极!

收盘时 (In Closing)

I hope you enjoyed the article and the examples won’t give you (too many) nightmares! I’m sure you’ve got your own tails of haunted or monstrous code, and there’s no need to let the holiday fun fizzle out regardless of where you are in the world, so feel free to share your scary PHP stories in the comments below!

我希望您喜欢这篇文章,并且示例不会给您带来太多的噩梦! 我敢肯定,您自己的代码缠着鬼怪的代码,无论您身在何处,都不必让假期的乐趣消散,所以请随时在下面的注释中分享您可怕PHP故事!

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/spooky-scary-php/

怪异模式

最新回复(0)