我的问题如下:
如果你看下面,你会看到有一个带有消息ID的数据结构,然后是包含应该从imap_fetch_overview
聚合的消息详细信息的最终数据结构。消息ID来自imap_thread
。问题是它没有将电子邮件详细信息放在消息ID所在的位置。
这是我的数据结构:
[5] => Array
(
[0] => 5
[1] => 9
)
[10] => Array
(
[0] => 10
[1] => 11
)
我想拥有的是:
[5] => Array
(
[0] => messageDetails for id 5
[1] => messageDetails for id 9
)
[10] => Array
(
[0] => messageDetails for id 10
[1] => messageDetails for id 11
)
这是我到目前为止的代码:
$emails = imap_fetch_overview($imap, implode(',',$ids));
// root is the array index position of the threads message, such as 5 or 10
foreach($threads as $root => $messages){
// id is the id being given to us from `imap_thread`
foreach($message as $key => $id){
foreach($emails as $index => $email){
if($id === $email->msgno){
$threads[$root][$key] = $email;
break;
}
}
}
}
以下是其中一封$电子邮件的打印输出:
[0] => stdClass Object
(
[subject] => Cloud Storage Dump
[from] => Josh Doe
[to] => jondoe@domain.com
[date] => Mon, 21 Jan 2013 23:18:00 -0500
[message_id] => <50FE12F8.9050506@domain.com>
[size] => 2559
[uid] => 5
[msgno] => 5
[recent] => 0
[flagged] => 0
[answered] => 1
[deleted] => 0
[seen] => 0
[draft] => 0
[udate] => 1358828308
)
如果你注意到,msgno是5,它与$id
一致,所以从技术上讲,数据应该填充到最终的数据结构中。
此外,这似乎是处理此问题的低效方法。
如果您需要任何其他说明,请告诉我。
更新代码
这段代码是我在php api上找到的代码和我的一些修复的组合。我认为有问题的仍然是$root
。
$addedEmails = array();
$thread = imap_thread($imap);
foreach ($thread as $i => $messageId) {
list($sequence, $type) = explode('.', $i);
//if type is not num or messageId is 0 or (start of a new thread and no next) or is already set
if($type != 'num' || $messageId == 0 || ($root == 0 && $thread[$sequence.'.next'] == 0) || isset($rootValues[$messageId])) {
//ignore it
continue;
}
if(in_array($messageId, $addedEmails)){
continue;
}
array_push($addedEmails,$messageId);
//if this is the start of a new thread
if($root == 0) {
//set root
$root = $messageId;
}
//at this point this will be part of a thread
//let's remember the root for this email
$rootValues[$messageId] = $root;
//if there is no next
if($thread[$sequence.'.next'] == 0) {
//reset root
$root = 0;
}
}
$ids=array();
$threads = array();
foreach($rootValues as $id => $root){
if(!array_key_exists($root,$threads)){
$threads[$root] = array();
}
if(!in_array($id,$threads[$root])){
$threads[$root][] = $id;
$ids[]=$id;
}
}
$emails = imap_fetch_overview($imap, implode(',', array_keys($rootValues)));
$keys = array();
foreach($emails as $k => $email)
{
$keys[$email->msgno] = $k;
}
$threads = array_map(function($thread) use($emails, $keys)
{
// Iterate emails in these threads
return array_map(function($msgno) use($emails, $keys)
{
// Swap the msgno with the email details
return $emails[$keys[$msgno]];
}, $thread);
}, $threads);
答案 0 :(得分:4)
请记住,在php中,无论你使用什么函数,它都会最终转换为某种循环。 但是,您可以采取一些步骤来提高效率,它们在PHP 5.5和5.3 / 5.4中有所不同。
最有效的方法是将功能拆分为2个单独的步骤。 在第一步中,您将生成电子邮件列表的密钥映射。
$keys = array();
foreach($emails as $k => $email)
{
$keys[$email->msgno] = $k;
}
在第二步中,您迭代多维$线程中的所有值,并将其替换为电子邮件详细信息:
// Iterate threads
$threads = array_map(function($thread) use($emails, $keys)
{
// Iterate emails in these threads
return array_map(function($msgno) use($emails, $keys)
{
// Swap the msgno with the email details
return $emails[$keys[$msgno]];
}, $thread);
}, $threads);
概念证明:http://pastebin.com/rp5QFN4J
匿名函数中关键字使用的说明:
为了使用父作用域中定义的变量,可以使用 use()关键字将变量从父作用域导入到闭包作用域中。虽然它是在PHP 5.3中引入的,但还没有在官方PHP手册中记录。这里只有关于php&#w; wiki的草案文档https://wiki.php.net/rfc/closures#userland_perspective
此版本的新功能之一使您可以使用生成器,这些生成器具有明显更小的内存指纹,因此效率更高。
生成器中关键字 yield 的说明:
生成器函数的核心是 yield 关键字。在最简单的形式中,yield语句看起来很像一个return语句,除了不是停止执行函数并返回,而是yield为循环生成器的代码提供一个值并暂停生成器函数的执行。
第1步:
function genetateKeyMap($emails)
{
foreach($emails as $k => $email)
{
// Yielding key => value pair to result set
yield $email->msgno => $k;
}
};
$keys = iterator_to_array(genetateKeyMap($emails));
第二步:
function updateThreads($emails, $threads, $keys)
{
foreach($threads as $thread)
{
$array = array();
// Create a set of detailed emails
foreach($thread as $msgno)
{
$array[] = $emails[$keys[$msgno]];
}
// Yielding array to result set
yield $array;
}
};
$threads = iterator_to_array(updateThreads($emails, $threads, $keys));
关于生成者返回的值的几句话:
生成器返回一个对象,该对象是SPL Iterator的一个实例,因此它需要使用iterator_to_array()才能将其转换为代码所期望的完全相同的数组结构。您不需要这样做,但需要在生成器功能之后更新您的代码,这可能会更有效。
概念证明:http://pastebin.com/9Z4pftBH
我生成了一个包含7000个线程的列表,每个线程包含5条消息,并测试了每种方法的性能(平均来自5个测试):
Takes: Memory used:
----------------------------
3x foreach(): 2.8s 5.2 MB
PHP 5.3/5.4 way 0.061s 2.7 MB
PHP 5.5 way 0.036s 2.7 MB
虽然您的机器/服务器上的结果可能不同,但概述显示两步法比使用3个foreach循环快45-77倍
答案 1 :(得分:2)
当你打印$ emails数组时,你会得到什么结构?也许以下应该这样做?
$threads[$root][$key] = $emails[$key];
答案 2 :(得分:0)
我现在无法访问PHP进行测试,但我相信你要做的就是
foreach($emails as $email) {
foreach($threads as $root => $messages) {
foreach($messages as $index =>$message_id){
if($message_id == $email->msgno){
$threads[$root][$index] = $email;
}
}
}
}
话虽如此,即使这样可行,但有可能采用比三个嵌套循环更有效的方法。您需要以此格式存储输出的原因是什么?
答案 3 :(得分:0)
使用分支的实现(比单个线程array('5' => array(5,7,8))
更复杂,但除非我只与1个人交谈,否则线程总是倾向于为我个人分支,所以我将不得不应对增加的复杂性)
<?php
$threads = imap_thread($imap, SE_UID);
/*
* threads returns entries as follows:
* <id>.num = <messageid>
* <id>.next = <messageid of first reply to <id>>, 0 = no replies
* <id>.branch = <messageid of nth. reply to <parent of id>>, 0 = no more branches
* Keep in mind: _every_ message 'starts' a branch, but that may be empty.
*/
$nodes = array( 0 => array( 'children' => array()));
$ids = array();
foreach ($threads as $key => $val) {
list($treeid,$type) = explode('.',$key);
switch($type){
case 'num':
//the actual message number of this tree node
//store id for retrieval later:
$ids[$val] = null;
if($val==0){
//return to root
$nodes[$treeid] = &$nodes[0];
} else {
if(!isset($nodes[$treeid])) $nodes[$treeid] = array();
$nodes[$treeid] = array_merge($nodes[$treeid],array(
'id' => $val,
'message' => &$ids[$val],
'treeid' => $treeid));
}
break;
case 'next':
// 0 means no next message, anything else is a reply
if (0!=$val) {
if(!isset($nodes[$val])) $nodes[$val] = array('parent' => $treeid);
$nodes[$treeid][] = &$nodes[$val];
}
break;
case 'branch':
//0 means end of branch, a number means continue as sibling \
//so we need to know the parent
if (0!=$val) {
if(!isset($nodes[$val])) $nodes[$val] = array('parent' => $nodes[$treeid]['parent']?:0);
$nodes[$nodes[$val]['parent']][] = &$nodes[$val];
}
break;
default:
trigger_error("Unknown tree traverse-type: $type", E_USER_WARNING);
}
}
//the great thing is we can get all our ID's at once:
$keystofetch = implode(',',array_filter(array_keys($nodes)));
$messages = imap_fetch_overview($imap,$keystofetch, FT_UID);
foreach($messages as $message){
// you can of course store the _whole_ message in this thread like:
// $nodes[$message->uid]['message'] = get_object_vars($message);
// and do what you like with $tree[0]['children'] (be it a resursive array iterator,
// or a resursive function, your pick.
// However, for this example we are going to only set message to a string of p.o.c
// (which is also nicer for our treeiterator)
$ids[$message->uid] = $message->from.':'.$message->subject;
}
//let's show the result:
$it = new RecursiveTreeIterator(new RecursiveArrayIterator($nodes[0]),
RecursiveTreeIterator::BYPASS_CURRENT,
CachingIterator::TOSTRING_USE_KEY);
foreach($it as $key => $item){
echo "$key".(is_scalar($item)?': '.$item:'').PHP_EOL;
}
哪位给我们:
|-children
|-0
| |-parent: 0
| |-id: 35
| |-message: Friend Purple Acc2 <purple2@example.com>:A bigger message thread
| |-treeid: 1
| \-0
| |-parent: 1
| |-id: 7
| |-message: Friend White <white@example.com>:Re: A bigger message thread
| |-treeid: 2
| \-0
| |-parent: 2
| |-id: 11
| |-message: Friend Grey <grey@example.com>Re: A bigger message thread
| |-treeid: 3
| \-0
| |-parent: 3
| |-id: 39
| |-message: Friend Purple Acc2 <purple2@example.com>:Re: A bigger message thread
| |-treeid: 4
| \-0
| |-parent: 4
| |-id: 40
| |-message: Friend Pink <pink@example.com>:Re: A bigger message thread
| |-treeid: 5
| \-0
| |-parent: 5
| |-id: 38
| |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| |-treeid: 6
| \-0
| |-parent: 6
| |-id: 12
| |-message: Friend Pink <pink@example.com>:Re: A bigger message thread
| |-treeid: 7
| \-0
| |-parent: 7
| |-id: 25
| |-message: Friend White <white@example.com>:Re: A bigger message thread
| |-treeid: 8
| \-0
| |-parent: 8
| |-id: 19
| |-message: Friend Black <black@example.com>:Re: A bigger message thread
| |-treeid: 9
| \-0
| |-parent: 9
| |-id: 23
| |-message: Friend Black <black@example.com>:Re: A bigger message thread
| |-treeid: 10
| \-0
| |-parent: 10
| |-id: 30
| |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| |-treeid: 11
| \-0
| |-parent: 11
| |-id: 2
| |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| |-treeid: 12
| |-0
| | |-parent: 12
| | |-id: 20
| | |-message: Me <me@example.com>:Re: A bigger message thread
| | |-treeid: 13
| | \-0
| | |-parent: 13
| | |-id: 1
| | |-message: Fiend Silver <silver@example.com>:Re: A bigger message thread
| | |-treeid: 14
| | \-0
| | |-parent: 14
| | |-id: 41
| | |-message: Fiend Silver <silver@example.com>:Re: A bigger message thread
| | |-treeid: 15
| | \-0
| | |-parent: 15
| | |-id: 27
| | |-message: Friend Grey <grey@example.com>Re: A bigger message thread
| | |-treeid: 16
| | \-0
| | |-parent: 16
| | |-id: 17
| | |-message: Friend Magenta <magenta@example.com>:Re: A bigger message thread
| | |-treeid: 17
| | |-0
| | | |-parent: 17
| | | |-id: 31
| | | |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| | | |-treeid: 18
| | | \-0
| | | |-parent: 18
| | | |-id: 4
| | | |-message: Friend Black <black@example.com>:Re: A bigger message thread
| | | |-treeid: 19
| | | \-0
| | | |-parent: 19
| | | |-id: 37
| | | |-message: Friend Black <black@example.com>:Re: A bigger message thread
| | | |-treeid: 20
| | | \-0
| | | |-parent: 20
| | | |-id: 24
| | | |-message: Friend Purple Acc2 <purple2@example.com>:Re: A bigger message thread
| | | |-treeid: 21
| | | \-0
| | | |-parent: 21
| | | |-id: 13
| | | |-message: Friend White <white@example.com>:Re: A bigger message thread
| | | \-treeid: 22
| | \-1
| | |-parent: 17
| | |-id: 15
| | |-message: Friend Grey <grey@example.com>Re: A bigger message thread
| | |-treeid: 23
| | \-0
| | |-parent: 23
| | |-id: 18
| | |-message: Friend Magenta <magenta@example.com>:Re: A bigger message thread
| | |-treeid: 24
| | \-0
| | |-parent: 24
| | |-id: 45
| | |-message: Friend Black <black@example.com>:Re: A bigger message thread
| | \-treeid: 25
| \-1
| |-parent: 12
| |-id: 46
| |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| |-treeid: 26
| \-0
| |-parent: 26
| |-id: 29
| |-message: Fiend Silver <silver@example.com>:Re: A bigger message thread
| |-treeid: 27
| \-0
| |-parent: 27
| |-id: 26
| |-message: Friend Magenta <magenta@example.com>:Re: A bigger message thread
| |-treeid: 28
| |-0
| | |-parent: 28
| | |-id: 34
| | |-message: Friend Grey <grey@example.com>Re: A bigger message thread
| | \-treeid: 29
| |-1
| | |-parent: 28
| | |-id: 33
| | |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| | |-treeid: 30
| | \-0
| | |-parent: 30
| | |-id: 36
| | |-message: Friend White <white@example.com>:Re: A bigger message thread
| | |-treeid: 31
| | |-0
| | | |-parent: 31
| | | |-id: 10
| | | |-message: Friend White <white@example.com>:Re: A bigger message thread
| | | \-treeid: 32
| | \-1
| | |-parent: 31
| | |-id: 48
| | |-message: Friend Pink <pink@example.com>:Re: A bigger message thread
| | \-treeid: 33
| \-2
| |-parent: 28
| |-id: 47
| |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| |-treeid: 34
| \-0
| |-parent: 34
| |-id: 5
| |-message: Friend White <white@example.com>:Re: A bigger message thread
| |-treeid: 35
| \-0
| |-parent: 35
| |-id: 3
| |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| |-treeid: 36
| \-0
| |-parent: 36
| |-id: 21
| |-message: Friend Yellow <yellow@example.com>:Re: A bigger message thread
| |-treeid: 37
| \-0
| |-parent: 37
| |-id: 8
| |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| |-treeid: 38
| \-0
| |-parent: 38
| |-id: 43
| |-message: Friend White <white@example.com>:Re: A bigger message thread
| |-treeid: 39
| \-0
| |-parent: 39
| |-id: 28
| |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| |-treeid: 40
| \-0
| |-parent: 40
| |-id: 42
| |-message: Friend Brown <brown@example.com>:Re: A bigger message thread
| |-treeid: 41
| \-0
| |-parent: 41
| |-id: 22
| |-message: Friend Purple <purple@example.com>:Re: A bigger message thread
| \-treeid: 42
|-1
| |-parent: 0
| |-id: 9
| |-message: Friend Blue <blue@example.com>:RE: A bigger message thread
| \-treeid: 43
|-2
| \-parent: 0
|-3
| |-parent: 44
| |-id: 49
| |-message: Some Subcription <foo@example.com>:Newsletter #1
| \-treeid: 45
|-4
| |-parent: 44
| |-id: 50
| |-message: Some Subcription <foo@example.com>:Newsletter #2
| \-treeid: 46
\-5
|-parent: 0
|-id: 32
|-message: Friend Red <red@example.com>:A second mainthread
|-treeid: 47
\-0
|-parent: 47
|-id: 16
|-message: Friend Black <black@example.com>:Re: A second mainthread
|-treeid: 48
\-0
|-parent: 48
|-id: 14
|-message: Friend Red <red@example.com>:Re: A second mainthread
|-treeid: 49
\-0
|-parent: 49
|-id: 6
|-message: Friend White <white@example.com>:Re: A second mainthread
|-treeid: 50
\-0
|-parent: 50
|-id: 44
|-message: Fiend Silver <silver@example.com>:Re: A second mainthread
\-treeid: 51
注意事项:
imap_thread
并不完美:我们将id=9
看作孤儿,虽然它似乎应该在某个地方的第一个线程中。但是,由于
标题没有提到这一点,谷歌应用程序在这里决定实现它
它是自己的节点。N.num.N.branch,N.next
方法显然没有其他方法可以返回
根。这是/return to root $nodes[$treeid] = &$nodes[0];
位。你可以/应该在确定所有其他之后过滤掉它
节点,但你需要它来构建数组。仅获取开始新线程的节点(消息上的第N个回复,N> 1):
$threads = imap_thread($imap, SE_UID);
$branchestarts = array();
foreach($threads as $key => $value){
list($num,$type) = explode('.',$key);
if (
$type=='num' // an id
&& $value == 0 // which is actually root
&& isset($threads[$num.'.next']) // then check for next
&& isset($threads[$threads[$num.'.next'].'.num'])
){
$branchestarts[] = $threads[$threads[$num.'.next'].'.num'];
} else if(
$type=='branch' // branch movement
&& $value != 0 // not back
&& isset($threads[$value.'.num']) // sanity: target exists
&& $threads[$value.'.num'] != 0 // and is not a return to root
){
$branchestarts[] = $threads[$value.'.num'];
}
}
echo json_encode($branchestarts);
这给了我们:
[35,15,46,33,48,47,9,49,50,32]
事实上,35,49,50&amp; 32是线程的开始,9也是由imap服务器识别的,其余的是第二个或更多个回复开始它们自己的分支。
现在,你可能确实将分支拆分为单独的对话,但正如你所看到的,这些通常只有1或2个回复,更长的线程往往更少发展。 要了解这些“分支”如何发展:
$branches = array();
$currenttree = null;
foreach($threads as $key => $value){
list($num,$type) = explode('.',$key);
switch($type){
case 'num':
//nothing
break;
case 'next':
if(is_null($currenttree)) $currenttree = &$branches[$threads[$value.'.num']];
if($value && isset($threads[$value.'.num'])) $currenttree[] = $threads[$value.'.num'];
break;
case 'branch':
unset($currenttree);
if($value && $threads[$value.'.num']){
$branches[$threads[$value.'.num']] = array($threads[$value.'.num']);
$currenttree =& $branches[$threads[$value.'.num']];
}
}
}
echo json_encode($branches, JSON_PRETTY_PRINT);
这给你带来了根源和分支机构他们的回复:
{
"35": [
35,
7,
11,
39,
40,
38,
12,
25,
19,
23,
30,
2,
20,
1,
41,
27,
17,
31,
4,
37,
24,
13
],
"15": [
15,
18,
45
],
"46": [
46,
29,
26,
34
],
"33": [
33,
36,
10
],
"48": [
48
],
"47": [
47,
5,
3,
21,
8,
43,
28,
42,
22
],
"9": [
9
],
"49": [
49
],
"50": [
50
],
"32": [
32,
16,
14,
6,
44
]
}
通过一些细微的改动我们可以在那里得到消息:
$branches = array();
$currenttree = null;
$messages = array();
foreach($threads as $key => $value){
list($num,$type) = explode('.',$key);
switch($type){
case 'num':
//nothing
break;
case 'next':
if(is_null($currenttree)) $currenttree = &$branches[$threads[$value.'.num']];
if($value && isset($threads[$value.'.num'])) $currenttree[] = &$messages[$threads[$value.'.num']];
break;
case 'branch':
unset($currenttree);
if($value && $threads[$value.'.num']){
$branches[$threads[$value.'.num']] = array(&$messages[$threads[$value.'.num']]);
$currenttree =& $branches[$threads[$value.'.num']];
} else {
$currenttree = null;
}
}
}
$keystofetch = implode(',',array_filter(array_keys($messages)));
foreach(imap_fetch_overview($imap,$keystofetch,FT_UID) as $message){
$messages[$message->uid] = $message;
}
echo json_encode($branches);//won't show it's output, this answer is to large as it is ;)
另一个选择是按日期时间值对它们进行排序,这对于具有很少/可忽略分支的对话来说是正常的,可能会使您计划的大部分代码正常工作。
两者的组合将是“移动分支”,跟随线程串联,所以:
1 2013-06-01
2 2013-06-02
3 2013-06-03
4 2013-06-03
5 2013-06-04
成为1,2,3,4,5
的序列,但3
上的回复会诉诸它:
1 2013-06-01
4 2013-06-03
5 2013-06-04
2 2013-06-02
3 2013-06-03
6 2013-06-05
使它成为1,4,5,2,3,6
的序列,这将保持逻辑上流畅的对话,始终是线程/分支,最后一个回复为最后一个。