Skip to content

Collections

Collections provide a fluent, convenient wrapper for working with arrays of data. They are particularly useful when working with groups of models or processing data sets in your application.

Introduction

The AvelPress Collection class is inspired by Laravel's Collection and provides dozens of methods for working with arrays in a more expressive and functional way. Collections are especially useful when working with Eloquent model results, but can be used with any array data.

Creating Collections

Manual Creation

php
use AvelPress\Support\Collection;

// Create from array
$collection = new Collection([1, 2, 3, 4, 5]);

// Create from models
$users = User::all(); // Returns a Collection
$posts = Post::where('status', 'published')->get(); // Returns a Collection

Collection Helper

php
// Using the collect helper (if available)
$collection = collect([1, 2, 3, 4, 5]);
$collection = collect(['name' => 'John', 'age' => 30]);

Basic Operations

Accessing Items

php
$collection = new Collection([1, 2, 3, 4, 5]);

// Get all items
$all = $collection->all(); // [1, 2, 3, 4, 5]

// Count items
$count = $collection->count(); // 5

// Check if empty
$isEmpty = $collection->isEmpty(); // false

// Get first item
$first = $collection->first(); // 1

// Convert to array
$array = $collection->toArray(); // [1, 2, 3, 4, 5]

Adding Items

php
$collection = new Collection([1, 2, 3]);

// Push single item
$collection->push(4); // [1, 2, 3, 4]

// Push multiple items
$collection->push([5, 6]); // [1, 2, 3, 4, 5, 6]

Filtering and Searching

Filter

Filter the collection by a given callback:

php
$collection = new Collection([1, 2, 3, 4, 5, 6]);

// Filter even numbers
$evens = $collection->filter(function ($item) {
    return $item % 2 == 0;
}); // [2, 4, 6]

// Filter without callback (removes falsy values)
$collection = new Collection([1, null, 2, '', 3, false, 4]);
$filtered = $collection->filter(); // [1, 2, 3, 4]

Where

Filter items by a specific key-value pair:

php
$users = new Collection([
    (object) ['name' => 'John', 'age' => 25],
    (object) ['name' => 'Jane', 'age' => 30],
    (object) ['name' => 'Bob', 'age' => 25],
]);

// Get users aged 25
$youngUsers = $users->where('age', 25);

First Where

Find the first item matching a condition:

php
$users = new Collection([
    (object) ['name' => 'John', 'active' => true],
    (object) ['name' => 'Jane', 'active' => false],
    (object) ['name' => 'Bob', 'active' => true],
]);

$firstActive = $users->firstWhere('active', true); // John

Transformation

Map

Transform each item in the collection:

php
$collection = new Collection([1, 2, 3, 4, 5]);

// Square each number
$squared = $collection->map(function ($item) {
    return $item * $item;
}); // [1, 4, 9, 16, 25]

// Transform user objects
$users = User::all();
$userNames = $users->map(function ($user) {
    return $user->name;
});

Pluck

Extract a specific field from each item:

php
$users = new Collection([
    ['name' => 'John', 'email' => 'john@example.com'],
    ['name' => 'Jane', 'email' => 'jane@example.com'],
    ['name' => 'Bob', 'email' => 'bob@example.com'],
]);

// Pluck names
$names = $users->pluck('name'); // ['John', 'Jane', 'Bob']

// Pluck with keys
$emailMap = $users->pluck('email', 'name');
// ['John' => 'john@example.com', 'Jane' => 'jane@example.com', ...]

Aggregation

Sum

Calculate the sum of numeric values:

php
$collection = new Collection([1, 2, 3, 4, 5]);
$sum = $collection->sum(); // 15

// Sum by key
$orders = new Collection([
    ['amount' => 100],
    ['amount' => 250],
    ['amount' => 75],
]);
$total = $orders->sum('amount'); // 425

// Sum with callback
$products = Product::all();
$totalValue = $products->sum(function ($product) {
    return $product->price * $product->quantity;
});

Slicing and Chunking

Slice

Get a portion of the collection:

