DEV Community

Cover image for Eager Loading Without Eloquent: Laravel Collection hasMany
victorstackAI
victorstackAI

Posted on • Originally published at victorstack-ai.github.io

Eager Loading Without Eloquent: Laravel Collection hasMany

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

I had two collections of plain arrays and needed to associate them relationally, but I was not working with Eloquent models. Laravel's Collection class is powerful, but it has no built-in way to express a one-to-many relationship between two arbitrary datasets. I built laravel-collection-has-many to fix that.

What It Is

laravel-collection-has-many is a small PHP library that registers a hasMany() macro on Laravel's Collection class. It lets you attach a related collection to a parent collection using foreign key and local key fields, exactly like Eloquent's eager loading, but for plain data. After calling $users->hasMany($posts, 'user_id', 'id', 'posts'), each user in the collection gains a posts property containing their matched items.

flowchart LR
    A[Parent Collection\nusers] --> B[hasMany Macro]
    C[Related Collection\nposts] --> B
    B --> D[Group Related by Foreign Key\nO(n+m) single pass]
    D --> E[Attach to Parent\neach user gains 'posts' property]
    E --> F[Enriched Collection\nusers with nested posts]
Enter fullscreen mode Exit fullscreen mode

The core functionality is unchanged: O(n+m) grouping instead of naive nested iteration, support for both arrays and objects, auto-wrapping results in collections, and fully customizable key names.

Tech Stack

Component Technology Why
Language PHP 8.1 - 8.4 Full matrix CI across all active versions
Framework Laravel Collections Macro system, no full framework required
CI GitHub Actions Every push/PR tests all PHP versions
Package format Composer (Packagist-ready) composer require just works
License MIT Use it, fork it, vendor it

What Changed

This update is about publication readiness, not new features.

💡 Tip: Ship the Infrastructure Before the Package

A working library without CI, a license, and a changelog is a personal project. A working library with all three is a dependency other teams can adopt. Get the CI matrix, the license file, and the changelog in place before you tag v1.0.0.

⚠️ Caution: No License = No Adoption

The package was functional but had no explicit license, which blocks adoption in any organization with a legal review process. If you are building a package you want others to use, MIT LICENSE is the first file you add, not the last.

```php title="usage-example.php" showLineNumbers
use Illuminate\Support\Collection;

$users = collect([
['id' => 1, 'name' => 'Alice'],
['id' => 2, 'name' => 'Bob'],
]);

$posts = collect([
['user_id' => 1, 'title' => 'First Post'],
['user_id' => 1, 'title' => 'Second Post'],
['user_id' => 2, 'title' => 'Bob Writes'],
]);

// highlight-next-line
$users->hasMany($posts, 'user_id', 'id', 'posts');

// Alice now has $user['posts'] => Collection of 2 posts
// Bob now has $user['posts'] => Collection of 1 post




</TabItem>
<TabItem value="ci" label="CI Matrix">



```yaml title=".github/workflows/test.yml" showLineNumbers
strategy:
  matrix:
php-version: ['8.1', '8.2', '8.3', '8.4']
steps:
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-version }}
- run: composer install
  # highlight-next-line
- run: vendor/bin/phpunit
Enter fullscreen mode Exit fullscreen mode

MIT LICENSE added. GitHub Actions CI runs the test suite against a PHP 8.1, 8.2, 8.3, and 8.4 matrix. Every push and pull request triggers the pipeline. CHANGELOG.md with a v1.0.0 entry documents the initial stable release. The package is now ready for Packagist publication.

Publication checklist

Item Status
MIT LICENSE Added
GitHub Actions CI (PHP 8.1-8.4) Running
README badges (CI, PHP versions, license) Added
CHANGELOG.md (v1.0.0) Added
Composer metadata + autoloading Configured
Packagist-ready Yes

Why this matters for Drupal and WordPress

Drupal developers often work with plain arrays from custom database queries, Views results, or JSON:API responses that need relational grouping without Eloquent. This library's hasMany pattern works on any PHP array collection, making it directly usable in Drupal custom modules or migration scripts where you need to associate parent-child data efficiently. WordPress developers face the same challenge when joining wp_posts with wp_postmeta or WooCommerce order items outside of WP_Query -- the O(n+m) grouping approach avoids the N+1 trap that makes custom WordPress admin dashboards slow.

Technical Takeaway

Ship the infrastructure before you ship the package. A working library without CI, a license, and a changelog is a personal project. A working library with all three is a dependency other teams can adopt. The code in this update is identical to the previous version -- the value is entirely in the packaging. If you are building a Laravel package you intend others to use, get the CI matrix, the license file, and the changelog in place before you tag v1.0.0. The cost is an afternoon. The payoff is that your first public release is credible from day one.

References


Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.


Looking for an Architect who doesn't just write code, but builds the AI systems that multiply your team's output? View my enterprise CMS case studies at victorjimenezdev.github.io or connect with me on LinkedIn.

Originally published at VictorStack AI — Drupal & WordPress Reference

Top comments (0)