DEV Community

Cover image for Exploring Real-World APIs with DataBlock
Roberto B.
Roberto B.

Posted on

Exploring Real-World APIs with DataBlock

Comparing Symfony and Laravel using GitHub and Packagist data

In the first article "Handling Nested PHP Arrays Using DataBlock", we explored DataBlock using a simple in-memory dataset to understand its core ideas: safe access, typed getters, and fluent navigation of nested PHP arrays.

That was useful to learn the library's methods, but in real projects, data rarely comes from hardcoded arrays.

More often, it comes from:

  • HTTP APIs
  • JSON or YAML configuration files
  • Tooling metadata (like composer.json)
  • External services with large, deeply nested responses

In this article, we’ll use real APIs to compare Symfony and Laravel and, at the same time, show how DataBlock shines when dealing with external, untrusted, and deeply nested data.

The setup

We’ll use:

  • GitHub API for repository statistics
  • Packagist API for download statistics and popular packages
  • Raw composer.json files from GitHub

And we’ll fetch everything using Symfony HTTP Client, letting DataBlock wrap the responses.

Installing DataBlock and the HTTP Client

To follow the examples in this article, you’ll need two packages:

  • DataBlock itself
  • An implementation of Symfony’s HTTP client

The PHP DataBlock GitHub repository: https://github.com/Hi-Folks/data-block

You can install both via Composer:

composer require hi-folks/data-block symfony/http-client
Enter fullscreen mode Exit fullscreen mode

DataBlock is completely framework-agnostic and works with plain PHP projects, Symfony, or any other setup.

Why DataBlock uses HttpClientInterface

In this article, we’ll be calling real APIs. For this, DataBlock provides a convenient helper:

Block::fromHttpJsonUrl(...)
Enter fullscreen mode Exit fullscreen mode

Instead of hard-coding a specific HTTP client, this method accepts any implementation of:

Symfony\Contracts\HttpClient\HttpClientInterface
Enter fullscreen mode Exit fullscreen mode

This design has a few important advantages:

  • You can use any compatible HTTP client (Symfony HttpClient, mocked clients, or custom implementations)
  • Your code stays testable (you can inject a fake client)
  • You stay decoupled from concrete implementations
  • DataBlock remains framework-agnostic

In our examples, we’ll use the Symfony HTTP Client, but you’re free to swap it with any client that implements the same interface.

Creating the HTTP Client

Here’s how we’ll create the client used in the rest of the article:

use HiFolks\DataType\Block;
use HiFolks\DataType\Enums\Operator;
use Symfony\Component\HttpClient\HttpClient;

$httpClient = HttpClient::create([
    "headers" => [
        "User-Agent" => "PHP DataBlock",
        "Accept" => "application/json",
    ],
]);
Enter fullscreen mode Exit fullscreen mode

And this client will be passed directly to DataBlock via the fromHttpJsonUrl() method:

Block::fromHttpJsonUrl($url, $httpClient);
Enter fullscreen mode Exit fullscreen mode

This keeps responsibilities clean:

  • The HTTP client handles transport
  • DataBlock handles data access and exploration

Fetching real data from GitHub and Packagist

To make this example concrete and enjoyable, we’ll work with real, public data from two widely used ecosystems: Symfony and Laravel.

We’ll query two different sources:

  • GitHub API, to retrieve repository information such as stars, forks, issues, and metadata
  • Packagist API, to retrieve download statistics for the main framework packages

More specifically, we’ll use:

  • https://api.github.com/repos/symfony/symfony
  • https://api.github.com/repos/laravel/framework

And from Packagist:

  • https://packagist.org/packages/symfony/symfony/stats.json
  • https://packagist.org/packages/laravel/framework/stats.json

Each of these endpoints returns a large JSON document with many nested fields, exactly the kind of data that is tedious and error-prone to navigate using plain PHP arrays.

With DataBlock, we can fetch and wrap these responses in a single step:

$symfonyGithub = Block::fromHttpJsonUrl(
    "https://api.github.com/repos/symfony/symfony",
    $httpClient,
);
$laravelGithub = Block::fromHttpJsonUrl(
    "https://api.github.com/repos/laravel/framework",
    $httpClient,
);

