Soluzione Grezza all'Ordinamento di un Array di Oggetti Pagina in Modo Gerarchico

Soluzione Grezza all'Ordinamento di un Array di Oggetti Pagina in Modo Gerarchico

A volte sorge la necessità di avere a disposizione un array di oggetti Pagina ordinato in modo gerarchico, ma tutto quanto disponibile è solo una collezione di oggetti Pagina ordinati per titolo per esempio, magari perché erano stati precedentemente recuperati dal database tramite l'oggetto globale $wpdb. In tali occasioni, per ordinare in modo gerarchico un array di questo tipo, dobbiamo essere creativi, perché WordPress non è stato provvisto di una funzione utilità atta allo scopo — o almeno non ho investigato abbastanza da scoprirne una. In verità, mi ha semplicemente divertito cercare una mia soluzione, ed è per lo più per questo che ora sono qui a scriverne.

In poche parole, ho raggiunto il mio obiettivo affrontando il problema in due fasi: la prima consiste nel creare un array temporaneo in cui gli elementi sono array di oggetti Pagina che condividono lo stesso valore di post_parent (pagine figlie), la seconda fase si riduce a combinare l'array temporaneo con un array di oggetti Pagina di primo livello (oggetti Pagina che hanno post_parent == 0) anch'esso nato all'interno dello stesso loop che ha riempito l'array temporaneo.

Non preoccuparti, descriverò ogni fase in dettaglio.

L'Array di Partenza

Il codice oggetto di questo post ha avuto origine dal ciclo di sviluppo di SiteTree 5.3, mentre stavo eseguendo il refactoring di un blocco di codice inteso a produrre una lista di tag <option> per un controllo a tendina (tag <select>). I tag <option> avevano la parte visibile indentata in modo da sembrare una lista gerarchica, che effettivamente rappresentava parte delle Pagine disponibili sul sito web.

La mia intenzione ora è giusto quella di fornire un contesto e una sorgente al nostro array di oggetti Pagina.

Suddetta lista di tag <option> era stata frutto della manipolazione di un array di oggetti Pagina progenie di una chiamata a $wpdb->get_results() molto simile a questa:

1global $wpdb;
2
3// It is important that the objects resulting from the query
4// have at least an 'ID' and a 'post_parent' attribute.
5$the_query = "SELECT ID, post_title, post_parent
6              FROM {$wpdb->posts}
7              WHERE post_type = 'page' AND post_status = 'publish' AND
8                    post_password = '' 
9              ORDER BY menu_order, post_title ASC";
10
11// The array of Page objects to order hierarchically.
12$pages = $wpdb->get_results( $the_query );

Voglio sottolineare che affinché il codice che sto per descriverti funzioni, gli oggetti Pagina nell'array da riordinare devono essere provvisti almeno degli attributi ID e post_parent, a prescindere che tali oggetti siano o meno istanze della classe WP_Post.

Prima Fase: La Cernita

In questa prima fase eseguiremo un loop attraverso i nostri oggetti Pagina in modo da creare due array distinti. Il primo, $ordered_pages, sarà un semplice array di oggetti Pagina di primo livello, il secondo, $pages_by_parent, sarà invece un array multidimensionale, i suoi elementi saranno array di oggetti Pagina accomunati dallo stesso valore di post_parent.

È fondamentale per l'attualizzazione della seconda fase che le chiavi di $ordered_pages siano uguali in valore all'attributo ID degli oggetti Pagina contenuti nell'array stesso, e che le prime chiavi dell'array multidimensionale $pages_by_parent siano uguali ai valori che l'attributo post_parent assume per ogni gruppo di pagine figlie contenuto in $pages_by_parent.

