Introduction

In my HermitClock project, I’ve set up an integration with the YouTube API to pull in recent uploads from the content creators who play on the Hermitcraft server. If you’re interested, you can read more about HermitClock here. After completing that update, I felt that my minimal, PHP YouTube client was worth sharing. If you’re looking to do something similar, interacting with the YouTube API via PHP, then look no further.

This post assumes:

  • You have a PHP project with Composer
  • You understand object-orientated PHP
  • You have an API key for the YouTube API

Installing Guzzle Http

Getting started with Guzzle is simple, it can be installed by running:

composer require guzzlehttp/guzzle

Once installed, assuming your composer packages are being autoloaded into your project correctly, you can access Guzzle from the following namespace:

<?php

namespace My\Namespace;

use GuzzleHttp\Client;

$client = new Client();

Creating a YouTube class file

HermitClock uses Laravel 11. I’ll be creating my YouTube client in app/Http/Youtube.php. Those more familiar with Laravel might notice that this file won’t be autoloaded from this directory. To fix that, add the following to the autoload directive within your project’s composer.php file:

    "autoload": {
        ...
        "files": [
            "app/Http/Youtube.php"
        ]
    },

Once done, it’s essential that you then run composer dump-autoload, without running this command, Composer won’t detect the change automatically.

Within the new app/Http/Youtube.php file, we’ll start it off with an empty class, and the required imports for Guzzle:

<?php

namespace App\Http;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

class Youtube
{
    // Upcoming
}

The core methods for our YouTube client

For the client to function, I’m adding the following methods:

  • __construct: The main constructor for the class, which initialises it with the required parameters.
  • get: A helper method to send GET requests via Guzzle, as this logic will be reused between various calls.
<?php

namespace App\Http;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\RequestException;

/**
 * Quota costs (Daily limit: 10,000)
 */
class Youtube
{
    public function __construct(
        private string $apiKey,
        private string $baseUrl = "https://www.googleapis.com/youtube/v3/",
        private ?Client $client = null,
        private bool $debug = false
    ) {
        // Allow client to be injected
        if ($client) {
            $this->client = $client;
        } else {
            $this->client = new Client([
                'base_uri' => $this->baseUrl,
                'timeout' => 30,
                'connection_timeout' => 10
            ]);
        }
    }

    /**
     * Sends a GET request to the API for the given endpoint
     */
    private function get(string $endpoint, array $params=[], array $default=[]): array
    {
        try {
            $response = $this->client->request('GET', $endpoint, $params);
            if ($response->getStatusCode() === 200) {
                $body = $response->getBody()->getContents();
                return json_decode($body, true);
            }
        } catch (RequestException $e) {
            if ($this->debug) {
                error_log('YouTube client GET request error: ' . $e->getMessage());
            }
        }
        return $default;
    }
}

While I won’t break this down line by line, if you study the code above you’ll notice a few things. In the __construct method, you’ll see that we’re passing an apiKey, baseUrl, client, and debug. These work as follows:

  • The API key is the YouTube API key you should have made for your project. I’d suggest loading this in via environment variables and then passing it to the class when instantiated.
  • The base URL is the YouTube API’s URL before any endpoint is chosen. This shouldn’t need to be changed, but if you’re doing any testing or using a proxy, you have that option.
  • The client allows you to pass a Guzzle client to this class. This is mainly for testing purposes, as you can mock a Guzzle client and then pass that to this class. If you do not pass a client, one is created for you.
  • Debug is an optional boolean that, when enabled, logs the output of any caught exceptions thrown during GET requests.

As mentioned above, the rest of the constructor method is just checking whether a client was provided and, if one wasn’t, it’s created.

I’ve added a comment of Quota costs (Daily limit: 10,000). This is a reminder that the YouTube API has a daily limit on free requests. Some requests cost 100 credits each, meaning you get 100 calls per day.

Moving on to the get method, this is the bread and butter of the whole operation.

Using the client attached to the class, this sends a GET request to the given endpoint along with the given parameters. If the response has a 200 status code (meaning success), it decodes the response data into an array and returns it. If the request didn’t get a 200 status code, a blank array is returned instead (which you can also set a default for, should you wish). This request is wrapped within a try>catch clock which looks out for any errors in the request. If an error is thrown, this gracefully handles it and logs the issue accordingly.

Adding specific endpoint calls to our client

Now that everything is set up, I’ll show you two example calls. The first accepts a channel ID as a parameter and then returns their recent videos. The second gets detailed information about a given video from its ID.

Get most recent YouTube videos

/**
 * Calls the 'search' endpoint, fetching the most recent video for the given YouTube channel.
 * Costs 100 credits
 * @param string $channelId The YouTube channel to fetch the video for
 */
public function mostRecentVideos(string $channelId, int $maxResults = 10): array
{
    $params = [
        'query' => [
            'key' => $this->apiKey,
            'channelId' => $channelId,
            'order' => 'date',
            'part' => 'snippet',
            'type' => 'video',
            'maxResults' => $maxResults
        ]
    ];
    return $this->get('search', $params);
}

Thanks to the setup we did earlier, making API calls is now as easy as creating a list of parameters, and then calling the get method.

As you can see, this call requires a channel ID and fetches the most recent videos for that channel. You can set the max results, but it defaults to 10.

This uses the Search endpoint.

Example of using this method:

$ytClient = new Youtube(env('YOUTUBE_API_KEY'));
$data = $ytClient->mostRecentVideos('someChannelId');

Get detailed YouTube video information

/**
 * Calls the 'videos' endpoint using a video ID.
 * Costs 1 credit
 * @param string $videoId The YouTube video to fetch the data for
 */
public function video(string $videoId): array
{
    $params = [
        'query' => [
            'key' => $this->apiKey,
            'id' => $videoId,
            'part' => 'snippet',
        ]
    ];
    return $this->get('videos', $params);
}

Similar to the previous example, this method accepts a video’s ID as a parameter, then fetches details about that video from the API.

This uses the Videos endpoint.

Example of using this method:

$ytClient = new Youtube(env('YOUTUBE_API_KEY'));
$data = $ytClient->video('someVideoId');

Conclusion

I hope this has been of some use to anyone looking to build a thin YouTube client for their project. While this isn’t a complete client for all endpoints, adding your own is very simple, and the construction of the client allows for easy testing and expandability.

If you haven’t already, check out some of my other posts covering PHP topics. If you’re new here, please consider saying hello or sharing with your friends on social media. Thank you for taking the time to check out my post.