Chapter 17. Helpers and Collections

We’ve already covered many global functions throughout the book: these are little helpers that make it easier to perform common tasks, like dispatch() for jobs, event() for events, and app() for dependency resolution. We also talked a bit about Laravel’s collections, or arrays on steroids, in Chapter 5.

In this chapter we’ll cover some of the more common and powerful helpers and some of the basics of programming with collections.

Helpers

You can find a full list of the helpers Laravel offers in the docs, but we’re going to cover a few of the most useful functions here.

Warning

Laravel 5.8 deprecated all global helpers that start with array_ or str_. The helpers were removed in Laravel 6.0 but were made available in a laravel/helpers package for backward compatibility. Each of these helpers is backed by a method on the Arr or Str classes.

Arrays

PHP’s native array manipulation functions give us a lot of power, but sometimes there are standard manipulations we want to make that require unwieldy loops and logic checks. Laravel’s array helpers make a few common array manipulations much simpler:

Arr::first($array, $callback, $default = null)

Returns the first array value that passes a test, defined in a callback closure. You can optionally set the default value as the third parameter. Here’s an example:

    $people = [
        [
            'email' => '[email protected]',
            'name' => 'Malcolm Me'
        ],
        [
            'email' => '[email protected]',
            'name' => 'James Jo'
        ],
    ];

    $value = Arr::first($people, function ($person, $key) {
        return $person['email'] == '[email protected]';
    });
Arr::get($array, $key, $default = null)

Makes it easy to get values out of an array, with two added benefits: it won’t throw an error if you ask for a key that doesn’t exist (and you can provide defaults with the third parameter), and you can use dot notation to traverse nested arrays. For example:

    $array = ['owner' => ['address' => ['line1' => '123 Main St.']]];

    $line1 = Arr::get($array, 'owner.address.line1', 'No address');
    $line2 = Arr::get($array, 'owner.address.line2');
Arr::has($array, $keys)

Makes it easy to check whether an array has a particular value set using dot notation for traversing nested arrays. The $keys parameter can be a single entry or an array of entries, which will check whether every entry in the array exists:

    $array = ['owner' => ['address' => ['line1' => '123 Main St.']]];

    if (Arr::has($array, 'owner.address.line2')) {
        // Do stuff
    }
Arr::pluck($array, $value, $key = null)

Returns an array of the values corresponding to the provided key:

    $array = [
        ['owner' => ['id' => 4, 'name' => 'Tricia']],
        ['owner' => ['id' => 7, 'name' => 'Kimberly']],
    ];

    $array = Arr::pluck($array, 'owner.name');

    // Returns ['Tricia', 'Kimberly'];

If you want the returned array to be keyed by another value from the source array, you can pass that value’s dot-notated reference as the third parameter:

    $array = Arr::pluck($array, 'owner.name', 'owner.id');

    // Returns [4 => 'Tricia', 7 => 'Kimberly'];
Arr::random($array, $num = null)

Returns a random item from the provided array. If you provide a $num parameter, it will pull an array of that many results, randomly selected:

    $array = [
        ['owner' => ['id' => 4, 'name' => 'Tricia']],
        ['owner' => ['id' => 7, 'name' => 'Kimberly']],
    ];

    $randomOwner = Arr::random($array);

Strings

Just like with arrays, there are some string manipulations and checks that are possible with native PHP functions, but can be cumbersome. Laravel’s helpers make a few common string operations faster and simpler:

e($string)

An alias to htmlentities(); prepares a (often user-provided) string for safe echoing on an HTML page. For example:

    e('<script>do something nefarious</script>');

    // Returns &lt;script&gt;do something nefarious&lt;/script&gt;
Str::startsWith($haystack, $needle), Str::endsWith($haystack, $needle), and Str::contains($haystack, $needle)

Return a Boolean indicating whether the provided $haystack string starts with, ends with, or contains the provided $needle string:

    if (Str::startsWith($url, 'https')) {
        // Do something
    }

    if (Str::endsWith($abstract, '...')) {
        // Do something
    }

    if (Str::contains($description, '1337 h4x0r')) {
        // Run away
    }
Str::limit($value, $limit = 100, $end = '...')

