PHP creando una matriz multidimensional de hilos de mensajes desde una matriz multidimensional (IMAP)

Mi pregunta es la siguiente:

Si mira más abajo verá que hay una estructura de datos con identificadores de mensajes y luego la estructura de datos final que contiene los detalles del mensaje que deben agregarse de imap_fetch_overview . Los identificadores del mensaje son de imap_thread . El problema es que no se ponen los detalles del correo electrónico en la posición donde se encuentra el ID del mensaje.

Aquí está mi estructura de datos:

 [5] => Array ( [0] => 5 [1] => 9 ) [10] => Array ( [0] => 10 [1] => 11 ) 

Lo que me gustaría tener es:

 [5] => Array ( [0] => messageDetails for id 5 [1] => messageDetails for id 9 ) [10] => Array ( [0] => messageDetails for id 10 [1] => messageDetails for id 11 ) 

Aquí está el código que tengo hasta ahora:

 $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; } } } } 

Aquí hay una copia impresa de uno de los $ correos electrónicos:

  [0] => stdClass Object ( [subject] => Cloud Storage Dump [from] => Josh Doe [to] => [email protected] [date] => Mon, 21 Jan 2013 23:18:00 -0500 [message_id] =>  [size] => 2559 [uid] => 5 [msgno] => 5 [recent] => 0 [flagged] => 0 [answered] => 1 [deleted] => 0 [seen] => 0 [draft] => 0 [udate] => 1358828308 ) 

Si se da cuenta, el msgno es 5, lo que se correlaciona con el $id , por lo que, técnicamente, los datos deberían estar en la estructura de datos final.

Además, esto parece una manera ineficiente de manejar esto.

Por favor, hágamelo saber si necesito alguna aclaración adicional.

CÓDIGO DE ACTUALIZACIÓN

Este código es una combinación de código que encontré en php api y algunas correcciones de mi parte. Lo que creo que es problemático aún es la $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); 

Recuerde que en php cualquier función que use se convertirá finalmente en algún tipo de bucle. Sin embargo, hay algunos pasos que puede tomar para hacerlo más eficiente y son diferentes en PHP 5.5 y 5.3 / 5.4.

PHP 5.3 / 5.4 camino

La forma más eficiente de hacer esto sería dividir la función en 2 pasos separados. En el primer paso, generaría un mapa de claves para la lista de correos electrónicos.

 $keys = array(); foreach($emails as $k => $email) { $keys[$email->msgno] = $k; } 

En el segundo paso, iterar todos los valores en los subprocesos $ multidimensionales y reemplazarlos con los detalles del correo electrónico:

 // 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); 

Prueba de concepto: http://pastebin.com/rp5QFN4J

Explicación del uso de palabras clave en funciones anónimas:

Con el fin de utilizar las variables definidas en el ámbito principal, es posible importar variables desde el ámbito principal al ámbito de cierre con la palabra clave use () . Aunque se introdujo en PHP 5.3, aún no se ha documentado en el manual oficial de PHP. Solo hay un documento borrador en la wiki de php aquí https://wiki.php.net/rfc/closures#userland_perspective

PHP 5.5

Una de las nuevas características de esta versión le permite usar generadores, que tienen una huella digital de memoria significativamente más pequeña, por lo que son más eficientes.

Explicación del rendimiento de palabras clave en generadores:

El corazón de una función de generador es la palabra clave de rendimiento . En su forma más simple, una statement de rendimiento se parece mucho a una statement de retorno, excepto que en lugar de detener la ejecución de la función y devolverla, el rendimiento proporciona un valor al bucle de código sobre el generador y detiene la ejecución de la función del generador.

1er paso:

 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)); 

2do paso:

 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)); 

Unas pocas palabras acerca de los valores que devuelven los genradores:

Los generadores devuelven un objeto que es una instancia de SPL Iterator, por lo tanto, necesita usar iterator_to_array () para convertirlo exactamente en la misma estructura de matriz que espera el código. No necesita hacer esto, pero requeriría una actualización de su código después de la función del generador, lo que podría ser aún más eficiente.

Prueba de concepto: http://pastebin.com/9Z4pftBH

Rendimiento de la prueba:

Generé una lista de 7000 hilos con 5 mensajes cada uno y probé el rendimiento de cada método (promedio de 5 pruebas):

  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 

Aunque los resultados en su máquina / servidor pueden ser diferentes, pero la visión general muestra que el método de 2 pasos es aproximadamente 45-77 veces más rápido que usar 3 bucles foreach

Script de prueba: http://pastebin.com/M40hf0x7

Cuando imprime en la matriz $ emails, ¿qué estructura obtiene? Tal vez lo de abajo debería hacerlo?

  $threads[$root][$key] = $emails[$key]; 

No tengo acceso a PHP en este momento, para probar, pero creo que lo que estás tratando de hacer es algo como

 foreach($emails as $email) { foreach($threads as $root => $messages) { foreach($messages as $index =>$message_id){ if($message_id == $email->msgno){ $threads[$root][$index] = $email; } } } } 

