1

I am reading a book called "Learn to Create WordPress Themes by Building 5 Projects" . In that to retrieve the parent for pages, they have a function to get the parent of the pages. The function is as follows,

function page_is_parent(){
global $post;
$pages = get_pages('child_of='.$post->ID);
return count($pages); 
}

Explanation given in the book is as follows,

Now we just have About. We can navigate using the menu here. However, if we go to Sample Page or any other page, it's going to still have this even though there's no child links. So, we'll create another short function in the functions.php file and call page_is_parent. Then, we'll say global $post and set $pages equal to get_pages(), and in here, we'll say 'child_of=' and concatenate the post ID. Next, we'll say return and then, we want the number of pages, so we'll count($pages):

But I feel the $pages = get_pages('child_of='.$post->ID); is weird since they pass a string 'child_of='.$post->ID Please explain why a string is passed and why it is concatenated.

2 Answers 2

1

It is concatenated because that's how we combine strings together in PHP, and string is passed because arguments can be passed as a query string or an array of key => value pairs.

See the get_pages() documentation for more details.

// So both of these will work:

// 1. Pass string of arguments.
$pages = get_pages( 'child_of=' . $post->ID );

// 2. Pass array of arguments.
$pages = get_pages( array(
    'child_of' => $post->ID,
) );

So if I had to give you a reason why would I use the former (the first one in the above example), then it'd be: because it actually works and simpler in the case of the argument in question.

And one could also use double quotes instead, e.g. "child_of={$post->ID}", but if you need explanation on that, you should ask on Stack Overflow :)

6
  • 1
    PS: You may also find this useful: codex.wordpress.org/How_to_Pass_Tag_Parameters
    – Sally CJ
    Jul 17, 2022 at 6:02
  • 1
    In more than 15 years, this is first time I've ever seen that Codex page. The WordPress rabbit hole knows no bounds :D
    – bosco
    Jul 17, 2022 at 7:01
  • 1
    I have found that the current "Developer Hub" seems to be missing a lot of the comments and context which were present in the Codex. In some cases the "Handbooks" are outright omissive, completely passing over important points to understand WTF WordPress expects you to do. And more often than not the Code Reference just provides the code comments without any usage examples or argument detail. It's sure a whole lot prettier, but I'm not certain if it's made it any easier for new developers to jump into the game :(
    – bosco
    Jul 17, 2022 at 7:22
  • 1
    Can I simply say that I agree? 🙊 And maybe the code reference should have a warning that reads "for experienced developers only"... 😆 just kidding. But I'm guessing the code ref was purposely made simple, i.e. just to tell us what a function/hook is for, the correct syntax, arguments that should be passed or that will be passed in the case of hooks, etc. You know, it's like just a UI for the source code, like what phpDocumentor does.. Thankfully, comments can be posted to the code ref pages, which can help add the missing pieces.
    – Sally CJ
    Jul 17, 2022 at 7:50
  • 1
    That's totally fair on all accounts - and truthfully I should just contribute instead of whining on the periphery 😄. But I'll shut up now before Tom ejects us into a chatroom come Monday 🙊
    – bosco
    Jul 17, 2022 at 7:56
3

Indeed - in the get_pages() documentation we can see that it accepts either a string or an array as an argument. It does seem a little bit weird to call a function with the arguments concatenated into a string, doesn't it?

Under the hood, the get_pages() function uses the WP_Query class in order to query for Page-type posts, and the root of the ambiguity boils down to WP_Query's implementation. The class was built such that it can accept a string of query arguments which will be converted into a more appropriate data structure - an associative array of arguments - before creating the actual SQL query to retrieve data from the database. The reason WP_Query was built this way is to accommodate queries which are specified via the "querystring" or "search string" in the URL (the list of key/value pairs which begins after a ? and is delineated with an & thereafter).

In my opinion, I would agree with your skepticism - it doesn't make sense to me to use a query in string format when writing an internal function such as the one described by the book, more specifically for two reasons:

  • String concatenation is inelegant and harder to interpret for humans and code analysis tools alike. The only reason WP_Query is capable of interpreting string arguments to begin with is out of necessity for parsing URLs.
  • Query arguments as a string require WP_Query to perform a tiny amount of additional work to convert them back into usable data.

In my subjective opinion, without describing why it might implement the function in the manner which it has, the book's example should be implemented with an associative array instead:

function page_is_parent(){
  global $post;

  $pages = get_pages(
    [
      'child_of' => $post->ID,
    ]
  );

  return count( $pages ); 
}

Additional Critique

I would go further to critique a few other points of that function.

  • I feel it is named inappropriately - the name the author has given to the function does not describe what it does. The function returns the number of pages which the current Page is a parent to. It can be used for the purpose implied by the author's chosen name in that it will return a "truthy" value if the current page is a parent to one or more pages and a "falsey" 0 if it is not. But the name does not actually describe what the function actually does which can lead to misuse. if( page_is_parent() > 1 ) hardly makes sense to anyone but the person who wrote the function.
  • Whenever possible, it is best to avoid directly accessing WordPress's global values in order to avoid circumventing filtration and sanitization logic which might otherwise be provided by accessor APIs. Instead of global $post, we have a convenient substitute in the form of get_post() which will access the same global if necessary.

The above in mind, I personally would implement the proposed function as follows:

function get_child_page_count() {
  $post = get_post();

  $child_pages = get_pages(
    [
      'child_of' => $post->ID,
    ]
  );

  return count( $child_pages );
}

You can read if( get_child_page_count() > 1 ) and know exactly what's going on a whole lot better than if( page_is_parent() > 1) - that's super-nice, isn't it?


An Advanced Form

The book may well get into this at some point, but for the purposes of counting the number of child pages, the provided query is inefficient. get_pages() retrieves the full data for every child page when all the function cares about is how many there are.

WP_Query provides a mechanism which get_pages() does not - the ability to only ask for certain columns instead of full rows via the fields parameter. Further, the WP_Query object provides a found_posts property detailing how many posts matched the query in total even if they were not returned "on the current page." So ultimately, we can ask for a single matching page's ID but still retrieve the total number of matching pages. This should speed things up a wee bit - not enough to matter when you're only dealing with a moderate number of child pages, but it can definitely make a difference when you're working with a lot of pages.

As a final point of critique, we can expand the function such that not only will it consider the current post when utilized within The Loop, but we can also pass an argument to have it calculate the number of child Pages for a specific Page/Page ID.

All together, I would implement the function as follows:

function get_child_page_count( $page = null ) {
  // Retrieve the post object specified by the $page argument, or from
  //   the global $post if the argument is omitted.
  $post = get_post( $page );

  $query = new WP_Query(
    [
      'post_type'      => 'page',    // Query for pages...
      'post_parent'    => $post->ID, // ...which have a parent of the page passed as an argument (or the current page if omitted)...
      'fields'         => 'ids',     // ...and only retrieve their IDs...
      'posts_per_page' => 1,         // ...but only retrieve 1 from the database.
    ]
  );

  return $query->found_posts; // Return the total number of matching posts.
}

Of course, you could also write that query as

$query = new WP_Query( 'post_type=page&post_parent=' . $post->ID . '&fields=ids&posts_per_page=1' );

...but that's utter nonsense, in my book ;)

1
  • 1
    Well that's a good nonsense that makes a lot of sense. And to be honest, it's been a loooong time since I actually passed arguments as query string in actual implementation... and I agreed on using the found_posts to get the total number of posts. So basically, +1 :)
    – Sally CJ
    Jul 17, 2022 at 7:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.