Limits a string to the provided number of characters. If the string’s length is less than the limit, just returns the string; if it’s greater, trims to the number of characters provided and then appends either ... or the provided $end string. For example:

    $abstract = Str::limit($loremIpsum, 30);

    // Returns "Lorem ipsum dolor sit amet, co..."

    $abstract = Str::limit($loremIpsum, 30, "&hellip;");

    // Returns "Lorem ipsum dolor sit amet, co&hellip;"
Str::is($pattern, $value)

Returns a Boolean indicating whether or not a given string matches a given pattern. The pattern can be a regex pattern, or you can use asterisks to indicate wildcard positions:

    Str::is('*.dev', 'myapp.dev');       // true
    Str::is('*.dev', 'myapp.dev.co.uk'); // false
    Str::is('*dev*', 'myapp.dev');       // true
    Str::is('*myapp*', 'www.myapp.dev'); // true
    Str::is('my*app', 'myfantasticapp'); // true
    Str::is('my*app', 'myapp');          // true

How to Pass a Regex to Str::is()

If you’re curious about what regex patterns are acceptable to pass to Str::is(), check out the method definition here (shortened for space) to see how it works.:

public function is($pattern, $value)
{
    if ($pattern == $value) return true;

    $pattern = preg_quote($pattern, '#');
    $pattern = Str::replace('*', '.*', $pattern);
    if (preg_match('#^'.$pattern.'z#u', $value) === 1) {
        return true;
    }

    return false;
}
Str::random($length = n)

Returns a random string of alphanumeric mixed-case characters of the length specified:

    $hash = Str::random(64);

    // Sample: J40uNWAvY60wE4BPEWxu7BZFQEmxEHmGiLmQncj0ThMGJK7O5Kfgptyb9ulwspmh
Str::slug($title, $separator = '-', $language = 'en')

Returns a URL-friendly slug from a string—often used for creating a URL segment for a name or title:

    Str::slug('How to Win Friends and Influence People');

    // Returns 'how-to-win-friends-and-influence-people'
Str::plural($value, $count = n)

Converts a string to its plural form. This function currently only supports the English language:

    Str::plural('book');

    // Returns books

    Str::plural('person');

    // Returns people

    Str::plural('person', 1);

    // Returns person
__($key, $replace = [], $locale = null)

Translates the given translation string or translation key using your localization files:

    echo __('Welcome to your dashboard');

    echo __('messages.welcome');

Application Paths

When you’re dealing with the filesystem, it can often be tedious to make links to certain directories for getting and saving files. These helpers give you quick access to find the fully qualified paths to some of the most important directories in your app.

Note that each of these can be called with no parameters, but if a parameter is passed, it will be appended to the normal directory string and returned as a whole:

app_path($append = '')

Returns the path for the app directory:

    app_path();

    // Returns /home/forge/myapp.com/app
base_path($path = '')

Returns the path for the root directory of your app:

    base_path();

    // Returns /home/forge/myapp.com
config_path($path = '')

Returns the path for configuration files in your app:

    config_path();

    // Returns /home/forge/myapp.com/config
database_path($path = '')

Returns the path for database files in your app:

    database_path();

    // Returns /home/forge/myapp.com/database
storage_path($path = '')

Returns the path for the storage directory in your app:

    storage_path();

    // Returns /home/forge/myapp.com/storage

URLs

Some frontend file paths are consistent but at times annoying to type—for example, paths to assets—and it’s helpful to have convenient shortcuts to them, which we’ll cover here. But some can actually vary as route definitions move or new files are versioned with Mix, so some of these helpers are vital in making sure all of your links and assets work correctly:

action($action, $parameters = [], $absolute = true)

Assuming a controller method has a single URL mapped to it, returns the correct URL given a controller and method name pair (separated by @) or using tuple notation:

    <a href="{{ action('PersonController@index') }}">See all People</a>
    // Or, using tuple notation:
    <a href=
        "{{ action([AppHttpControllersPersonController::class, 'index']) }}">
        See all People
    </a>

    // Returns <a href="http://myapp.com/people">See all People</a>

If the controller method requires parameters, you can pass them in as the second parameter (as an array, if there’s more than one required parameter). You can key them if you want for clarity, but what matters is just that they’re in the right order:

    <a href="{{ action('PersonController@show', ['id' => 3] }}">See Person #3</a>
    // or
    <a href="{{ action('PersonController@show', [3] }}">See Person #3</a>

    // Returns <a href="http://myapp.com/people/3">See Person #3</a>