Dicho esto, incluso si esto funciona, probablemente haya una manera más eficiente de abordar esto que con tres bucles nesteds. ¿Cuál es la razón por la que necesita almacenar la salida en este formato?

Una implementación con bifurcaciones (más compleja que una array('5' => array(5,7,8)) un solo subproceso array('5' => array(5,7,8)) , pero a menos que solo estuviera hablando con 1 persona, los subprocesos siempre tienden a bifurcarme personalmente, así que ‘ Tendré que hacer frente a la complejidad añadida)

 .num =  * .next = >, 0 = no replies * .branch = >, 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; } 

Que nos dan:

 |-children |-0 | |-parent: 0 | |-id: 35 | |-message: Friend Purple Acc2 :A bigger message thread | |-treeid: 1 | \-0 | |-parent: 1 | |-id: 7 | |-message: Friend White :Re: A bigger message thread | |-treeid: 2 | \-0 | |-parent: 2 | |-id: 11 | |-message: Friend Grey Re: A bigger message thread | |-treeid: 3 | \-0 | |-parent: 3 | |-id: 39 | |-message: Friend Purple Acc2 :Re: A bigger message thread | |-treeid: 4 | \-0 | |-parent: 4 | |-id: 40 | |-message: Friend Pink :Re: A bigger message thread | |-treeid: 5 | \-0 | |-parent: 5 | |-id: 38 | |-message: Friend Yellow :Re: A bigger message thread | |-treeid: 6 | \-0 | |-parent: 6 | |-id: 12 | |-message: Friend Pink :Re: A bigger message thread | |-treeid: 7 | \-0 | |-parent: 7 | |-id: 25 | |-message: Friend White :Re: A bigger message thread | |-treeid: 8 | \-0 | |-parent: 8 | |-id: 19 | |-message: Friend Black :Re: A bigger message thread | |-treeid: 9 | \-0 | |-parent: 9 | |-id: 23 | |-message: Friend Black :Re: A bigger message thread | |-treeid: 10 | \-0 | |-parent: 10 | |-id: 30 | |-message: Friend Yellow :Re: A bigger message thread | |-treeid: 11 | \-0 | |-parent: 11 | |-id: 2 | |-message: Friend Yellow :Re: A bigger message thread | |-treeid: 12 | |-0 | | |-parent: 12 | | |-id: 20 | | |-message: Me :Re: A bigger message thread | | |-treeid: 13 | | \-0 | | |-parent: 13 | | |-id: 1 | | |-message: Fiend Silver :Re: A bigger message thread | | |-treeid: 14 | | \-0 | | |-parent: 14 | | |-id: 41 | | |-message: Fiend Silver :Re: A bigger message thread | | |-treeid: 15 | | \-0 | | |-parent: 15 | | |-id: 27 | | |-message: Friend Grey Re: A bigger message thread | | |-treeid: 16 | | \-0 | | |-parent: 16 | | |-id: 17 | | |-message: Friend Magenta :Re: A bigger message thread | | |-treeid: 17 | | |-0 | | | |-parent: 17 | | | |-id: 31 | | | |-message: Friend Purple :Re: A bigger message thread | | | |-treeid: 18 | | | \-0 | | | |-parent: 18 | | | |-id: 4 | | | |-message: Friend Black :Re: A bigger message thread | | | |-treeid: 19 | | | \-0 | | | |-parent: 19 | | | |-id: 37 | | | |-message: Friend Black :Re: A bigger message thread | | | |-treeid: 20 | | | \-0 | | | |-parent: 20 | | | |-id: 24 | | | |-message: Friend Purple Acc2 :Re: A bigger message thread | | | |-treeid: 21 | | | \-0 | | | |-parent: 21 | | | |-id: 13 | | | |-message: Friend White :Re: A bigger message thread | | | \-treeid: 22 | | \-1 | | |-parent: 17 | | |-id: 15 | | |-message: Friend Grey Re: A bigger message thread | | |-treeid: 23 | | \-0 | | |-parent: 23 | | |-id: 18 | | |-message: Friend Magenta :Re: A bigger message thread | | |-treeid: 24 | | \-0 | | |-parent: 24 | | |-id: 45 | | |-message: Friend Black :Re: A bigger message thread | | \-treeid: 25 | \-1 | |-parent: 12 | |-id: 46 | |-message: Friend Yellow :Re: A bigger message thread | |-treeid: 26 | \-0 | |-parent: 26 | |-id: 29 | |-message: Fiend Silver :Re: A bigger message thread | |-treeid: 27 | \-0 | |-parent: 27 | |-id: 26 | |-message: Friend Magenta :Re: A bigger message thread | |-treeid: 28 | |-0 | | |-parent: 28 | | |-id: 34 | | |-message: Friend Grey Re: A bigger message thread | | \-treeid: 29 | |-1 | | |-parent: 28 | | |-id: 33 | | |-message: Friend Yellow :Re: A bigger message thread | | |-treeid: 30 | | \-0 | | |-parent: 30 | | |-id: 36 | | |-message: Friend White :Re: A bigger message thread | | |-treeid: 31 | | |-0 | | | |-parent: 31 | | | |-id: 10 | | | |-message: Friend White :Re: A bigger message thread | | | \-treeid: 32 | | \-1 | | |-parent: 31 | | |-id: 48 | | |-message: Friend Pink :Re: A bigger message thread | | \-treeid: 33 | \-2 | |-parent: 28 | |-id: 47 | |-message: Friend Purple :Re: A bigger message thread | |-treeid: 34 | \-0 | |-parent: 34 | |-id: 5 | |-message: Friend White :Re: A bigger message thread | |-treeid: 35 | \-0 | |-parent: 35 | |-id: 3 | |-message: Friend Purple :Re: A bigger message thread | |-treeid: 36 | \-0 | |-parent: 36 | |-id: 21 | |-message: Friend Yellow :Re: A bigger message thread | |-treeid: 37 | \-0 | |-parent: 37 | |-id: 8 | |-message: Friend Purple :Re: A bigger message thread | |-treeid: 38 | \-0 | |-parent: 38 | |-id: 43 | |-message: Friend White :Re: A bigger message thread | |-treeid: 39 | \-0 | |-parent: 39 | |-id: 28 | |-message: Friend Purple :Re: A bigger message thread | |-treeid: 40 | \-0 | |-parent: 40 | |-id: 42 | |-message: Friend Brown :Re: A bigger message thread | |-treeid: 41 | \-0 | |-parent: 41 | |-id: 22 | |-message: Friend Purple :Re: A bigger message thread | \-treeid: 42 |-1 | |-parent: 0 | |-id: 9 | |-message: Friend Blue :RE: A bigger message thread | \-treeid: 43 |-2 | \-parent: 0 |-3 | |-parent: 44 | |-id: 49 | |-message: Some Subcription :Newsletter #1 | \-treeid: 45 |-4 | |-parent: 44 | |-id: 50 | |-message: Some Subcription :Newsletter #2 | \-treeid: 46 \-5 |-parent: 0 |-id: 32 |-message: Friend Red :A second mainthread |-treeid: 47 \-0 |-parent: 47 |-id: 16 |-message: Friend Black :Re: A second mainthread |-treeid: 48 \-0 |-parent: 48 |-id: 14 |-message: Friend Red :Re: A second mainthread |-treeid: 49 \-0 |-parent: 49 |-id: 6 |-message: Friend White :Re: A second mainthread |-treeid: 50 \-0 |-parent: 50 |-id: 44 |-message: Fiend Silver :Re: A second mainthread \-treeid: 51 

