Chapter 6. Frontend Components

Laravel is primarily a PHP framework, but it also has a series of components focused on generating frontend code. Some of these, like pagination and message bags, are PHP helpers that target the frontend, but Laravel also provides a Webpack-based build system called Mix and some conventions around non-PHP assets.

Laravel’s Build Tools Before and After Laravel 5.4

Prior to Laravel 5.4, Laravel’s frontend build tool was named Elixer, and it was based on Gulp. In 5.4 and later, the new build tool is named Mix, and it’s based on Webpack.

Since Mix is at the core of the non-PHP frontend components, let’s start there.

Laravel Mix

Mix is a build tool that provides a simple user interface and a series of conventions on top of Webpack. Mix’s core value proposition is simplifying the most common build and compilation Webpack tasks by means of a cleaner API and a series of naming and application structure conventions.

At its core, Mix is just a tool in your Webpack toolbox. The “Mix file” you’ll use to set your configurations is simply a Webpack configuration file which lives at the root of your project, named webpack.mix.js. However, the configuration you have to set there is a lot simpler than most Webpack configuration is out of the box, and you’ll have to do a lot less work to get most common asset compilation tasks working.

Let’s look at a common example: running Sass to preprocess your CSS styles. In a normal Webpack environment, that might look a little bit like Example 6-1.

Example 6-1. Compiling a Sass file in Webpack, before Mix
var path = require('path');
var MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
    entry: './src/sass/app.scss',
    module: {
        rules: [
            {
                test: /.s[ac]ss$/,
                use: [
                    MiniCssExtractPlugin.loader,
                    "css-loader",
                    "sass-loader"
                ]
            }
        ]
    },
    plugins: [
        new MiniCssExtractPlugin({
            path: path.resolve(__dirname, './dist'),
            filename: 'app.css'
        })
    ]
}

Now, I’ve seen worse. There aren’t an unimaginable number of configuration properties, and it’s relatively clear what’s going on. But this is the sort of code that you copy from project to project, not code you feel comfortable writing yourself or even modifying to any significant degree. Working like this can get confusing and repetitive.

Let’s try that same task in Mix (Example 6-2).

Example 6-2. Compiling a Sass file in Mix
let mix = require('laravel-mix');

mix.sass('resources/sass/app.scss', 'public/css');

That’s it. And not only is it infinitely simpler, it also covers file watching, browser syncing, notifications, prescribed folder structures, autoprefixing, URL processing, and much more.

Mix Folder Structure

Much of Mix’s simplicity comes from the assumed directory structure. Why decide for every new application where the source and compiled assets will live? Just stick with Mix’s conventions, and you won’t have to think about it ever again.

Every new Laravel app comes with a resources folder, which is where Mix will expect your frontend assets to live. Your Sass will live in resources/sass, or your Less in resources/less, or your source CSS in resources/css, and your JavaScript will live in resources/js. These will export to public/css and public/js.

The Assets Subdirectory Prior to Laravel 5.7

In versions of Laravel prior to 5.7, the sass, less, and js directories were nested under the resources/assets directory instead of directly underneath the resources directory.

Running Mix

Since Mix runs on Webpack, you’ll need to set up a few tools before using it:

  1. First, you’ll need Node.js installed. Visit the Node website to learn how to get it running.

    Once Node (and NPM with it) is installed once, you will not have to do this again for each project. Now you’re ready to install this project’s dependencies.

  2. Open the project root in your terminal, and run npm install to install the required packages (Laravel ships with a Mix-ready package.json file to direct NPM).

You’re now set up! You can run npm run dev to run Webpack/Mix once, npm run watch to listen for relevant file changes and run in response, or npm run prod to run Mix once with production settings (such as minifying the output). You can also run npm run watch-poll if npm run watch doesn’t work in your environment, or npm run hot for Hot Module Replacement (HMR; discussed in the next section).

What Does Mix Provide?

I’ve already mentioned that Mix can preprocess your CSS using Sass, Less, and/or PostCSS. It can also concatenate any sort of files, minify them, rename them, and copy them, and it can copy entire directories or individual files.

Additionally, Mix can process all flavors of modern JavaScript and provide autoprefixing, concatenation, and minification specifically as a part of the JavaScript build stack. It makes it easy to set up Browsersync, HMR, and versioning, and there are plug-ins available for many other common build scenarios.

