我有一个有点有趣的查询…
我可能过于简化了这个例子,但我会尽我所能来描述我的问题。
我正在从头开始构建一个非常简单的wiki实现,一切都很顺利,直到我意识到我需要循环检测来防止无休止的数据循环填充页面并溢出堆栈堆。
数据库结构是基本的,它比所示的更复杂,但对于本文的目的,我们只需要两列。
Content字段是直接的,它存储了页面的内容或WikiPart链接,即[[n]]链接到另一部分和包含,链接显示为[[n]]和包含是{{n}}。
+---------------------------+
| id | Content |
+---------------------------+
| 1 | see {{2}} here |
+---------------------------+
| 2 | {{1}} here [[4]] |
+---------------------------+
| 4 | {{1}} |
+---------------------------+
$html_for_screen = readData($this->Content);
function readData($wikipage) {
$str = "";
//Convert any wiki links to HTML Links
$wikipage = Converter::convertWikink($wikipage);
//Get ALL Include Link matches into array
$wiki_inc = RegEx::getMatches(wikipage);
//Iterate through the Matches
foreach($wiki_inc as $wiki) {
//traverse through each match.
//but I assume here is where I would eventually have the trouble
//With infinant loops
$str .= readData($wiki);
}
return $str;
}
问题:我怎样才能防止Wiki的各个部分无休止地相互包含。也就是说,wikipart1包含了WikiPart2…但是wikipart2包含了WikiPart1
parse或readData()函数将继续循环。
对
您可以使用堆栈(或集合)跟踪您的包含。如果在堆栈中找到要包含的页面,则停止。
你也可以设置一个递归限制,比如30或其他,这不是很干净,但工作
实际上如果你遇到一个循环,你就不能再解决了。例子:
1: {{2}}
2: {{1}}
这将创建一个无限循环:
1 -> 2 -> 1 -> 2 -> ...
由于任何计算机资源都是有限的,无限循环将导致崩溃。
那么你能做什么呢?你可以检测到这个错误,然后使用stack:
function readData($wikipage)
{
static $stack = array();
if (in_array($wikipage, $stack))
{
throw new Exception(sprintf('Circular reference detected: %s -> %s', implode(' -> ', $stack), $wikipage));
}
$stack[] = $wikipage;
... (your existing code)
array_pop($stack);
}
此外,您可以通过使用count($stack)
来确定嵌套级别来控制递归限制。
实际上抛出异常可能不是对循环引用的正确反应,但它显示了检测是如何工作的。您可以自己决定如何处理这种情况,例如返回FALSE
或不再解析该字段等。
编辑:创意在这里:
如果输出是HTML,您也可以让用户解决问题。如果检测到这样的循环引用,则可以插入一些AJAX标记,以某种形式在浏览器中请求在服务器端无法获得的代码片段。这样的覆盖将再次包含循环引用(能够再次覆盖),以便用户能够交互式地看到循环引用。