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 …
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.jsonfiles 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
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(...)
Instead of hard-coding a specific HTTP client, this method accepts any implementation of:
Symfony\Contracts\HttpClient\HttpClientInterface
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",
],
]);
And this client will be passed directly to DataBlock via the fromHttpJsonUrl() method:
Block::fromHttpJsonUrl($url, $httpClient);
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/symfonyhttps://api.github.com/repos/laravel/framework
And from Packagist:
https://packagist.org/packages/symfony/symfony/stats.jsonhttps://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,
);
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";
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";
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']
... 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";
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,
);
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";
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());
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");
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");
Let’s break this down.
The where() Method
The where() method filters a dataset based on a condition:
->where(field, operator, value)
In our case:
->where("name", Operator::LIKE, "symfony")
Means:
“Keep only the items where the
namefield contains the wordsymfony.”
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::EQUALOperator::NOT_EQUALOperator::GREATER_THANOperator::LESS_THANOperator::LIKE- …and others
In this example, we use:
Operator::LIKE
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")
This means:
“Sort the remaining items by the
faversfield, 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;
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
- PHP DataBlock GitHub repository: https://github.com/Hi-Folks/data-block
- Laravel News mentioned PHP DataBlock in an article: https://laravel-news.com/data-block
- Previous article about DataBlock: Handling Nested PHP Arrays Using DataBlock https://dev.to/robertobutti/handling-nested-php-arrays-using-datablock-26d7