The Mix documentation covers all of these options and more, but we’ll discuss a few specific use cases in the following sections.

Source maps

If you’re not familiar with source maps, they work with any sort of preprocessor to teach your browser’s web inspector which files generated the compiled source you’re inspecting.

By default, Mix will not generate source maps for your files. But you can enable them by chaining the sourceMaps() method after your Mix calls, as you can see in Example 6-3.

Example 6-3. Enabling source maps in Mix
let mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js')
   .sourceMaps();

Once you configure Mix this way, you’ll see the source maps appear as a .{filename}.map file next to each generated file.

Without source maps, if you use your browser’s development tools to inspect a particular CSS rule or JavaScript action, you’ll just see a big mess of compiled code. With source maps, your browser can pinpoint the exact line of the source file, whether it be Sass or JavaScript or whatever else, that generated the rule you’re inspecting.

Pre- and post-processors

We’ve already covered Sass and Less, but Mix can also handle Stylus (Example 6-4), and you can chain PostCSS onto any other style calls (Example 6-5).

Example 6-4. Preprocessing CSS with Stylus
mix.stylus('resources/stylus/app.styl', 'public/css');
Example 6-5. Post-processing CSS with PostCSS
mix.sass('resources/sass/app.scss', 'public/css')
   .options({
        postCss: [
            require('postcss-css-variables')()
        ]
   });

Preprocessorless CSS

If you don’t want to deal with a preprocessor, there’s a command for that—it will grab all of your CSS files, concatenate them, and output them to the public/css directory, just as if they had been run through a preprocessor. There are a few options, which you can see in Example 6-6.

Example 6-6. Combining stylesheets with Mix
// Combines all files from resources/css
mix.styles('resources/css', 'public/css/all.css');

// Combines files from resources/css
mix.styles([
    'resources/css/normalize.css',
    'resources/css/app.css'
], 'public/css/all.css');

Concatenating JavaScript

The options available for working with normal JavaScript files are very similar to those available for normal CSS files. Take a look at Example 6-7.

Example 6-7. Combining JavaScript files with Mix
let mix = require('laravel-mix');

// Combines all files from resources/js
mix.scripts('resources/js', 'public/js/all.js');

// Combines files from resources/js
mix.scripts([
    'resources/js/normalize.js',
    'resources/js/app.js'
], 'public/js/all.js');

Processing JavaScript

If you want to process your JavaScript—for example, to compile your ES6 code into plain JavaScript—Mix makes it easy to use Webpack for this purpose (see Example 6-8).

Example 6-8. Processing JavaScript files in Mix with Webpack
let mix = require('laravel-mix');

mix.js('resources/js/app.js', 'public/js');

These scripts look for the provided filename in resources/js and output to public/js/app.js.

You can use more complicated aspects of Webpack’s feature set by creating a webpack.config.js file in your project root.

Copying files or directories

To move either a single file or an entire directory, use the copy() method or the copyDirectory() method:

mix.copy('node_modules/pkgname/dist/style.css', 'public/css/pkgname.css');
mix.copyDirectory('source/images', 'public/images');

Versioning

Most of the tips from Steve Souders’s Even Faster Web Sites (O’Reilly) have made their way into our everyday development practices. We move scripts to the footer, reduce the number of HTTP requests, and more, often without even realizing where those ideas originated.

One of Steve’s tips is still very rarely implemented, though, and that is setting a very long cache life on assets (scripts, styles, and images). Doing this means there will be fewer requests to your server to get the latest version of your assets. But it also means that users are extremely likely to have a cached version of your assets, which will make things get outdated, and therefore break, quickly.

The solution to this is versioning. Append a unique hash to each asset’s filename every time you run your build script, and then that unique file will be cached indefinitely—or at least until the next build.

What’s the problem? Well, first you need to get the unique hashes generated and appended to your filenames. But you also will need to update your views on every build to reference the new filenames.

As you can probably guess, Mix handles that for you, and it’s incredibly simple. There are two components: the versioning task in Mix, and the mix() PHP helper. First, you can version your assets by running mix.version() like in Example 6-9.

Example 6-9. mix.version
let mix = require('laravel-mix');

mix.sass('resources/sass/app.scss', 'public/css')
    .version();

The version of the file that’s generated is no different—it’s just named app.css and lives in public/css.

Versioning Assets Using Query Parameters

