1
\$\begingroup\$

For a Laravel project I needed to show latest three blog posts on the homepage. The blog was made with Wordpress and after some research I found a cool plugin called REST API that allows to get a json object of posts.

I made a Laravel Controller that uses curl to get the posts and send the response to jquery in the homepage, So as to build the widget dynamically.

The solution works well and I would like to know if there are parts to improve.

Php Class.

/**
 * Developer: My name
 * Date: 20/07/2016
 * Time: 11:08
 */

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Carbon\Carbon;



class WpBlogController extends Controller
{
    /*
     * Using Wordpress Rest API
     * http://v2.wp-api.org/
     *
     * Plugins to Install in Wordpress:
     * 1. https://wordpress.org/plugins/rest-api/
     * 2. https://wordpress.org/plugins/better-rest-api-featured-images/
     * 3. https://wordpress.org/plugins/wp-rest-api-pure-taxonomies/
     */

    const WP_USERNAME = "my_wp_username";
    const WP_PASSWORD = "my_wp_password";
    const WP_API_URL = "http://my.wp-blog.example/wp-json/wp/v2";


    public function getLatestThreePosts(){

        $curl = $this->getCurl();

        //set the right url to get only three posts
        curl_setopt($curl, CURLOPT_URL, self::WP_API_URL . "/posts?per_page=3&page=1");

        $raw_posts = curl_exec($curl) or abort(502); //bad gateway error if request fails

        //decode json and get the stdClass object
        $raw_posts = json_decode($raw_posts);


        $formatted_posts = [];

        foreach($raw_posts as $post){

            //some operations on the original html to get only the text
            $curpost = [
                "title" => trim(strip_tags($post->title->rendered)),
                "content" => trim(strip_tags($post->content->rendered)),
                "url" => $post->link,
                "date" => Carbon::parse($post->date)->formatLocalized('%d %B %Y')
            ];

            //abrreviate the post content.
            $curpost["abbr"] = substr($curpost["content"],0,200).'...';

            //get the featured image link if present
            if (!is_null($post->better_featured_image)){
                $curpost["thumb"] = $post->better_featured_image->media_details->sizes->medium->source_url;
            }

            //get the post categories
            $cat_list = [];
            foreach($post->pure_taxonomies->categories as $tax_obj){
                $cat_list[] = $tax_obj->name;
            }

            $curpost["categories"] = $cat_list;
            $formatted_posts[] = $curpost;

        }

        return response()->json($formatted_posts);
    }



    //returns a curl instance with http auth header already set 
    private function getCurl(){

        // using http basic auth
        $auth_header = ["Authorization: Basic " . base64_encode(self::WP_USERNAME . ":" . self::WP_PASSWORD)];

        $curl = curl_init();
        curl_setopt_array($curl, array(
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_HTTPHEADER => $auth_header
        ));

        return $curl;
    }
}

Jquery Code.

//load wp posts
function loadPosts(){
    $.ajax({
        url: '/api/blog/get-latest-three-posts',
        method: 'GET',
        dataType: "json",
        success: function (posts, txtStatus, xhr) {
            $.each(posts, function (index, post) {
                //console.log(post);
                var post_html = '<div class="post-block">';

                if (post.hasOwnProperty('thumb')) {
                    post_html += '<div class="post-thumb">';
                    post_html += '<img src="' + post.thumb + '" alt="Post Thumb">';
                    post_html += '</div>';
                }

                post_html += '<div class="post-body">';
                post_html += '<div class="post-categories">';
                $.each(post.categories, function (index, category) {
                    post_html += '<span class="subcategory-label">' + category + '</span>';
                });
                post_html += '</div>';
                post_html += '<h3><a href="' + post.url + '"target="_blank">' + post.title + '</a></h3>';
                post_html += '<p class="post-date"><small>' + post.date + '</small></p>'
                post_html += '<p>' + post.abbr + '</p>';
                post_html += '<div class="text-left">'
                post_html += '<a href="' + post.url + '" class="btn post-read-all" target="_blank">Leggi tutto</a>'
                post_html += '</div>';
                post_html += '</div>';
                post_html += '</div>';

                $('#wp-posts').append(post_html);
            });
        },
        error: function (xhr) {
            $('#wp-posts').append('<p><strong>Impossibile caricare i post in questo momento</strong></p>');
        }

    });

}
\$\endgroup\$
  • \$\begingroup\$ I don't have experience with REST API but in MVC structure controller contains only basic logic : instantiate object, delegate data to function etc. It should be about 1-10 lines of code long and very rarely longer. \$\endgroup\$ – xReprisal Sep 5 '16 at 13:59
