It’s not rocket science using WP_Query to return objects from posts in WordPress. Also, it’s relatively simple encoding or normalizing the responses to use data as associative arrays or JSON. However, there are many reasons why we could prefer our queries to return in different formats: JSON itself, XML, CSV our even Yaml.

I personally needed to create lately an endpoint to return data in CSV. Of course I’d prefer it to come in JSON or even as an object, but my client’s needs have been well-paid. So he got the data in CSV.

While it could be managed in a number of different approaches, I started thinking about the possibility of similar requests from clients in the future. Then I remember the simplicity of Symfony Serializer component. If I could successfully extend the WP_Query class to work under Symfony’s multiple encoders and serializers, then I would be able to generate query responses or even endpoint responses in a pocketful of interesting formats.

Thinking of integrations

Unfortunately, not every web service, API or software in the world work with the same input and output formats. That would be fantastic, but even in the foreseeable future, we can’t expect something like that to happen. So, in the meantime, we need to convert files and adapt requests and responses.

Although WP_Query is mostly for internal requests, it is commonly used as data provider for WP API endpoints. Also, it is used to grab posts and data and generating export files. Especially for this last situation, getting query responses automatically in different formats could be really handy.

So let’s use a simple strategy – in order to not affect the main query or the usual wp query system, the easier and safer option is to extend the WP_Query class. And once we are planning to use Symfony’s components, let’s name it WP_Serializer_Query, to match the selected component.

namespace Plugin;

class WP_Serializer_Query extends \WP_Query
{
}

In order to use Symfony Serializer to generate responses in all formats, we need to require three different libraries using Composer:

  • symfony/serializer
  • symfony/property-access
  • symfony/yaml

So, the next step is use all relevant classes from Symfony to attain what we need with the class we just created. Also, to not mess with WP_Query original code, let’s create a new method output() for managing the conversions. Thus:

namespace Plugin;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\YamlEncoder;

class WP_Serializer_Query extends \WP_Query
{
    /**
	 * Serialize and encode WP_Query object output to json, xml or csv
	 * 
	 * @author 	WP Helpers | Carlos Matos
	 * 
	 * @return  $encoder or $serializer
	 */
	public function output() {}
}

Limiting serializing and results to posts

WP_Query object holds much more than just the post query. For obvious reasons, we don’t want any further data to be serialized and then converted in the new formats – just the post data. This way, we’ll limit work on the property $posts from WP_Query class.

To make things easier, we want the custom response format to be passed as an additional parameter, together with all other parameters usually passed as $args to queries. Let’s call it response_format. Now, let’s start building the new method we created for generating the custom output.


// ...

public function output() {

    if($this->query['response_format'] == 'xml') {
        $normalizer = new ObjectNormalizer(); // Symfony normalizer
	$encoder = new XmlEncoder(); // Symfony XML encoder
	$serializer = new Serializer([$normalizer], [$encoder]); // Instace of Serializer

	$rt = $this->posts; // Just post data
	$ry = json_decode(json_encode($rt)); // Optimal format for Symfony's processing

	$output = $encoder->encode($ry, 'xml'); // XML encoding
        return htmlspecialchars($output); // Basic HTML entities conversion, just in case
    }

// ...

This will return the query results as XML. The same process can be developed to have the response in other formats. Let’s try CSV instead. All we need to do is creating a new possibility to the argument response_format (could be either elseif or switch-case).

// ...

public function output() {

    if($this->query['response_format'] == 'xml') {
    // XML response
    } elseif($this->query['response_format'] == 'csv') {
        
        $normalizer = new ObjectNormalizer();
	$encoder = new CsvEncoder(); // CSV encoder
	$serializer = new Serializer([$normalizer], [$encoder]);

	$rt = $this->posts;
	$ry = json_decode(json_encode($rt));

	$output = $serializer->serialize($ry, 'csv'); // This time, serialize(), instead of encode()

	return htmlspecialchars($output);
    }

In our original library, we also covered JSON and YAML responses, as per Symfony Serializer’s encoders. You can find the library for using the class or requiring through Composer, by accessing our Github repo (end of the article).

Using the library

As standalone class or as a library, WP_Serializer_Query can be used just as its parent class. The only difference is the extra argument that can be passed, to determine the response format. Also, the object itself will return the same results than the usual WP_Query instance. The formatted response will be generated by using the method output().

$query = new WP_Serializer_Query(array(
	'numberposts' => -1, 
	'response_format' => 'csv' // optional argument for custom formats
));

var_dump($query); // Regular WP_Query response
var_dump($query->output()); // CSV response, in this case

Easy, huh? So check our Github repo for the complete class, and leave your comment on this experience, or ideas for new ones.

Dark Mode