The way versioning is handled in Laravel is a little different from traditional versioning, in that the versioning is appended with a query parameter instead of by modifying filenames. It still functions the same way, because browsers read it as a “new” file, but it handles a few edge cases with caches and load balancers.

Next, use the PHP mix() helper in your views to refer to that file like in Example 6-10.

Example 6-10. Using the mix() helper in views
<link rel="stylesheet" href="{{ mix("css/app.css") }}">

// Will output something like:

<link rel="stylesheet" href="/css/app.css?id=5ee7141a759a5fb7377a">

Vue and React

Mix can handle building both Vue (with single-file components) and React components. Mix’s default js() call handles Vue, and you can replace it with a react() call if you want to build React components:

mix.react('resources/js/app.js', 'public/js');

If you take a look at the default Laravel sample app.js and the components it imports (Example 6-11), you’ll see that you don’t have to do anything special to work with Vue components. A simple mix.js() call makes this possible in your app.js.

Example 6-11. App.js configured to work with Vue
window.Vue = require('vue');

Vue.component('example-component', require('./components/ExampleComponent.vue'));

const app = new Vue({
    el: '#app'
});

And if you switch to react(), this is all you need to run in your file for your first component:

require('./components/Example');

Both presets also bring in Axios, Lodash, and Popper.js, so you don’t have to spend any time getting your Vue or React ecosystems set up.

Hot Module Replacement

When you’re writing single components with Vue or React, you’re likely used to either refreshing the page every time your build tool recompiles your components or, if you’re using something like Mix, relying on Browsersync to reload it for you.

That’s great, but if you’re working with single-page apps (SPAs), that means you’re booted back to the beginning of the app; that refresh wipes any state you had built up as you navigated through the app.

Hot Module Replacement (HMR, sometimes called hot reloading) solves this problem. It’s not always easy to set up, but Mix comes with it enabled out of the box. HMR works essentially as if you’d taught Browsersync to not reload the entire file that was recompiled, but instead to just reload the bits of code you changed. That means you can get the updated code injected into your browser, but still retain the state you had built up as you got your SPA into just the right spot for testing.

To use HMR, you’ll want to run npm run hot instead of npm run watch. In order for it to work correctly, all of your <script> references have to be pulling the right versions of your JavaScript files. Essentially, Mix is booting up a small Node server at localhost:8080, so if your <script> tag points to a different version of the script, HMR won’t work.

The easiest way to achieve this is to just use the mix() helper to reference your scripts. This helper will handle prepending either localhost:8080 if in HMR mode or your domain if you’re in a normal development mode. Here’s what it looks like inline:

<body>
    <div id="app"></div>

    <script src="{{ mix('js/app.js') }}"></script>
</body>

If you develop your applications on an HTTPS connection—for example, if you run valet secure—all your assets must also be served via an HTTPS connection. This is a little bit trickier, so it’s best to consult the HMR docs.

Vendor extraction

The most common frontend bundling pattern, which Mix also encourages, ends up generating a single CSS file and a single JavaScript file that encompasses both the app-specific code for your project and the code for all its dependencies.

However, this means that vendor file updates require the entire file to be rebuilt and recached, which might introduce an undesirable load time.

Mix makes it easy to extract all of the JavaScript from your app’s dependencies into a separate vendor.js file. Simply supply a list of the vendor’s library names to the extract() method, chained after your js() call. Take a look at Example 6-12 to see how it looks.

Example 6-12. Extracting a vendor library into a separate file
mix.js('resources/js/app.js', 'public/js')
    .extract(['vue'])

This outputs your existing app.js and then two new files: manifest.js, which gives instructions to your browser about how to load the dependencies and app code, and vendor.js, which contains the vendor-specific code.

It’s important to load these files in the correct order in your frontend code—first manifest.js, then vendor.js, and finally app.js:

Extracting All Dependencies Using extract() in Mix 4.0+

If your project is using Laravel Mix 4.0 or greater, you can call the extract() method with no arguments. This will extract the entire dependency list for your application.

<script src="{{ mix('js/manifest.js') }}"></script>
<script src="{{ mix('js/vendor.js') }}"></script>
<script src="{{ mix('js/app.js') }}"></script>

Environment variables in Mix

As Example 6-13 shows, if you prefix an environment variable (in your .env file) with MIX_, it will become available in your Mix-compiled files with the naming convention process.env.ENV_VAR_NAME.

