Мой вопрос следующий:
Если вы посмотрите ниже, вы увидите, что существует структура данных с идентификаторами сообщений, а затем окончательная структура данных, содержащая детали сообщения, которые должны быть агрегированы из imap_fetch_overview
. Идентификаторы сообщений из imap_thread
. Проблема заключается в том, что он не помещает данные электронной почты в позицию, где находится идентификатор сообщения.
Вот моя структура данных:
[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; } } } }
Вот распечатка одного из $ email:
[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);
Помните, что в php любая функция, которую вы используете, будет окончательно преобразована в какой-то цикл. Есть, однако, некоторые шаги, которые вы могли бы предпринять, чтобы сделать его более эффективным, и они отличаются в PHP 5.5 и 5.3 / 5.4.
Самый эффективный способ сделать это – разделить функцию на два отдельных шага. На первом этапе вы создадите карту ключей для списка писем.
$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. Здесь есть только проект документа на wiki php. https://wiki.php.net/rfc/closures#userland_perspective
Одна из новых функций этой версии позволяет использовать генераторы, которые имеют значительно меньший отпечаток памяти, таким образом, более эффективны.
Объяснение выхода ключевого слова в генераторах:
Сердцем функции генератора является ключевое слово 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
Хотя результаты на вашем компьютере / сервере могут быть разными, но обзор показывает, что двухэтапный метод примерно в 45-77 раз быстрее, чем использование 3 циклов foreach
Тестовый скрипт: http://pastebin.com/M40hf0x7
Когда вы печатаете_r массив $ emails, какую структуру вы получаете? Может быть, нижеследующее должно это сделать?
$threads[$root][$key] = $emails[$key];
У меня нет доступа к 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; } } } }
При этом, даже если это работает, возможно, более эффективный способ приблизиться к этому, чем с тремя вложенными циклами. Какова ваша причина для сохранения вывода в этом формате?
Реализация с ветвями (более сложный, чем один array('5' => array(5,7,8))
нитей array('5' => array(5,7,8))
, но, если я не разговаривал только с array('5' => array(5,7,8))
человеком, потоки всегда склонны к ветвлению для меня лично, придется справляться с дополнительной сложностью)
<?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 poc // (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
как сироту, хотя, похоже, он должен быть в первом потоке где-то. Однако из-за того, что заголовки не упоминают об этом, Google Apps здесь решил сделать его собственным узлом. 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 и 32 – это потоки, 9 также распознаются сервером imap, а остальные – 2 или более ответов, начиная их собственные ветви.
Теперь вы действительно можете разделить ветви как отдельный разговор, но, как вы можете видеть, это часто только 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);
с$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 ;)
с$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 ;)
Другой вариант состоит в том, чтобы просто отсортировать их по значению datetime, что было бы хорошо для разговоров с небольшим / пренебрежимым ветвлением, возможно, большую часть кода, который вы планируете, просто работает.
Комбинация этих двух будет «движущимися ветвями», последовательными последовательностями, так что:
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
, что обеспечит логически текущую беседу, всегда с веткой / веткой с последним ответом как последним.