# From Arrays to Collections: Building a Laravel-Style Wrapper in PHP

> Author: [Oussama Ghaieb](https://oussama.ghaieb.com/about-me)
> Canonical: https://oussama.ghaieb.com/posts/from-arrays-to-collections-building-a-laravel-style-wrapper-in-php

# <span class="hover:bg-yellow-100">From Arrays to Collections: Building a Laravel-Style Wrapper in PHP</span>

Laravel's Collections are one of the most powerful and expressive features of the framework. They provide a fluent, object-oriented way to work with arrays of data, offering a variety of helpful methods for filtering, transforming, and manipulating data. If you've ever wondered how to create a similar class that converts a PHP array into a Laravel-like collection, this blog post is for you.

In this tutorial, we'll walk through the process of building a simple `Collection` class in PHP that mimics some of the functionality of Laravel's Collections. By the end, you'll have a better understanding of how Laravel's Collections work under the hood and how you can implement similar functionality in your own projects—complete with **method chaining** for a fluent and expressive API.

---

## What Are Laravel Collections?

Laravel Collections are essentially an object-oriented wrapper for arrays. They provide a wide range of methods for performing common operations on arrays, such as `map`, `filter`, `reduce`, `pluck`, and `values`. These methods allow you to write more expressive and readable code when dealing with arrays of data.

For example, instead of writing a `foreach` loop to filter an array, you can use the `filter` method:

```php
$collection = collect([1, 2, 3, 4, 5]);

$filtered = $collection->filter(function ($value, $key) {
    return $value > 2;
});

// Result: [3, 4, 5]
```

Our goal is to create a similar `Collection` class that can wrap a PHP array and provide some of these useful methods, with support for **method chaining** to make the API more expressive.

---

## Building the Collection Class

Let's start by creating a basic `Collection` class. This class will accept an array as input and provide methods to manipulate the data. We'll also ensure that the class supports method chaining by returning `$this` or a new `Collection` instance from each method.

### Step 1: Create the Class

First, create a new PHP file, e.g., `Collection.php`, and define the class:

```php
class Collection
{
    protected $items = [];

    public function __construct(array $items = [])
    {
        $this->items = $items;
    }

    public static function make(array $items = [])
    {
        return new static($items);
    }

    public function all()
    {
        return $this->items;
    }
}
```

Here's what's happening:
- The `__construct` method initializes the class with an array of items.
- The `make` method is a static factory method that allows us to create a new `Collection` instance more fluently.
- The `all` method returns the underlying array.

---

### Step 2: Add the `map` Method

The `map` method applies a callback to each item in the array and returns a new `Collection` with the results.

```php
public function map(callable $callback)
{
    $mapped = [];

    foreach ($this->items as $key => $item) {
        $mapped[$key] = $callback($item, $key);
    }

    return new static($mapped);
}
```

Example usage:

```php
$collection = Collection::make([1, 2, 3]);

$mapped = $collection->map(function ($item) {
    return $item * 2;
});

// Result: [2, 4, 6]
```

---

### Step 3: Add the `filter` Method

The `filter` method removes items from the array that don't pass a given truth test.

```php
public function filter(callable $callback)
{
    $filtered = [];

    foreach ($this->items as $key => $item) {
        if ($callback($item, $key)) {
            $filtered[$key] = $item;
        }
    }

    // Reindex the array keys
    $filtered = array_values($filtered);

    return new static($filtered);
}
```

Example usage:

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

$filtered = $collection->filter(function ($item) {
    return $item > 2;
});

// Result: [3, 4, 5]
```

---

### Step 4: Add the `reduce` Method

The `reduce` method reduces the array to a single value by applying a callback function.

```php
public function reduce(callable $callback, $initial = null)
{
    $result = $initial;

    foreach ($this->items as $key => $item) {
        $result = $callback($result, $item, $key);
    }

    return $result;
}
```

Example usage:

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

$sum = $collection->reduce(function ($carry, $item) {
    return $carry + $item;
}, 0);

// Result: 15
```

---

### Step 5: Add the `pluck` Method

The `pluck` method extracts a list of values for a given key from an array of arrays or objects.

```php
public function pluck($key)
{
    $plucked = [];

    foreach ($this->items as $item) {
        if (is_array($item) && isset($item[$key])) {
            $plucked[] = $item[$key];
        } elseif (is_object($item) && isset($item->$key)) {
            $plucked[] = $item->$key;
        }
    }

    return new static($plucked);
}
```

Example usage:

```php
$collection = Collection::make([
    ['name' => 'John', 'age' => 30],
    ['name' => 'Jane', 'age' => 25],
]);

$names = $collection->pluck('name');

// Result: ['John', 'Jane']
```

---

### Step 6: Add the `each` Method

The `each` method allows you to perform an action on each item without modifying the collection.

```php
public function each(callable $callback)
{
    foreach ($this->items as $key => $item) {
        $callback($item, $key);
    }

    return $this; // Return $this for chaining
}
```

Example usage:

```php
$collection->each(function ($item) {
    echo $item['name'] . "\n";
});
```

---

### Step 7: Add the `sort` Method

The `sort` method sorts the collection by a given callback or by natural order.

```php
public function sort(callable $callback = null)
{
    $items = $this->items;

    if ($callback) {
        uasort($items, $callback);
    } else {
        asort($items);
    }

    return new static($items);
}
```

Example usage:

```php
$sorted = $collection->sort(function ($a, $b) {
    return $a['age'] <=> $b['age'];
});
```

---

### Step 8: Add the `values()` Method

The `values()` method reindexes the array numerically, removing any existing keys and resetting them to start from zero.

```php
public function values()
{
    // Use array_values to reindex the array numerically
    $reindexed = array_values($this->items);

    return new static($reindexed);
}
```

Example usage:

```php
$collection = Collection::make([
    10 => 'John',
    20 => 'Jane',
    30 => 'Doe',
]);

$reindexed = $collection->values();

print_r($reindexed->all());
```

---

### Output

The output will be a reindexed array:

```php
Array
(
    [0] => John
    [1] => Jane
    [2] => Doe
)
```

---

## Putting It All Together

Here’s the complete `Collection` class with all the methods we've implemented, including support for method chaining:

```php
class Collection
{
    protected $items = [];

    public function __construct(array $items = [])
    {
        $this->items = $items;
    }

    public static function make(array $items = [])
    {
        return new static($items);
    }

    public function all()
    {
        return $this->items;
    }

    public function map(callable $callback)
    {
        $mapped = [];

        foreach ($this->items as $key => $item) {
            $mapped[$key] = $callback($item, $key);
        }

        return new static($mapped);
    }

    public function filter(callable $callback)
    {
        $filtered = [];

        foreach ($this->items as $key => $item) {
            if ($callback($item, $key)) {
                $filtered[$key] = $item;
            }
        }

        // Reindex the array keys
        $filtered = array_values($filtered);

        return new static($filtered);
    }

    public function reduce(callable $callback, $initial = null)
    {
        $result = $initial;

        foreach ($this->items as $key => $item) {
            $result = $callback($result, $item, $key);
        }

        return $result;
    }

    public function pluck($key)
    {
        $plucked = [];

        foreach ($this->items as $item) {
            if (is_array($item) && isset($item[$key])) {
                $plucked[] = $item[$key];
            } elseif (is_object($item) && isset($item->$key)) {
                $plucked[] = $item->$key;
            }
        }

        return new static($plucked);
    }

    public function each(callable $callback)
    {
        foreach ($this->items as $key => $item) {
            $callback($item, $key);
        }

        return $this; // Return $this for chaining
    }

    public function sort(callable $callback = null)
    {
        $items = $this->items;

        if ($callback) {
            uasort($items, $callback);
        } else {
            asort($items);
        }

        return new static($items);
    }

    public function values()
    {
        // Use array_values to reindex the array numerically
        $reindexed = array_values($this->items);

        return new static($reindexed);
    }
}
```

---

## Example Usage with Method Chaining

Here’s how you can use the updated `Collection` class with method chaining:

```php
$collection = Collection::make([
    10 => ['name' => 'John', 'age' => 30],
    20 => ['name' => 'Jane', 'age' => 25],
    30 => ['name' => 'Doe', 'age' => 35],
]);

$result = $collection
    ->filter(function ($item) {
        return $item['age'] > 25; // Filter out ages <= 25
    })
    ->map(function ($item) {
        return [
            'name' => strtoupper($item['name']), // Transform names to uppercase
            'age' => $item['age'],
        ];
    })
    ->values(); // Reindex the array numerically

print_r($result->all());
```

---

### Output

The output will be a filtered, transformed, and reindexed array:

```php
Array
(
    [0] => Array
        (
            [name] => JOHN
            [age] => 30
        )

    [1] => Array
        (
            [name] => DOE
            [age] => 35
        )
)
```

---

## Conclusion

In this blog post, we've built a simple `Collection` class that mimics some of the functionality of Laravel's Collections, complete with **method chaining** for a fluent and expressive API. By enabling method chaining, we've made the class much more powerful and user-friendly.

You can extend this class further by adding more methods like `groupBy`, `keys`, `first`, `last`, and more. The possibilities are endless, and you can tailor the class to suit your specific needs.

By understanding how these methods work, you'll not only gain a deeper appreciation for Laravel's Collections but also improve your ability to write clean, expressive, and efficient PHP code.

Happy coding! 🚀