Example 6-13. Using .env variables in Mix-compiled JavaScript
# In your .env file
MIX_BUGSNAG_KEY=lj12389g08bq1234
MIX_APP_NAME="Your Best App Now"
// In Mix-compiled files
process.env.MIX_BUGSNAG_KEY

// For example, this code:
console.log("Welcome to " + process.env.MIX_APP_NAME);

// Will compile down to this:
console.log("Welcome to " + "Your Best App Now");

You can also access those variables in your Webpack configuration files using Node’s dotenv package, as shown in Example 6-14.

Example 6-14. Using .env variables in Webpack configuration files
// webpack.mix.js
let mix = require('laravel-mix');
require('dotenv').config();

let isProduction = process.env.MIX_ENV === "production";

Laravel UI: Frontend Presets and Auth Scaffolding

As a full-stack framework, Laravel has more connections to and opinions about frontend tooling than your average backend framework. Out of the box it provides an entire build system, which we’ve already covered, but it also includes tools like Bootstrap, Axios, and Lodash, and has easy-to-install presets for Vue, React, and more.

Frontend Presets

You can get a sense of the frontend tools that come along with each new Laravel install by taking a look at package.json, webpack.mix.js (or gulpfile.js in older versions of Laravel), and the views, JavaScript files, and CSS files in the resources directory.

In Laravel prior to 6, the default Laravel frontend preset was Vue-focused, but in 6+ it’s just a very light JavaScript system. If you want to work with a frontend framework like Vue or React, you’ll want to use Laravel’s “presets”, pre-baked configurations of dependencies and bootstrap code. Laravel offers presets for Vue, React, and Bootstrap through the laravel/ui package, and you can also pull in myriad third-party presets created by the community.

Once you’ve brought in the laravel/ui package by running composer require laravel/ui, you can use a built-in preset by running php artisan ui preset_name:

# After including laravel/ui...
php artisan ui vue
php artisan ui react
php artisan ui bootstrap
php artisan ui none

Third-party frontend presets

If you’re interested in creating your own preset, or using one created by another community member, that’s also possible with the frontend preset system. There’s a GitHub organization designed to make it easy to find great third-party frontend presets, and they’re easy to install. For most, the steps are as follows:

  1. Install the package (e.g., composer require laravel-frontend-presets/tailwindcss).

  2. Install the preset (e.g., php artisan preset tailwindcss).

  3. Just like with the built-in presets, run npm install and npm run dev.

If you want to create a preset of your own, the same organization has a skeleton repository you can fork to make it easier.

Auth Scaffolding

Although they’re technically not a part of the frontend presets, Laravel has a series of routes and views called the auth scaffold that are, essentially, frontend presets.

In Laravel 6+, you’ll also need to import the laravel/ui package in order to use the auth scaffold; in previous versions they were built into the core. Simply run composer require laravel/ui once in your project and you’ll now be able to run the following command.

If you run php artisan ui:auth (or php artisan make:auth prior to Laravel 6), you’ll get a login page, a signup page, a new master template for the “app” view of your app, routes to serve these pages, and more. Take a look at Chapter 9 to learn more.

Pagination

For something that is so common across web applications, pagination still can be wildly complicated to implement. Thankfully, Laravel has a built-in concept of pagination, and it’s also hooked into Eloquent results and the router by default.

Paginating Database Results

The most common place you’ll see pagination is when you are displaying the results of a database query and there are too many results for a single page. Eloquent and the query builder both read the page query parameter from the current page request and use it to provide a paginate() method on any result sets; the single parameter you should pass paginate() is how many results you want per page. Take a look at Example 6-15 to see how this works.

Example 6-15. Paginating a query builder response
// PostController
public function index()
{
   return view('posts.index', ['posts' => DB::table('posts')->paginate(20)]);
}

Example 6-15 specifies that this route should return 20 posts per page, and will define which “page” of results the current user is on based on the URL’s page query parameter, if it has one. Eloquent models all have the same paginate() method.

When you display the results in your view, your collection will now have a links() method on it (or render() for Laravel 5.1) that will output the pagination controls, with class names from the Bootstrap component library assigned to them by default (see Example 6-16).

Example 6-16. Rendering pagination links in a template
// posts/index.blade.php
<table>
@foreach ($posts as $post)
    <tr><td>{{ $post->title }}</td></tr>