php
$collection = new Collection([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

// Get items from index 2, take 3 items
$slice = $collection->slice(2, 3); // [3, 4, 5]

// Get items from index 5 to end
$slice = $collection->slice(5); // [6, 7, 8, 9, 10]

Working with Models

Model Collections

When working with Eloquent models, collections provide additional functionality:

php
$users = User::all(); // Returns Collection of User models

// Convert models to arrays
$userArrays = $users->toArray();

// Get specific field from all models
$userIds = $users->pluck('id');
$userNames = $users->pluck('name', 'id');

// Filter models
$activeUsers = $users->filter(function ($user) {
    return $user->status === 'active';
});

// Transform models
$userSummaries = $users->map(function ($user) {
    return [
        'id' => $user->id,
        'name' => $user->name,
        'email' => $user->email,
        'posts_count' => $user->posts()->count(),
    ];
});

Relationship Collections

Collections work seamlessly with model relationships:

php
$user = User::find(1);
$posts = $user->posts; // Collection of Post models

// Get published posts
$publishedPosts = $posts->filter(function ($post) {
    return $post->status === 'published';
});

// Get post titles
$titles = $posts->pluck('title');

// Sum comments count
$totalComments = $posts->sum('comments_count');

Real-World Examples

E-commerce Cart

php
class ShoppingCart
{
    protected $items;

    public function __construct()
    {
        $this->items = new Collection();
    }

    public function addItem($product, $quantity = 1)
    {
        $existingItem = $this->items->firstWhere('product_id', $product->id);

        if ($existingItem) {
            $existingItem->quantity += $quantity;
        } else {
            $this->items->push((object) [
                'product_id' => $product->id,
                'product' => $product,
                'quantity' => $quantity,
                'price' => $product->price,
            ]);
        }
    }

    public function getTotal()
    {
        return $this->items->sum(function ($item) {
            return $item->quantity * $item->price;
        });
    }

    public function getItemCount()
    {
        return $this->items->sum('quantity');
    }

    public function getItemsByCategory($category)
    {
        return $this->items->filter(function ($item) use ($category) {
            return $item->product->category === $category;
        });
    }
}

Report Generation

php
class SalesReport
{
    public function generateDailySales($date)
    {
        $orders = Order::whereDate('created_at', $date)->get();

        return [
            'total_orders' => $orders->count(),
            'total_revenue' => $orders->sum('total'),
            'average_order' => $orders->count() > 0 ? $orders->sum('total') / $orders->count() : 0,
            'top_products' => $this->getTopProducts($orders),
            'sales_by_hour' => $this->getSalesByHour($orders),
        ];
    }

    private function getTopProducts($orders)
    {
        $items = new Collection();

        $orders->each(function ($order) use ($items) {
            $order->items->each(function ($item) use ($items) {
                $items->push($item);
            });
        });

        return $items->groupBy('product_id')
            ->map(function ($productItems) {
                return [
                    'product_name' => $productItems->first()->product_name,
                    'total_quantity' => $productItems->sum('quantity'),
                    'total_revenue' => $productItems->sum(function ($item) {
                        return $item->quantity * $item->price;
                    }),
                ];
            })
            ->sortByDesc('total_revenue')
            ->take(10);
    }

    private function getSalesByHour($orders)
    {
        return $orders->groupBy(function ($order) {
            return $order->created_at->format('H');
        })->map(function ($hourOrders) {
            return [
                'order_count' => $hourOrders->count(),
                'revenue' => $hourOrders->sum('total'),
            ];
        });
    }
}

User Management

php
class UserManager
{
    public function getUserStatistics()
    {
        $users = User::all();

        return [
            'total_users' => $users->count(),
            'active_users' => $users->where('status', 'active')->count(),
            'users_by_role' => $this->getUsersByRole($users),
            'recent_signups' => $this->getRecentSignups($users),
            'top_contributors' => $this->getTopContributors($users),
        ];
    }

    private function getUsersByRole($users)
    {
        return $users->groupBy('role')
            ->map(function ($roleUsers) {
                return $roleUsers->count();
            });
    }

    private function getRecentSignups($users)
    {
        return $users->filter(function ($user) {
            return $user->created_at->isAfter(now()->subDays(7));
        })->count();
    }

    private function getTopContributors($users)
    {
        return $users->map(function ($user) {
            return [
                'id' => $user->id,
                'name' => $user->name,
                'posts_count' => $user->posts()->count(),
                'comments_count' => $user->comments()->count(),
                'total_contribution' => $user->posts()->count() + $user->comments()->count(),
            ];
        })->sortByDesc('total_contribution')->take(10);
    }
}

WordPress Integration

php
class PostManager
{
    public function getPostAnalytics()
    {
        $posts = Post::all();

        return [
            'total_posts' => $posts->count(),
            'published_posts' => $posts->where('status', 'published')->count(),
            'draft_posts' => $posts->where('status', 'draft')->count(),
            'posts_by_category' => $this->getPostsByCategory($posts),
            'most_commented' => $this->getMostCommentedPosts($posts),
            'recent_posts' => $this->getRecentPosts($posts),
        ];
    }

    private function getPostsByCategory($posts)
    {
        return $posts->groupBy('category_id')
            ->map(function ($categoryPosts) {
                $category = $categoryPosts->first()->category;
                return [
                    'category_name' => $category->name,
                    'post_count' => $categoryPosts->count(),
                ];
            });
    }

    private function getMostCommentedPosts($posts)
    {
        return $posts->sortByDesc('comments_count')
            ->take(5)
            ->map(function ($post) {
                return [
                    'title' => $post->title,
                    'comments_count' => $post->comments_count,
                    'permalink' => get_permalink($post->id),
                ];
            });
    }

    private function getRecentPosts($posts)
    {
        return $posts->filter(function ($post) {
            return $post->created_at->isAfter(now()->subDays(30));
        })->sortByDesc('created_at')->take(10);
    }
}

Custom Collections

You can extend the Collection class to create domain-specific collections:

php
class UserCollection extends Collection
{
    public function admins()
    {
        return $this->filter(function ($user) {
            return $user->role === 'admin';
        });
    }

    public function active()
    {
        return $this->filter(function ($user) {
            return $user->status === 'active';
        });
    }

    public function byRole($role)
    {
        return $this->filter(function ($user) use ($role) {
            return $user->role === $role;
        });
    }

    public function totalPosts()
    {
        return $this->sum('posts_count');
    }

    public function averageAge()
    {
        $totalAge = $this->sum('age');
        return $this->count() > 0 ? $totalAge / $this->count() : 0;
    }
}

Using custom collections:

php
class User extends Model
{
    public function newCollection(array $models = [])
    {
        return new UserCollection($models);
    }
}

// Now User::all() returns UserCollection
$users = User::all();
$admins = $users->admins();
$activeUsers = $users->active();
$totalPosts = $users->totalPosts();

Best Practices

1. Use Method Chaining

php
// Good - readable and fluent
$result = $users
    ->filter(function ($user) { return $user->active; })
    ->map(function ($user) { return $user->name; })
    ->sort()
    ->take(10);

// Avoid - multiple assignments
$filtered = $users->filter(function ($user) { return $user->active; });
$mapped = $filtered->map(function ($user) { return $user->name; });
$sorted = $mapped->sort();
$result = $sorted->take(10);

2. Prefer Collections Over Arrays

php
// Good - use collections for data manipulation
public function getActiveUserNames()
{
    return User::where('active', true)
        ->get()
        ->pluck('name');
}

// Avoid - manual array processing
public function getActiveUserNames()
{
    $users = User::where('active', true)->get();
    $names = [];
    foreach ($users as $user) {
        $names[] = $user->name;
    }
    return $names;
}

3. Use Lazy Loading with Collections

php
// Eager load relationships before collection operations
$users = User::with('posts', 'comments')->get();

$userStats = $users->map(function ($user) {
    return [
        'name' => $user->name,
        'posts_count' => $user->posts->count(), // No N+1 query
        'comments_count' => $user->comments->count(), // No N+1 query
    ];
});

Collections in AvelPress provide a powerful and expressive way to work with data, making your code more readable and maintainable while providing performance benefits over traditional array manipulation.

Released under the MIT License.