$symfonyStats = Block::fromHttpJsonUrl(
    "https://packagist.org/packages/symfony/symfony/stats.json",
    $httpClient,
);
$laravelStats = Block::fromHttpJsonUrl(
    "https://packagist.org/packages/laravel/framework/stats.json",
    $httpClient,
);
Enter fullscreen mode Exit fullscreen mode

From this point on, we never touch arrays directly.

Reading GitHub stats safely

GitHub responses are large, nested, and can change over time. We don’t want fragile code.

echo "Symfony GitHub:\n";
echo "Stars: " . $symfonyGithub->getInt("stargazers_count") . "\n";
echo "Forks: " . $symfonyGithub->getInt("forks_count") . "\n";
echo "Open issues: " . $symfonyGithub->getInt("open_issues_count") . "\n";
echo "Main language: " .
    $symfonyGithub->getString("language", "unknown") .
    "\n";
Enter fullscreen mode Exit fullscreen mode

Same for Laravel:

echo "Laravel GitHub:\n";
echo "Stars: " . $laravelGithub->getInt("stargazers_count") . "\n";
echo "Forks: " . $laravelGithub->getInt("forks_count") . "\n";
echo "Open issues: " . $laravelGithub->getInt("open_issues_count") . "\n";
echo "Main language: " .
    $laravelGithub->getString("language", "unknown") .
    "\n";
Enter fullscreen mode Exit fullscreen mode

At no point do we need to worry about whether a field exists or not.
There’s no isset() scattered around, no PHP warnings to silence, and no manual casting to keep track of. We can focus solely on what we want to extract from the data.

Reading Packagist download statistics

Packagist also returns a fairly rich and nested JSON structure. Even for something that sounds simple like “download counts”, the numbers are not at the top level of the response but grouped under a downloads object, with multiple levels and fields.

If you were working with plain arrays, you’d end up writing code like:

$data['downloads']['total']
$data['downloads']['monthly']
$data['downloads']['daily']
Enter fullscreen mode Exit fullscreen mode

... plus all the usual isset() checks to make sure nothing breaks at runtime.

With DataBlock, you can express exactly what you want to read using dot notation:

echo "Symfony downloads:\n";
echo "Total: " . $symfonyStats->getInt("downloads.total") . "\n";
echo "Monthly: " . $symfonyStats->getInt("downloads.monthly") . "\n";
echo "Daily: " . $symfonyStats->getInt("downloads.daily") . "\n";

echo "Laravel downloads:\n";
echo "Total: " . $laravelStats->getInt("downloads.total") . "\n";
echo "Monthly: " . $laravelStats->getInt("downloads.monthly") . "\n";
echo "Daily: " . $laravelStats->getInt("downloads.daily") . "\n";
Enter fullscreen mode Exit fullscreen mode

What’s nice here is that the shape of the JSON almost disappears from the code. You don’t have to care how deeply nested the structure is; you describe the path to the value you want.

Dot notation turns a deeply nested document into something that feels flat, readable, and easy to reason about, while still remaining safe and type-aware.

Parsing composer.json like a data structure

Now let’s fetch the composer.json files directly from GitHub:

$symfonyComposer = Block::fromHttpJsonUrl(
    "https://raw.githubusercontent.com/symfony/symfony/master/composer.json",
    $httpClient,
);
$laravelComposer = Block::fromHttpJsonUrl(
    "https://raw.githubusercontent.com/laravel/framework/master/composer.json",
    $httpClient,
);
Enter fullscreen mode Exit fullscreen mode

Getting nested typed (strin g) values with getString():

echo "Symfony PHP requirement: " .
    $symfonyComposer->getString("require.php") .
    "\n";
echo "Laravel PHP requirement: " .
    $laravelComposer->getString("require.php") .
    "\n";
Enter fullscreen mode Exit fullscreen mode

Exploring dependencies, getting proper blocks data and then accessing to the keys values:

$symfonyRequires = $symfonyComposer->getBlock("require");
$laravelRequires = $laravelComposer->getBlock("require");