@endforeach
</table>

{{ $posts->links() }}

// By default, $posts->links() will output something like this:
<ul class="pagination">
    <li class="page-item disabled"><span>&laquo;</span></li>
    <li class="page-item active"><span>1</span></li>
    <li class="page-item">
        <a class="page-link" href="http://myapp.com/posts?page=2">2</a>
    </li>
    <li class="page-item">
        <a class="page-link" href="http://myapp.com/posts?page=3">3</a>
        </li>
    <li class="page-item">
        <a class="page-link" href="http://myapp.com/posts?page=2" rel="next">
            &raquo;
        </a>
    </li>
</ul>

Customizing the Number of Pagination Links in Laravel 5.7 and Later

If you’d like to control how many links show on either side of the current page, projects running Laravel 5.7 and later can customize this number easily with the onEachSide() method:

DB::table('posts')->paginate(10)->onEachSide(3);

// Outputs:
// 5 6 7 [8] 9 10 11

Manually Creating Paginators

If you’re not working with Eloquent or the query builder, or if you’re working with a complex query (e.g., one using groupBy), you might find yourself needing to create a paginator manually. Thankfully, you can do that with the IlluminatePaginationPaginator or IlluminatePaginationLengthAwarePaginator classes.

The difference between the two classes is that Paginator will only provide previous and next buttons, but no links to each page; LengthAwarePaginator needs to know the length of the full result so that it can generate links for each individual page. You may find yourself wanting to use Paginator on large result sets, so your paginator doesn’t have to be aware of a massive count of results that might be costly to run.

Both Paginator and LengthAwarePaginator require you to manually extract the subset of content that you want to pass to the view. Take a look at Example 6-17 for an example.

Example 6-17. Manually creating a paginator
use IlluminateHttpRequest;
use IlluminatePaginationPaginator;

Route::get('people', function (Request $request) {
    $people = [...]; // huge list of people

    $perPage = 15;
    $offsetPages = $request->input('page', 1) - 1;

    // The Paginator will not slice your array for you
    $people = array_slice(
        $people,
        $offsetPages * $perPage,
        $perPage
    );

    return new Paginator(
        $people,
        $perPage
    );
});

The Paginator syntax changed over the span of several versions of Laravel, so if you’re using 5.1, take a look at the docs to find the correct syntax.

Message Bags

Another common but painful feature in web applications is passing messages between various components of the app, when the end goal is to share them with the user. Your controller, for example, might want to send a validation message: “The email field must be a valid email address.” However, that particular message doesn’t just need to make it to the view layer; it actually needs to survive a redirect and then end up in the view layer of a different page. How do you structure this messaging logic?

IlluminateSupportMessageBag is a class tasked with storing, categorizing, and returning messages that are intended for the end user. It groups all messages by key, where the keys are likely to be something like errors and messages, and it provides convenience methods for getting all its stored messages or only those for a particular key and outputting these messages in various formats.

You can spin up a new instance of MessageBag manually like in Example 6-18. To be honest though, you likely won’t ever do this manually—this is just a thought exercise to show how it works.

Example 6-18. Manually creating and using a message bag
$messages = [
    'errors' => [
        'Something went wrong with edit 1!',
    ],
    'messages' => [
        'Edit 2 was successful.',
    ],
];
$messagebag = new IlluminateSupportMessageBag($messages);

// Check for errors; if there are any, decorate and echo
if ($messagebag->has('errors')) {
    echo '<ul id="errors">';
    foreach ($messagebag->get('errors', '<li><b>:message</b></li>') as $error) {
        echo $error;
    }
    echo '</ul>';
}

Message bags are also closely connected to Laravel’s validators (you’ll learn more about these in “Validation”): when validators return errors, they actually return an instance of MessageBag, which you can then pass to your view or attach to a redirect using redirect('route')->withErrors($messagebag).

Laravel passes an empty instance of MessageBag to every view, assigned to the variable $errors; if you’ve flashed a message bag using withErrors() on a redirect, it will get assigned to that $errors variable instead. That means every view can always assume it has an $errors MessageBag it can check wherever it handles validation, which leads to Example 6-19 as a common snippet developers place on every page.

Example 6-19. Error bag snippet
// partials/errors.blade.php
@if ($errors->any())
    <div class="alert alert-danger">
        <ul>
        @foreach ($errors as $error)
            <li>{{ $error }}</li>
        @endforeach
        </ul>
    </div>