If you pass false to the third parameter, your links will generate as relative (/people/3) instead of absolute (http://myapp.com/people/3).

route($name, $parameters = [], $absolute = true)

If a route has a name, returns the URL for that route:

    // routes/web.php
    Route::get('people', 'PersonController@index')->name('people.index');

    // A view somewhere
    <a href="{{ route('people.index') }}">See all People</a>

    // Returns <a href="http://myapp.com/people">See all People</a>

If the route definition requires parameters, you can pass them in as the second parameter (as an array if more than one parameter is required). Again, you can key them if you want for clarity, but what matters is just that they’re in the right order:

    <a href="{{ route('people.show', ['id' => 3]) }}">See Person #3</a>
    // or
    <a href="{{ route('people.show', [3]) }}">See Person #3</a>

    // Returns <a href="http://myapp.com/people/3">See Person #3</a>

If you pass false to the third parameter, your links will generate as relative instead of absolute.

url($string) and secure_url($string)

Given any path string, converts to a fully qualified URL. (secure_url() is the same as url() but forces HTTPS):

    url('people/3');

    // Returns http://myapp.com/people/3

If no parameters are passed, this instead gives an instance of IlluminateRoutingUrlGenerator, which makes method chaining possible:

    url()->current();
    // Returns http://myapp.com/abc

    url()->full();
    // Returns http://myapp.com/abc?order=reverse

    url()->previous();
    // Returns http://myapp.com/login

    // And many more methods available on the UrlGenerator...
mix($path, $manifestDirectory = '')

If assets are versioned with Elixir’s versioning system, given the nonversioned path name, returns the fully qualified URL for the versioned file:

    <link rel="stylesheet" href="{{ mix('css/app.css') }}">

    // Returns something like /build/css/app-eb555e38.css

Using the elixir() Helper Prior to Laravel 5.4

In projects running versions of Laravel prior to 5.4, you’ll want to use the elixir() helper instead of the mix() helper. Check the docs for more info.

Miscellaneous

There are a few other global helpers that I’d recommend getting familiar with. Of course, you should check out the whole list, but the ones mentioned here are definitely worth taking a look at:

abort($code, $message, $headers), abort_unless($boolean, $code, $message, $headers), and abort_if($boolean, $code, $message, $headers)

Throw HTTP exceptions. abort() throws the exception defined, abort_unless() throws it if the first parameter is false, and abort_if() throws it if the first parameter is true:

    public function controllerMethod(Request $request)
    {
        abort(403, 'You shall not pass');
        abort_unless(request()->filled('magicToken'), 403);
        abort_if(request()->user()->isBanned, 403);
    }
auth()

Returns an instance of the Laravel authenticator. Like the Auth facade, you can use this to get the current user, to check for login state, and more:

    $user = auth()->user();
    $userId = auth()->id();

    if (auth()->check()) {
        // Do something
    }
back()

Generates a “redirect back” response, sending the user to the previous location:

    Route::get('post', function () {
        ...

        if ($condition) {
            return back();
     }
    });
collect($array)

Takes an array and returns the same data, converted to a collection:

    $collection = collect(['Rachel', 'Hototo']);

We’ll cover collections in just a bit.

config($key)

Returns the value for any dot-notated configuration item:

    $defaultDbConnection = config('database.default');
csrf_field() and csrf_token()

Return a full HTML hidden input field (csrf_field()) or just the appropriate token value (csrf_token()) for adding CSRF verification to your form submission:

    <form>
        {{ csrf_field() }}
    </form>

    // or

    <form>
        <input type="hidden" name="_token" value="{{ csrf_token() }}">
    </form>
dd($variable...)

Short for “dump and die,” runs var_dump() on all provided parameters and then exit() to quit the application (this is used for debugging):

    ...
    dd($var1, $var2, $state); // Why is this not working???
env($key, $default = null)

Returns the environment variable for the given key:

    $key = env('API_KEY', '');
Remember not to ever use +env()+ outside of config files.
dispatch($job)

Dispatches a job:

    dispatch(new EmailAdminAboutNewUser($user));
event($event)

Fires an event:

    event(new ContactAdded($contact));
factory($entityClass)

Returns an instance of the factory builder for the given class:

    $contact = factory(AppContact::class)->make();
old($key = null, $default = null)

Returns the old value (from the last user form submission) for this form key, if it exists:

    <input name="name" value="{{ old('value', 'Your name here') }}"
redirect($path)

Returns a redirect response to the given path:

    Route::get('post', function () {
        ...

        return redirect('home');
    });

Without parameters, this generates an instance of the IlluminateRoutingRedirector class.

response($content, $status = 200, $headers)

If passed with parameters, returns a prebuilt instance of Response. If passed with no response, it returns an instance of the Response factory:

    return response('OK', 200, ['X-Header-Greatness' => 'Super great']);

    return response()->json(['status' => 'success']);
tap($value, $callback = null)

Calls the closure (the second argument), passing it the first argument, and then returns the first argument (instead of the output of the closure).

return tap(Contact::first(), function ($contact) {
    $contact->name = 'Aheahe';
    $contact->save();
});
view($viewPath)

Returns a view instance:

    Route::get('home', function () {
        return view('home'); // Gets /resources/views/home.blade.php
    });

Collections

Collections are one of the most powerful yet underappreciated tools Laravel provides. We covered them a bit in “Eloquent Collections”, but here’s a quick recap.

Collections are essentially arrays with superpowers. The array-traversing methods you normally have to pass arrays into (array_walk(), array_map(), array_reduce(), etc.), all of which have confusingly inconsistent method signatures, are available as consistent, clean, chainable methods on every collection. You can get a taste of functional programming and map, reduce, and filter your way to cleaner code.

We’ll cover some of the basics of Laravel’s collections and collection pipeline programming here, but for a much deeper overview, check out Adam Wathan’s book Refactoring to Collections (Gumroad).

The Basics

Collections are not a new idea within Laravel. Many languages make collection-style programming available on arrays out of the box, but with PHP we’re not quite so lucky.

Using PHP’s array*() functions, we can take the monstrosity shown in Example 17-1 and turn it into the slightly less monstrous monstrosity shown in Example 17-2.

Example 17-1. A common, but ugly, foreach loop
$users = [...];

$admins = [];

foreach ($users as $user) {
    if ($user['status'] == 'admin') {
        $user['name'] = $user['first'] . ' ' . $user['last'];
        $admins[] = $user;
    }
}

return $admins;
Example 17-2. Refactoring the foreach loop with native PHP functions
$users = [...];

return array_map(function ($user) {
    $user['name'] = $user['first'] . ' ' . $user['last'];
    return $user;
}, array_filter($users, function ($user) {
    return $user['status'] == 'admin';
}));

Here, we’ve gotten rid of a temporary variable ($admins) and converted one confusing foreach loop into two distinct actions: map and filter.

The problem is, PHP’s array manipulation functions are awful and confusing. Just look at this example; array_map() takes the closure first and the array second, but array_filter() takes the array first and the closure second. In addition, if we added any complexity to this, we’d have functions wrapping functions wrapping functions. It’s a mess.

Laravel’s collections take the power of PHP’s array manipulation methods and give them a clean, fluent syntax—and they add many methods that don’t even exist in PHP’s array manipulation toolbox. Using the collect() helper method that turns an array into a Laravel collection, we can do what’s shown in Example 17-3.

Example 17-3. Refactoring the foreach loop with Laravel’s collections
$users = collect([...]);

return $users->filter(function ($user) {
    return $user['status'] == 'admin';
})->map(function ($user) {
    $user['name'] = $user['first'] . ' ' . $user['last'];
    return $user;
});

This isn’t the most extreme of examples. There are plenty where the reduction in lines of code and the increased simplicity would make an even stronger case. But this right here is so common.

Look at the original example and how muddy it is. It’s not entirely clear until you understand the entire code sample what any given piece is there for.

The biggest benefit collections provide, over anything else, is breaking the actions you’re taking to manipulate an array into simple, discrete, understandable tasks. You can now do something like this:

$users = [...]
$countAdmins = collect($users)->filter(function ($user) {
    return $user['status'] == 'admin';
})->count();

or something like this:

$users = [...];
$greenTeamPoints = collect($users)->filter(function ($user) {
    return $user['team'] == 'green';
})->sum('points');

Many of the examples we’ll look at in the rest of this chapter operate on this mythical $users collection we’ve started imagining here. Each entry in the $users array will represent a single human; they’ll likely all be array-accessible. The specific properties each user will have may vary a bit depending on the example. But any time you see this $users variable, know that that’s what we’re working with.

A Few Methods

There’s much more you can do than what we’ve covered so far. I recommend you take a look at the Laravel collections to learn more about all the methods you can use, but to get you started, here are just a few of the core methods:

all() and toArray()

If you’d like to convert your collection to an array, you can do so with either all() or toArray(). toArray() flattens to arrays not just the collection, but also any Eloquent objects underneath it. all() only converts the collection to an array; any Eloquent objects contained within the collection will be preserved as Eloquent objects. Here are a few examples:

$users = User::all();

$users->toArray();

/* Returns
    [
        ['id' => '1', 'name' => 'Agouhanna'],
        ...
    ]
*/

$users->all();

/* Returns
    [
        Eloquent object { id : 1, name: 'Agouhanna' },
        ...
    ]
*/
filter() and reject()

When you want to get a subset of your original collection by checking each item against a closure, you’ll use filter() (which keeps an item if the closure returns true) or reject() (which keeps an item if the closure returns false):

$users = collect([...]);
$admins = $users->filter(function ($user) {
    return $user->isAdmin;
});

$paidUsers = $user->reject(function ($user) {
    return $user->isTrial;
});
where()

where() makes it easy to provide a subset of your original collection where a given key is equal to a given value. Anything you can do with where() you can also do with filter(), but it’s a shortcut for a common scenario:

$users = collect([...]);
$admins = $users->where('role', 'admin');
first() and last()

If you want just a single item from your collection, you can use first() to pull from the beginning of the list or last() to pull from the end.

If you call first() or last() with no parameters, they’ll just give you the first or last item in the collection, respectively. But if you pass either a closure, they’ll instead give you the first or last item in the collection that returns true when passed to that closure.

Sometimes you’ll do this because you want the actual first or last item. But sometimes it’s the easiest way to get one item even if you only expect there to be one:

$users = collect([...]);
$owner = $users->first(function ($user) {
    return $user->isOwner;
});

$firstUser = $users->first();
$lastUser = $users->last();

You can also pass a second parameter to each method, which is the default value and will be provided as a fallback if the closure doesn’t provide any results.

each()

If you’d like to do something with each item of a collection, but it doesn’t include modifying the items or the collection itself, you can use each():

$users = collect([...]);
$users->each(function ($user) {
    EmailUserAThing::dispatch($user);
});
map()

If you’d like to iterate over all the items in a collection, make changes to them, and return a new collection with all of your changes, you’ll want to use map():

$users = collect([...]);
$users = $users->map(function ($user) {
    return [
        'name' => $user['first'] . ' ' . $user['last'],
        'email' => $user['email'],
    ];
});
reduce()

If you’d like to get a single result from your collection, like a count or a string, you’ll probably want to use reduce(). This method works by taking an initial value (called the “carry”) and then allowing each item in the collection to change that value somehow. You can define an initial value for the carry, and a closure that accepts the current state of the carry and then each item as parameters:

$users = collect([...]);

$points = $users->reduce(function ($carry, $user) {
    return $carry + $user['points'];
}, 0); // Start with a carry of 0
pluck()

If you want to pull out just the values for a given key under each item in a collection, you can use pluck() ((lists(), in Laravel 5.1 and earlier):

$users = collect([...]);

$emails = $users->pluck('email')->toArray();
chunk() and take()

chunk() makes it easy to split your collection into groups of a predefined size, and take() pulls just the provided number of items:

$users = collect([...]);

$rowsOfUsers = $users->chunk(3); // Separates into groups of 3

$topThree = $users->take(3); // Pulls the first 3
groupBy()

If you want to group all of the items in your collection by the value of one of their properties, you can use groupBy():

$users = collect([...]);

$usersByRole = $users->groupBy('role');

/* Returns:
    [
        'member' => [...],
        'admin' => [...],
    ]
*/

You can also pass a closure, and whatever you return from the closure will be what’s used to group the records:

$heroes = collect([...]);

$heroesByAbilityType = $heroes->groupBy(function ($hero) {
    if ($hero->canFly() && $hero->isInvulnerable()) {
        return 'Kryptonian';
    }

    if ($hero->bitByARadioactiveSpider()) {
        return 'Spidermanesque';
    }

    if ($hero->color === 'green' && $hero->likesSmashing()) {
        return 'Hulk-like';
    }

    return 'Generic';
});
reverse() and shuffle()

reverse() reverses the order of the items in your collection, and shuffle() randomizes them:

$numbers = collect([1, 2, 3]);

$numbers->reverse()->toArray(); // [3, 2, 1]
$numbers->shuffle()->toArray(); // [2, 3, 1]
sort(), sortBy(), and sortByDesc()

If your items are simple strings or integers, you can use sort() to sort them:

$sortedNumbers = collect([1, 7, 6])->sort()->toArray(); // [1, 6, 7]

If they’re more complex, you can pass a string (representing the property) or a closure to sortBy() or sortByDesc() to define your sorting behavior:

$users = collect([...]);

// Sort an array of users by their 'email' property
$users->sort('email');

// Sort an array of users by their 'email' property
$users->sortBy(function ($user, $key) {
    return $user['email'];
});
countBy()

countBy counts every occurence of each value in a collection:

$collection = collect([10, 10, 20, 20, 20, 30]);

$collection->countBy()->all();

// [10 => 2, 20 => 3, 30 => 1]

Each key in the resulting collection is one of the original values; its paired value is the number of times that value occurred in the original collection.

The countBy method also accepts a callback, which customizes the value that’s used to count each item in the collection:

$collection = collect(['laravel.com', 'tighten.co']);

$collection->countBy(function ($address) {
    return Str::after($address, '.');
})->all();

// all: ["com" => 1, "co" => 1]
count(), isEmpty(), and isNotEmpty()

You can see how many items there are in your collection using count(), isEmpty(), or isNotEmpty():

$numbers = collect([1, 2, 3]);

$numbers->count();   // 3
$numbers->isEmpty(); // false
$numbers->isNotEmpty() // true
avg() and sum()

If you’re working with a collection of numbers, avg() and sum() do what their method names say and don’t require any parameters:

collect([1, 2, 3])->sum(); // 6
collect([1, 2, 3])->avg(); // 2

But if you’re working with arrays, you can pass the key of the property you’d like to pull from each array to operate on:

$users = collect([...]);

$sumPoints = $users->sum('points');
$avgPoints = $users->avg('points');
join

join() joins the collection values into a single output string, joining each with a provided string—like PHP’s join method. You can also (optionally) customize the final concatenation operator:

$collection = collect(['a', 'b', 'c', 'd', 'e']);
$collection->join(', ', ', and ');

// 'a, b, c, d, and e'

Using Collections Outside of Laravel

Have you fallen in love with collections, and do you want to use them on your non-Laravel projects? With Taylor’s blessing, I split out just the collections functionality from Laravel into a separate project called Collect, and developers at my company keep it up to date with Laravel’s releases.

Just use the composer require tightenco/collect command and you’ll have the IlluminateSupportCollection class ready to use in your code—along with the collect() helper.

Lazy Collections

Laravel 6 brought a new LazyCollection class. Lazy Collections leverage the power of PHP Generators to process very large datasets while keeping the memory usage of your app very low.

Imagine needing to iterate over 100,000 contacts in your database. When using Collections in Laravel you would probably run into memory issues very easily as all 100,000 records would be loaded into memory.

$verifiedContacts = AppContact::all()->filter(function ($contact) {
    return $contact->isVerified();
});

Laravel 6.0 makes it very easy to use the power of Lazy Collections with your Eloquent models. The cursor method has been updated to return an instance of LazyCollection instead of the default Collection class. By using Lazy Collections you only need to load 1 record at a time into memory.

$verifiedContacts = AppContact::cursor()->filter(function ($contact) {
    return $contact->isVerified();
});

TL;DR

Laravel provides a suite of global helper functions that make it simpler to do all sorts of tasks. They make it easier to manipulate and inspect arrays and strings, they facilitate generating paths and URLs, and they provide simple access to some consistent and vital functionality.

Laravel’s collections are powerful tools that bring the possibility of collection pipelines to PHP.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.63.136