Algunas cosas de la nota:

  • La primera encarnación de este script agregó twigs incorrectamente al primer hijo de un nodo en lugar del propio nodo real, que ahora se arregla al almacenar también su padre.
  • imap_thread no es perfecto: vemos id=9 como un huérfano, aunque parece que debería estar en el primer hilo en algún lugar. Sin embargo, debido a que los encabezados no mencionan esto, Google Apps aquí decidió convertirlo en su propio nodo.
  • La tercera (clave = 2) entrada es un método para ‘regresar a la raíz’, ya que el método N.num.N.branch,N.next aparentemente no tiene otra forma de regresar a la raíz. Este es el /return to root $nodes[$treeid] = &$nodes[0]; poco. Puede / debe filtrar esto después de determinar todos los demás nodos, pero lo necesita para construir la matriz al principio.

Para obtener solo los nodos que inician hilos nuevos (Novena respuesta en el mensaje, 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); 

Lo que nos da:

 [35,15,46,33,48,47,9,49,50,32] 

Y, de hecho, 35,49,50 y 32 son inicios de subprocesos, 9 también es reconocido como tal por el servidor imap, y el rest son 2 o más respuestas que comienzan sus propias sucursales.

Ahora, de hecho, podrías dividir las twigs como una conversación separada, pero como puedes ver, a menudo son solo 1 o 2 respuestas más, los hilos más largos tienden a desarrollarse un poco más raramente. Para ver cómo van estas ‘twigs’:

 $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); 

Lo que te da raíces y twigs y sus respuestas:

 { "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 ] } 

Con algunas pequeñas alteraciones podemos recibir los mensajes allí:

 $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 ;) 

Otra opción es simplemente ordenarlos por el valor de fecha y hora, lo que estaría bien para conversaciones con ramificaciones pequeñas / insignificantes, probablemente haciendo que la mayoría del código que está planeando simplemente funcione.

Una combinación de los dos sería ‘twigs en movimiento’, seguir los hilos en serie, por lo que esto:

  1 2013-06-01 2 2013-06-02 3 2013-06-03 4 2013-06-03 5 2013-06-04 

Se convierte en una secuencia de 1,2,3,4,5 pero una respuesta en 3 recurriría:

  1 2013-06-01 4 2013-06-03 5 2013-06-04 2 2013-06-02 3 2013-06-03 6 2013-06-05 

Haciéndolo una secuencia de 1,4,5,2,3,6 , lo que lo mantendría como una conversación que fluye lógicamente, con siempre el hilo / twig con la última respuesta como última.