@endif

Missing $errors Variable

If you have any routes that aren’t under the web middleware group, they won’t have the session middleware, which means they won’t have this $errors variable available.

Named Error Bags

Sometimes you need to differentiate message bags not just by key (notices versus errors) but also by component. Maybe you have a login form and a signup form on the same page; how do you differentiate them?

When you send errors along with a redirect using withErrors(), the second parameter is the name of the bag: redirect('dashboard')->withErrors($validator, 'login'). Then, on the dashboard, you can use $errors->login to call all of the methods you saw before: any(), count(), and more.

String Helpers, Pluralization, and Localization

As developers, we tend to look at blocks of text as big placeholder divs, waiting for the client to put real content into them. Seldom are we involved in any logic inside these blocks.

But there are a few circumstances where you’ll be grateful for the tools Laravel provides for string manipulation.

The String Helpers and Pluralization

Laravel has a series of helpers for manipulating strings. They’re available as methods on the Str class (e.g., Str::plural()).

Removal of string and array global helpers in Laravel 6+

Laravel, prior to 6.0, included global helpers that were aliases for the Str and Arr methods. These global str_ and array_ helpers were removed in Laravel 6 and exported to a separate package. If you’d like you can install the laravel/helpers package via Composer: composer require laravel/helpers.

The Laravel documentation covers all of them in detail, but here are a few of the most commonly used string helpers:

e()

A shortcut for html_entities(); encodes all HTML entities for safety.

Str::startsWith(), Str::endsWith(), Str::contains()

Check a string (first parameter) to see if it starts with, ends with, or contains another string (second parameter).

Str::is()

Checks whether a string (second parameter) matches a particular pattern (first parameter)—for example, foo* will match foobar and foobaz.

Str::slug()

Converts a string to a URL-type slug with hyphens.

Str::plural(word, count), Str::singular()