2
\$\begingroup\$

In this example I'm using this curl wrapper.

I have created separate class to take care of querying rest api as this logic should not be in controller.

Names could use some more polishing but in my example I make camel case names and I stick with those. If you are more comfortable with snake case you should stick with it only, do not mix them up.

I have tried to make this code readable. You don't have to see low level operations to know what happens in function. ex getAbbrievation($text) gives you pretty good idea what it returns and you don't even have to see how it does it.

Configuration should not be mixed up with logic. Put it in configuration file and load it in when needed.

It is very important to write unit tests to make sure it works as expected but I leave this to you.

$rawPost->better_featured_image->media_details->sizes->medium->source_url good thing that I have getThumb otherwise I would have to look at this piece of code and get distracted trying to figure what each of this relations stands for.

Use comments only in those places in which you failed to clearly communicate with proper naming what your code does.

You want to make writing code as enjoyable as possible and only clean code can be enjoyable to work with. I would put readability of code as high as its ability to perform given task.

<?php

namespace App\Http\Controllers\Api;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Carbon\Carbon;

class WpBlogController extends Controller {

    public function getThreeLatestPosts() {
        $blog = new WpBlog(new Curl());

        return $blog->getLatestPosts("/posts?per_page=3&page=1");
    }

}

class WpBlog {
    private $curl;
    private $username;
    private $password;
    private $apiUrl;

    public function __construct(Curl $curl) { // dependency injection to enable mocking in tests
        $this->curl = $curl;

        // You place configuration variables in configuration file.
        // I have read that below is how you retrieve config variables in Laravel but it might be wrong
        $this->username = Config::get('wp_username');
        $this->password = Config::get('wp_password');
        $this->apiUrl = Config::get('wp_api_url');
    }

    public function getLatestPosts($query) {
        $rawPosts = $this->curlGet($this->apiUrl.$query);
        $posts = [];
        foreach($rawPosts as $rawPost) {
            $posts[] = $this->formatPost($rawPost);
        }
        return response()->json($posts);
    }

    private function curlGet($url) {
        $this->curl->setOpt(CURLOPT_RETURNTRANSFER, true);
        $this->curl->setOpt(CURLOPT_HTTPHEADER, ["Authorization: Basic " . base64_encode($this->username . ":" . $this->password)]);
        return json_decode($this->curl->curlGet($url));
    }

    private function formatPost($rawPost) {
        $post = [
            "title" => $this->formatText($rawPost->title->rendered),
            "content" => $this->formatText($rawPost->content->rendered),
            "url" => $rawPost->link,
            "date" => $this->formatDate($rawPost->date)
        ];
        $post["abbr"] = $this->getAbbreviation($post["content"]);
        $post["thumb"] = $this->getThumb($post["content"]);
        $post["categories"] = $this->getPostCategories($rawPost);
        return $post;
    }

    private function getPostCategories($rawPost) {
        $categories = [];
        foreach($rawPost->pure_taxonomies->categories as $category) {
            $categories[] = $category->name;
        }
        return $categories;
    }

    private function getThumb($rawPost) {
        return $rawPost->better_featured_image->media_details->sizes->medium->source_url;
    }

    private function getAbbreviation($text) {
        return substr($text,0,200).'...';
    }

    private function formatText($text) {
        return trim(strip_tags($text));
    }

    private function formatDate($date) {
        return Carbon::parse($date)->formatLocalized('%d %B %Y');
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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