1// Variable that will store the ordered array 
2// of Page objects.
3$ordered_pages = array();
4
5// Temporary array where the elements are arrays 
6// of Page objects having all the same 
7// value of 'post_parent' (child pages).
8$pages_by_parent = array();
9
10// Associative array used to temporarily 
11// collect all the ids of the Page objects 
12// to order.
13$ids = array();
14
15// We populate the $ids array and, to make the most 
16// out of the loop, we also sanitise the Page objects.
17// This preliminary step is of paramount importance in 
18// ensuring that the loop we'll meet in the second section 
19// of this post it actually ends, instead of degenerating 
20// into an infinite iteration.
21foreach ( $pages as $page ) {
22  $page->ID          = (int) $page->ID;
23  $page->post_parent = (int) $page->post_parent;
24  
25  // The ID of each Page object is stored 
26  // both as key and as value.
27  $ids[$page->ID] = $page->ID;
28}
29
30foreach ( $pages as $page ) {
31  if (
32    // When 'post_parent' is 0, 
33    // it means that the object is a top-level Page.
34    ( $page->post_parent === 0 ) ||
35
36    // This condition checks whether the Page's parent 
37    // is among the elements of our array of Page objects, 
38    // if it isn't, the Page is an "orphan" 
39    // and so it is treated as a top-level Page.
40    !isset( $ids[$page->post_parent] )
41  ) {
42    $ordered_pages[$page->ID] = $page;
43  }
44  else {
45    // Notice that the first keys of $pages_by_parent 
46    // are equal to the values the 'post_parent' attribute 
47    // takes for each group of Page objects.
48    $pages_by_parent[$page->post_parent][$page->ID] = $page;
49  }
50}

Seconda Fase: La Fusione

Per ricapitolare, completare il riordinamento implica fondere $pages_by_parent con $ordered_pages, array che contiene ancora solo oggetti Pagina di primo livello.

Quindi ciò che occorre fare ora è inserire i gruppi di oggetti contenuti in $pages_by_parent nelle opportune posizioni dell'array $ordered_pages, e ripetere l'operazione per ogni livello di nidificazione.

La scelta fatta durante la prima fase riguardo le chiavi dei due array da fondere è di primaria importanza nell'assicurare che l'uso di risorse del server venga tenuto a bada.

1// With each iteration, the content of 
2// one or more arrays making up $pages_by_parent 
3// is joined to the content of $ordered_pages.
4while( $pages_by_parent ) {
5  // Temporary array used to merge the groups 
6  // of child pages contained in $pages_by_parent 
7  // with the Page objects in $ordered_pages.
8  $array = array();
9  
10  foreach( $ordered_pages as $page_id => $page ) {
11    $array[$page_id] = $page;
12    
13    // Checks whether the current $page object 
14    // has child pages.
15    if ( isset( $pages_by_parent[$page_id] ) ) {
16      // Appends a group of child pages 
17      // to the temporary array.
18      foreach ( $pages_by_parent[$page_id] as $child_page_id => $child_page ) {
19          $array[$child_page_id] = $child_page;
20      }
21
22      // This is essential for the While loop 
23      // to draw to an end.
24      unset( $pages_by_parent[$page_id] );
25    }
26  }
27
28  // With each execution of the loop, 
29  // are inserted into $ordered_pages
30  // as much Page objects as each level of
31  // nesting is composed of. 
32  $ordered_pages = $array;
33}

Usando Funzioni PHP Native

Implementare questa seconda fase in modo grezzo è stata solo una preferenza personale, infatti il codice può essere riscritto usando funzioni PHP native senza troppi problemi, anche se, non giurerei che le prestazioni ne possano giovare.

Ti lascio con un esempio di detta alternativa:

1// With each iteration, are inserted 
2// into $ordered_pages as much child pages 
3// as each level of nesting is composed of. 
4while( $pages_by_parent ) {
5  foreach( $pages_by_parent as $parent_id => $child_pages ) {
6    // Checks whether the current group of 
7    // child pages has a parent in $ordered_pages.
8    if ( isset( $ordered_pages[$parent_id] ) ) {
9      $insert_position = 1 + array_search( $parent_id, 
10                                           array_keys( $ordered_pages ) );
11      
12      // We split $ordered_pages into two distinct arrays 
13      // according to the computed value of $insert_position.
14      // To perform such task we use array_slice() 
15      // so as to preserve the numeric keys.
16      $ordered_pages_head = array_slice( $ordered_pages, 0, 
17                                         $insert_position, true );
18      $ordered_pages_tail = array_slice( $ordered_pages, $insert_position,
19                                         null, true );
20      
21      // Using the + operator for the merging
22      // ensures that the numeric keys are preserved.
23      $ordered_pages = $ordered_pages_head + $child_pages;
24
25      // Appends $ordered_pages_tail only 
26      // if it isn't an empty array.
27      if ( $ordered_pages_tail ) {
28        $ordered_pages += $ordered_pages_tail;
29      }
30
31      // This is essential for the While loop 
32      // to draw to an end.
33      unset( $pages_by_parent[$parent_id] );
34    }
35  }
36}
TOP