Pluralizes a word or singularizes it; English-only (e.g., Str::plural('dog') returns dogs; Str::plural('dog', 1')) returns dog).

Str::camel()(), Str::kebab(), Str::snake(), Str::studly(), Str::title()

Convert a provided string to a different capitalization “case”.

Str::after(), Str::before(), Str::limit()

Trim a string and provide a substring. Str::after() returns everything after a given string and Str::before() everything before the given string (both accept the full string as the first parameter and the string you’re using to cut as the second). Str::limit() truncates a string (first parameter) to a given number of characters (second parameter).

Localization

Localization allows you to define multiple languages and mark any strings as targets for translation. You can set a fallback language, and even handle pluralization variations.

In Laravel, you’ll need to set an “application locale” at some point during the page load so the localization helpers know which bucket of translations to pull from. Each “locale” is usually connected to a translation, and will often look like “en” (for English). You’ll do this with App::setLocale($localeName), and you’ll likely put it in a service provider. For now you can just put it in the boot() method of AppServiceProvider, but you may want to create a LocaleServiceProvider if you end up with more than just this one locale-related binding.

You can define your fallback locale in config/app.php, where you should find a fallback_locale key. This allows you to define a default language for your application, which Laravel will use if it can’t find a translation for the requested locale.

Basic localization

So, how do we call for a translated string? There’s a helper function, __($key), that will pull the string for the current locale for the passed key or, if it doesn’t exist, grab it from the default locale. In Blade you can also use the @lang() directive. Example 6-20 demonstrates how a basic translation works. We’ll use the example of a “back to the dashboard” link at the top of a detail page.

Example 6-20. Basic use of __()
// Normal PHP
<?php echo __('navigation.back'); ?>
// Blade
{{ __('navigation.back') }}

// Blade directive
@lang('navigation.back')

Let’s assume we are using the es locale right now. Laravel will look for a file in resources/lang/es/navigation.php, which it will expect to return an array. It’ll look for a back key on that array, and if it exists, it’ll return its value. Take a look at Example 6-21 for a sample.

Example 6-21. Using a translation
// resources/lang/es/navigation.php
return [
    'back' => 'Volver al panel',
];

// routes/web.php
Route::get('/es/contacts/show/{id}', function () {
    // Setting it manually, for this example, instead of in a service provider
    App::setLocale('es');
    return view('contacts.show');
});

// resources/views/contacts/show.blade.php
<a href="/contacts">{{ __('navigation.back') }}</a>

The Translation Helper Prior to Laravel 5.4

In projects running versions of Laravel prior to 5.4, the __() helper isn’t available. You will instead have to use the trans() helper, which accesses an older translation system that works similarly to what we’re describing here, but can’t access the JSON translation system.

Parameters in localization

The preceding example was relatively simple. Let’s dig into some that are more complex. What if we want to define which dashboard we’re returning to? Take a look at Example 6-22.

Example 6-22. Parameters in translations
// resources/lang/en/navigation.php
return [
    'back' => 'Back to :section dashboard',
];

// resources/views/contacts/show.blade.php
{{ __('navigation.back', ['section' => 'contacts']) }}

As you can see, prepending a word with a colon (:section) marks it as a placeholder that can be replaced. The second, optional, parameter of __() is an array of values to replace the placeholders with.

Pluralization in localization

We already covered pluralization, so now just imagine you’re defining your own pluralization rules. There are two ways to do it; we’ll start with the simplest, as shown in Example 6-23.

Example 6-23. Defining a simple translation with an option for pluralization
// resources/lang/en/messages.php
return [
    'task-deletion' => 'You have deleted a task|You have successfully deleted tasks',
];

// resources/views/dashboard.blade.php
@if ($numTasksDeleted > 0)
    {{ trans_choice('messages.task-deletion', $numTasksDeleted) }}
@endif

As you can see, we have a trans_choice() method, which takes the count of items affected as its second parameter; from this it will determine which string to use.

You can also use any translation definitions that are compatible with Symfony’s much more complex Translation component; see Example 6-24 for an example.

Example 6-24. Using the Symfony’s Translation component
// resources/lang/es/messages.php
return [
    'task-deletion' => "{0} You didn't manage to delete any tasks.|" .
        "[1,4] You deleted a few tasks.|" .
        "[5,Inf] You deleted a whole ton of tasks.",
];

Storing the default string as the key with JSON

One common difficulty with localization is that it’s hard to ensure there’s a good system for defining key namespacing—for example, remembering a key nested three or four levels deep or being unsure which key a phrase used twice in the site should use.

An alternative to the slug key/string value pair system is to store your translations using your primary language string as the key, instead of a made-up slug. You can indicate to Laravel that you’re working this way by storing your translation files as JSON in the resources/lang directory, with the filename reflecting the locale (Example 6-25).

Example 6-25. Using JSON translations and the __() helper
// In Blade
{{ __('View friends list') }}
// resources/lang/es.json
{
  'View friends list': 'Ver lista de amigos'
}

This is taking advantage of the fact that the __() translation helper, if it can’t find a matching key for the current language, will just display the key. If your key is the string in your app’s default language, that’s a much more reasonable fallback than, for example, widgets.friends.title.

JSON Translations Unavailable Prior to Laravel 5.4

The JSON string translation format is only available in Laravel 5.4 and later.

Testing

In this chapter we focused primarily on Laravel’s frontend components. These are less likely the objects of unit tests, but they may at times be used in your integration tests.

Testing Message and Error Bags

There are two primary ways of testing messages passed along with message and error bags. First, you can perform a behavior in your application tests that sets a message that will eventually be displayed somewhere, then redirect to that page and assert that the appropriate message is shown.

Second, for errors (which is the most common use case), you can assert the session has errors with $this->assertSessionHasErrors($bindings = []). Take a look at Example 6-26 to see what this might look like.

Example 6-26. Asserting the session has errors
public function test_missing_email_field_errors()
{
    $this->post('person/create', ['name' => 'Japheth']);
    $this->assertSessionHasErrors(['email']);
}

In order for Example 6-26 to pass, you’ll need to add input validation to that route. We’ll cover this in Chapter 7.

Translation and Localization

The simplest way to test localization is with application tests. Set the appropriate context (whether by URL or session), “visit” the page with get(), and assert that you see the appropriate content.

TL;DR

As a full-stack framework, Laravel provides tools and components for the frontend as well as the backend.

Mix is a layer in front of Webpack that makes common tasks and configurations much simpler. Mix makes it easy to use popular CSS pre- and post-processors, common JavaScript processing steps, and much more.

Laravel also offers other internal tools that target the frontend, including tools for implementing pagination, message and error bags, and localization.

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

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