print_r($symfonyRequires->keys());
print_r($laravelRequires->keys());
Enter fullscreen mode Exit fullscreen mode

This is structured document exploration, not just array access.

Filtering and sorting real datasets

So far, we’ve only been reading values. But DataBlock can also work with collections of structured items, not just single documents.

The Packagist endpoint we’re using returns a list of the 100 most popular packages. That means we’re no longer dealing with just a nested object, we’re dealing with a nested dataset.

$url = "https://packagist.org/explore/popular.json?per_page=100";
$popularPackages = Block::fromHttpJsonUrl($url, $httpClient)
    ->getBlock("packages");
Enter fullscreen mode Exit fullscreen mode

At this point, DataBlock provides a lightweight query engine for structured arrays.

Let’s start by selecting only the packages related to Symfony and Laravel:

$symfonyPopularPackages = $popularPackages
    ->where("name", Operator::LIKE, "symfony")
    ->orderBy("favers", "desc");

$laravelPopularPackages = $popularPackages
    ->where("name", Operator::LIKE, "laravel")
    ->orderBy("favers", "desc");
Enter fullscreen mode Exit fullscreen mode

Let’s break this down.

The where() Method

The where() method filters a dataset based on a condition:

->where(field, operator, value)
Enter fullscreen mode Exit fullscreen mode

In our case:

->where("name", Operator::LIKE, "symfony")
Enter fullscreen mode Exit fullscreen mode

Means:

“Keep only the items where the name field contains the word symfony.”

Each item in $popularPackages is itself a structured object (a package entry), and DataBlock applies this condition to all of them.

The Operator Enum

The Operator enum defines how the comparison should be done.

For example:

  • Operator::EQUAL
  • Operator::NOT_EQUAL
  • Operator::GREATER_THAN
  • Operator::LESS_THAN
  • Operator::LIKE
  • …and others

In this example, we use:

Operator::LIKE
Enter fullscreen mode Exit fullscreen mode

Which performs a string contains match.

Using an enum instead of raw strings makes the API:

  • Safer (no typos)
  • More explicit
  • Easier to discover via autocomplete

The orderBy() Method

After filtering, we sort the result:

->orderBy("favers", "desc")
Enter fullscreen mode Exit fullscreen mode

This means:

“Sort the remaining items by the favers field, in descending order.”

So now the most starred / favorited packages appear first.

Inspecting the results

After filtering and sorting the datasets, we don’t just want to know that something matched; we want to examine the result and understand what we actually obtained.

Because the result of where() and orderBy() is still a Block, we can keep using all the same tools we’ve seen so far: counting items, accessing nested fields, and safely reading values.

echo "Popular Packages:\n";

echo "Symfony: " . $symfonyPopularPackages->count() . PHP_EOL;
echo " High Favers: " .
    $symfonyPopularPackages->getString("0.name", "") .
    " - " .
    $symfonyPopularPackages->getInt("0.favers", 0) .
    PHP_EOL;

echo "Laravel: " . $laravelPopularPackages->count() . PHP_EOL;
echo " High Favers: " .
    $laravelPopularPackages->getString("0.name", "") .
    " - " .
    $laravelPopularPackages->getInt("0.favers", 0) .
    PHP_EOL;
Enter fullscreen mode Exit fullscreen mode

At this point, we’ve done quite a lot:

  • Filtered real datasets
  • Sorted them by meaningful fields
  • Counted and inspected the results
  • Navigated deeply nested structures
  • And all of this without ever touching raw arrays

Conclusion

In this article, we used DataBlock with real HTTP APIs to show that it’s not limited to local arrays or local files. It works just as well with remote JSON and YAML data, such as API responses and external documents.

Once the data is wrapped in a DataBlock, you can:

  • Access deeply nested values safely
  • Filter datasets
  • Sort and inspect results
  • Extract only the parts you actually care about

All without worrying about missing keys, broken structures, or manual casting.

DataBlock doesn’t try to replace DTOs or business logic. Its job is more straightforward and very focused: make structured data, wherever it comes from, easier and safer to explore, query, and consume.

References

Top comments (0)