Getting Real Data From GitHub With Guzzle


In this video we are going to replace the existing static / mocked sidebar with the dynamic / real data from the GitHub API.

To do this, we are going to use Guzzle as our HTTP client. Guzzle makes it easy for us as PHP developers to make requests to external web servers - much like you might do using $.ajax() or fetch in JavaScript - from our web server.

It is worth pointing out a limitation in PHP applications at this point. We aren't using JavaScript at all in this example. JavaScript would allow us to move all of this logic to the front end. Using JavaScript, we could eliminate the need for hard page refreshes when we wanted to change the active profile, for example. With a plain PHP application (be it Symfony, Laravel, or other), once the page is rendered, the page does not change.

At this point you may be wondering: if JavaScript is so great, why the heck am I bothering to learn Symfony then?!

The answer to that, in as concise a manner as I can muster, is that the two (PHP and JavaScript) are complimentary, not competitiors. This Symfony tutorial application doesn't store any data in a database, which is partly why a JavaScript equivalent would be an acceptable alternative.

However, if we were storing data in a database, we could make use PHP for all of that sort of thing, and then interact with that data via a Symfony RESTful API. This is a more advanced topic, but it's important to be aware of it.

"Chris," I hear you say, "this is all very interesting, but why are we going off down JavaScript lane? I thought this was Symfony boulevard."

Good point, well spotted.

The reason for this is to highlight a difference between Guzzle and a JavaScript / front-end approach. Guzzle makes request from the server. JavaScript requests take place clientside.

This means we need a library (Guzzle) that lives on our server that is able to make outbound requests to, in this case, GitHub's API.

In a modern PHP project, whenever we need a new library we should be thinking composer, and the defacto first stop should be Packagist. Packagist is the main Composer repository. It lists all the public PHP packages installable with Composer.

There's a potential gotcha with Guzzle. The top result (at the time of writing) is the deprecated Guzzle which we don't want. The one we do want is guzzlehttp/guzzle. Packagist will give you instructions on how to install this using composer, but here it is also for reference:

composer require guzzlehttp/guzzle

Run this from the same directory you install Symfony in too. Or if you are developing locally and working on a remote server, then run the command server side, but remember to pull down your composer.json and composer.lock files.

These two files are incredibly important to your project.

composer.json - amongst other things, lists the versions of external dependencies you have installed in your project. As long as your project has this file, other developers can simply run composer install to download all the needed external dependencies that are needed to run your code. Very cool.

composer.lock - this is a little more confusing, or at least, I found its purpose initially confusing. The lock file is a point in time snapshot of the exact versions of the external dependencies installed in your project.

Keep both of these files under version control, PLEASE!

A Walk Down Composer Lane

Feel free to skip this entire section :)

But wait, the composer.lock file sounds extrememly similar to the composer.json file. Why do we need both?

The lock file will contain a commit hash referencing the exact version of code you have installed in your project. This becomes crucially important when you factor in humans.

Remember, composer handles our external dependencies. External means someone / something outside of our control. Software is difficult enough when we control everything. But now we are reliant on other people behaving properly. Which most people do, most of the time.

But not always.

There are a number of situations where you might end up with the same tag referencing multiple different commits in someone else's code. I'm getting quite off topic here, it's information that you don't need to know just yet, and it's a little advanced, so I will keep this fairly brief.

Let's quickly look at a small part of a composer.json file:

    "require": {
        "nesbot/carbon": "dev-master",
        "hashnz/twig-markdown-bundle": "dev-master",
    },

I've removed all other dependencies to highlight the biggest cause of headaches. I'm not picking on these projects, just a problem caused by using the dev-master tag.

The problem here is that dev-master references a branch which changes, potentially frequently.

When we first run composer install, we lock down to the commit hash in use by dev-master as of right now.

We run our test suite, and good news, everything passes.

The code gets deployed, and the new composer.json and composer.lock file get pushed up to some git host somewhere. I recommend GitLab :)

Let's pretend that two weeks go by and somehow, one of these two dev-master dependencies gets broken somehow under dev-master.

Fortunately, we committed our composer.lock file, so let's see how this plays out:

A new dev joins the team. She runs the composer install command and then runs the test suite. Things still pass.

Why?

When the new dev ran composer install, composer looked at the installed versions stored in our lock file, and then requested the same version by way of the exact commit hash. Even though the external code broke in a later commit, at the point in time we were locked too, things were working. Happy days.

The flip side to this is if we didn't commit the composer.lock file, our composer install command would still have worked. Instead of knowing exactly which version to get, it would have got the latest commit hash referenced by dev-master. Oops, things were broken in this commit. The tests fail. Things are bad. Sad panda.

Anyway, thats a part of why composer is important. :)

Making A Request

As we've already covered, Guzzle requests take place server side.

Though not immediately obvious - at least, not to me when I first used this version of Guzzle - we get back our response in the form of a Stream. You don't need to know anything about streams to use Guzzle, other than it's a very memory efficient way of dealing with varying sized response bodies.

Making a request is easy enough. First we need to create a new instance of Guzzle, and then we need to tell this instance of Guzzle who to talk too, and how (GET, POST, etc).

$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/users/codereviewvideos');

The tricky part comes in the form of the $response, which as mentioned, is a stream, not a ready to go piece of data.

To get the data back, we need to get the body, and then get the contents of the body:

$response->getBody()->getContents()

And this will be the raw JSON data returned from the GitHub API query.

As mentioned in an earlier video, we will then need to decode this JSON to a format that PHP can work with natively:

$data = json_decode($response->getBody()->getContents(), true);

This says: decode the JSON content from the response body, and store it into a PHP array. Then put all this into $data. The manual page for json_decode may help you further if unsure.

The good news is, we are now 90% of the way there. We just need to convert the returned GitHub JSON field names into the field names that our Twig Template is going to want:

$templateData = [
    'avatar_url'  => $data['avatar_url'],
    'name'        => $data['name'],
    'login'       => $data['login'],
    'details'     => [
        'company'   => $data['company'],
        'location'  => $data['location'],
        'joined_on' => 'Joined on ' . (new \DateTime($data['created_at']))->format('d m Y'),
    ],
    'blog'        => $data['blog'],
    'social_data' => [
        "Public Repos" => $data['public_repos'],
        "Followers"    => $data['followers'],
        "Following"    => $data['following'],
    ]
];

At this stage, the controller action is getting a little messy. The best thing to do is to extract this entire sidebar / profile data collection and preparation functionality into its own Symfony Service.

We cover this off in the video, but if you are unsure on Symfony Services then please check out this tutorial series where we cover services in greater depth.

One thing to note with Symfony services - at least, when using YAML to declare your services, such as in app/config/services.yml - is that the tabs / spacing is incredibly important. This isn't a Symfony thing, but rather a YAML thing. If you find that your Symfony application is unable to locate your service (such as the github_api service as defined in this video), then I would suggest running the entire contents of your services.yml file through a linting service, such as YAML Lint. This will quickly flag up any spacing inconsistencies which can otherwise cause relatively cryptic errors.

All that's left at this stage is to repeat the process for our Repositories section. We will do this in the next video, along with some extra bits of geekery to make things more exciting :)

Code For This Course

Get the code for this course.

Episodes