Documentation

Last updated: September 8th, 2019

Events

There are many events that are fired throughout the applications lifecycle if they meet the requirements.

A current list of events fired are:

Frontend

  • UserConfirmed
  • UserLoggedIn
  • UserLoggedOut
  • UserRegistered
  • UserProviderRegistered

Backend

  • UserCreated
  • UserDeactivated
  • UserDeleted
  • UserPasswordChanged
  • UserPermanentlyDeleted
  • UserReactivated
  • UserRestored
  • UserUpdated
  • UserConfirmed
  • UserUnconfirmed
  • UserSocialDeleted
  • RoleCreated
  • RoleDeleted
  • RoleUpdated

The event stubs are found in the app\Events directory and are registered in the EventServiceProvider using the Event Subscriber feature of Laravel.

The event listeners are found in the app\Listeners directory.

Subscribers

Currently, all the subscriber events do is log when the user performs the event being fired. This is meant to be extended by you to do whatever you want.

The events are fired using the Laravel event() helper throughout the application.

When the event helper is called with the event class, the objects needed to process the event are passed through, which are caught in the constructor of the Event classes themselves, and passed through to the listener as a property on the $event variable. (This is default Laravel behavior and not specific to this application).

Exceptions

Besides the default PHP Exception class, there are two custom exception that can get thrown called GeneralException and ReportableException.

These exceptions do nothing special except act as a way to change the default exception functionality when calling it.

Note: ReportableException will get logged as GeneralException will not.

Any custom exceptions live in the app\Exceptions directory.

Anytime you throw these exception, it will not display a "Whoops" error. Instead, it will take the error into a session variable and redirect the user to the previous page and display the message in a bootstrap danger alert box.

If a regular PHP Exception is thrown, it will still get caught and the default functionality will takeover.

Example order of events would be:

  1. User tries to delete the administrator account.
  2. Repository says this is not allowed and throws a GeneralException with the text explaining the reason.
  3. The Handler class catches the exception and redirects the user back with old input.
  4. A session variable called flash_danger is set with the exception text.
  5. The page is rendered and a file called messages.blade.php checks for this session variable, and displays the proper bootstrap alert with the provided text.

Note: The withFlashDanger($text) is some Laravel magic that is parsed into with('flash_danger', $text). This functionality exists in many places around the Laravel Framework.

Note: In the case of the request classes, the default functionality when validation fails is to redirect back with errors:

From the Laravel Documentation:

"If validation fails, a redirect response will be generated to send the user back to their previous location. The errors will also be flashed to the session so they are available for display. If the request was an AJAX request, a HTTP response with a 422 status code will be returned to the user including a JSON representation of the validation errors."

Composers

The app\Http\Composers directory holds all view composers.

There are 2 composers that ship with the boilerplate:

  • GlobalComposer: Variables sent to every view.
  • SidebarComposer: Variables sent to views containing a sidebar.

The composer classes themselves are registered in the app\Providers\ComposerServiceProvider class.

The GlobalComposer binds the variable $logged_in_user to every view.

If the user is logged in, it will be an app\Models\Auth\User object, if they are not it will be false.

Routes

The only stock route file modified is routes\web.php

Lets look at web.php, line by line:

Route::get('lang/{lang}', 'LanguageController@swap');

The first line gets envoked anytime the user chooses a language from the language picker dropdown. The LanguageController's swap method switches the language code in the session and refreshes the page for it to take effect.


Route::group(['namespace' => 'Frontend', 'as' => 'frontend.'], function () {
    includeRouteFiles(__DIR__ . '/frontend/');
});

This section registers all of the frontend routes, such as login, register, etc.

Key Points:

  • The namespaces of the routes indicate the folder structure. In the above case the routes that will be included live in routes\frontend.
  • The as property prepends the value to all routes inside the closure, so in the above case all included route names will be prepended with frontend..
  • The includeRouteFiles() is a helper function located in app\helpers.php and autoloaded by composer. (Learn about the helpers here). This takes all files in the specified directory and includes them in the closure so you can add new routes without having to touch the web.php routes file.

Route::group(['namespace' => 'Backend', 'prefix' => 'admin', 'as' => 'admin.', 'middleware' => 'admin'], function () {
    includeRouteFiles(__DIR__ . '/backend/');
});

This section registers all of the backend routes, such as admin dashboard, user management, etc.

It is nearly identical as the frontend routes with the addition of the admin middleware and prefix.

Key Points:

  • The namespaces of the routes indicate the folder structure. In the above case the routes that will be included live in routes\backend.
  • The as property prepends the value to all routes inside the closure, so in the above case all included route names will be prepended with admin..
  • The prefix property prepends the value before all of the URL's of the routes inside the closure, so in the above case all route URL's will be prepended with admin/.
  • The includeRouteFiles() is a helper function located in app\helpers.php and autoloaded by composer. (Learn about the helpers here). This takes all files in the specified directory and includes them in the closure so you can add new routes without having to touch the web.php routes file.
  • The admin middleware is specified in app\Http\Kernel.php and states that anyone trying to access the routes in the following closure must:
    • Be logged in
    • Have the view-backend permission associated with one of their roles or by itself.

Note: Administrator should get all permissions so you do not have to specify the administrator role everywhere.

Note: Most route resources use Laravel's Route/Model Binding which you will see as well in the controller methods.

For more information about the permission middleware included in the admin middleware, see middleware and Access Control.

Controllers

All of the controllers live in the default Laravel controller location of app\Http\Controllers and follow a namespacing convention of the folders they're in.

The controllers are very clean, so there is not much to say, some key points and different sections to read about are:

  • All controller methods use injected Request Classes to both validate and act as a second security check after the middleware.
  • All database logic is extracted out to Repositories.
  • Controllers are only comprised of CRUD methods, any other methods have been extracted to other controllers for cleanliness.

Middleware

Laravel Boilerplate ships with 5 additional middleware out of the box.

There is also one extra middleware group:

LocaleMiddleware

The LocaleMiddleware is appended to the web middleware stack and runs on each request.

It takes care of:

  • Checking to see if localization is turned on in the config file.
  • Setting the session locale code.
  • Setting the PHP locale code.
  • Setting the Carbon library locale code.
  • Checking to see if the required language is a "Right-to-Left" language and sets a session variable to be used elsewhere in the system.

PasswordExpires

The password expires middleware forces the user to change their password after X number of days if specified in the access config file's password_expires_days property. You can disable it by setting it to false.

CheckForReadOnlyMode

This middleware is triggered by the env item APP_READ_ONLY which will disable any POST requests except /login, which will put the application into a demo mode that people can you but not alter data.

ToBeLoggedOut

This middleware checks the to_be_logged_out property on the user, and if enabled invalidates the user session and logs them out.

SecureHeaders

This middleware is disabled by default in the Kernel. This middleware enables headers specified by https://securityheaders.com/

Middleware Groups

Laravel Boilerplate currently ships with one additional middleware group called admin.

The admin middleware is specified in app\Http\Kernel.php and states that anyone trying to access the routes in the following closure must:

  • Be logged in
  • Have the view-backend permission associated with one of their roles.
  • Be subject to being forced to change your password after X number of days.

It currently wraps all backend routes by default.

Note: If you remove the admin middleware from the backend routes, anyone will be able to access the backend unless you specify elsewhere.

Requests

Any controller method throught the application that either needs validation or a security check, will have its own injected Request class.

App requests are stored in the app\Http\Request folder and their namespaces match the folder structure.

Learn more about Laravel request classes here.

Models

Models in Laravel Boilerplate live in the app\Models directory instead of the root for organizational purposes.

The folder structure of the Model indicates its namespace as always.

Attributes

If a model has any custom attributes, they are held as traits in an Attributes folder of the current model directory.

Relationships

If a model has any relationships, they are held as traits in an Relationships folder of the current model directory.

Scopes

If a model has any scopes, they are held as traits in an Scopes folder of the current model directory.

Methods

If a model has any methods, they are held as traits in a Method folder of the current model directory.

Notifications

Laravel Boilerplate ships with a few Models that utilize the Notifiable trait, and in turn there are a few notifications that get sent.

  • UserNeedsConfirmation - Send when a user registers and email confirmation is on.
  • UserNeedsPasswordReset- Send when the user requests a password reset.
  • UserAccountActive s- Send when the users account is confirmed by an admin.

All notifications are stored in the app\Notifications directory by default.

All notifications are self-explanatory, for more information view the Laravel Documentation.

Providers

Service Providers are classes that sort of "bootstrap" your application. Laravel ships with many, there are some new ones, and some default ones have been modified.

AppServiceProvider

The following modifications have been made to the default AppServiceProvider:

  • The boot method does the same checks as the LocalizationMiddleware class.
  • There is a conditional to check to see if the app is in production if you need to do anything specific such as force https.
  • The register method has a conditional for local development where you can register service providers that you only need for local development, since there is no point in registering them in a production environment.
  • Registers any blade extensions.

ComposerServiceProvider

The ComposerServiceProvider registers any view composers the application needs.

EventServiceProvider

The EventServiceProvider is extended to use the $subscribers property to register the event listeners for the system.

HelperServiceProvider

The HelperServiceProvider registers any helper classes in the global helper directory.

ObserverServiceProvider

The ObserverServiceProvider is used to register any model observer classes.

By default the supplied one registers the UserObserver class to the User model, which logs the users password history.

RouteServiceProvider

The only addition to the RouteServiceProvider is in the boot method.

Since most of the controller/routes use Laravels Route/Model Binding, there is one instance where we need to specify a binding.

We specify this binding for use in deleting/restoring a user, because the binding needs to know to have to check for deleted users as well. If you get rid of this and just use the default user binding, it will fail because it's not checking for a user id that has a non-null deleted_at timestamp.

We register the impersonate package routes.

Repositories

Repositories are a way of extracting database logic into their own classes so you can have slim easy to read controllers.

Repositories are injected into any controller that needs them via the constructor, and are resolved out of the service container.

You do not have to use repositories, or repository/contract patterns, but it is good to learn ways to better structure your code instead of having bloated controllers.

Key Points

  • Repositories extend a base repository class to get included helper methods.
  • If you extend the base repository you must inject the current model into the constructor.

Access Control

Role/Permission control has been replaced with spatie/laravel-permission in this version of the boilerplate.

Laravel Boilerplate ships with other access features as well:

Features:

  • Register/Login/Logout/Password Reset
  • Third party login (Github/Facebook/Twitter/Google/Linked In/BitBucket)
  • Account Confirmation By E-mail
  • Resend Confirmation E-mail
  • Option for Manual Account Confirmation by Admin
  • Login Throttling
  • Enable/Disable Registration
  • Single Login (Logout all other devices)
  • Clear User Session
  • Configurable Password History
  • Password Expiration
  • Administrator Management
    • User Index
    • Activate/Deactivate Users
    • Soft & Permanently Delete Users
    • Resend User Confirmation E-mails
    • Change Users Password
    • Create/Manage Roles
    • Manage Users Roles/Permissions
    • Impersonate User
    • Clear User Session

Configuration

 [
        // Whether the registration captcha is on or off
        'contact' => env('CONTACT_CAPTCHA_STATUS', false),
        'registration' => env('REGISTRATION_CAPTCHA_STATUS', false),
        'login' => env('LOGIN_CAPTCHA_STATUS', false),
    ],

    // Whether or not registration is enabled
    'registration' => env('ENABLE_REGISTRATION', true),

    // Configurations for the user
    'users' => [
        // Whether or not the user has to confirm their email when signing up
        'confirm_email' => env('CONFIRM_EMAIL', false),

        // Whether or not the users email can be changed on the edit profile screen
        'change_email' => env('CHANGE_EMAIL', false),

        // The name of the super administrator role
        'admin_role' => 'administrator',

        // The default role all new registered users get added to
        'default_role' => 'user',

        /*
         * Whether or not new users need to be approved by an administrator before logging in
         * If this is set to true, then confirm_email is not in effect
         */
        'requires_approval' => env('REQUIRES_APPROVAL', false),

        // Login username to be used by the controller.
        'username' => 'email',

        /*
         * When active, a user can only have one session active at a time
         * That is all other sessions for that user will be deleted when they log in
         * (They can only be logged into one place at a time, all others will be logged out)
         * AuthenticateSession middleware must be enabled
         */
        'single_login' => env('SINGLE_LOGIN', true),

        /*
         * How many days before users have to change their passwords
         * false is off
         */
        'password_expires_days' => env('PASSWORD_EXPIRES_DAYS', 30),

        /*
         * The number of most recent previous passwords to check against when changing/resetting a password
         * false is off which doesn't log password changes or check against them
         */
        'password_history' => env('PASSWORD_HISTORY', 3),
    ],

    // Configuration for roles
    'roles' => [
        // Whether a role must contain a permission or can be used standalone as a label
        'role_must_contain_permission' => true,
    ],

    /*
     * Socialite session variable name
     * Contains the name of the currently logged in provider in the users session
     * Makes it so social logins can not change passwords, etc.
     */
    'socialite_session_name' => 'socialite_provider',
];

Middleware

Both permission middleware from the spatie/laravel-permission package are used from their default locations and not extended, but you are free to extend them.

Blade Extensions

Custom Blade Directives are registered in the AppServiceProvider.

Localization

Laravel Boilerplate comes in many languages, each language has it's own folder as usual, the language files are very well organized and indented.

If you would like to contribute a language, please make a pull request based on the requirements.

The parts of the localization system are:

The language files try to be as organized as possible first by file name, then by keys frontend, backend, or global. Then by the 'type' they may be display, i.e. 'auth'. Please try to keep them organized if you are contributing to them on GitHub.

Helpers

Helper Classes

Helper classes are stored in app\Helpers.

And helpers stored in app\Helpers\Global are autoloaded as global functions from HelperServiceProvider.

Resources

The resources section has the same high level folder structure as a default installation, with many new items inside:

webpack.mix.js

The webpack.mix.js file that ships with the project is well documented and pretty self-explanatory, it currently looks like:

const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.setPublicPath('public')
    .setResourceRoot('../') // Turns assets paths in css relative to css file
    // .options({
    //     processCssUrls: false,
    // })
    .sass('resources/sass/frontend/app.scss', 'css/frontend.css')
    .sass('resources/sass/backend/app.scss', 'css/backend.css')
    .js('resources/js/frontend/app.js', 'js/frontend.js')
    .js([
        'resources/js/backend/before.js',
        'resources/js/backend/app.js',
        'resources/js/backend/after.js'
    ], 'js/backend.js')
    .extract([
        // Extract packages from node_modules to vendor.js
        'jquery',
        'bootstrap',
        'popper.js',
        'axios',
        'sweetalert2',
        'lodash'
    ])
    .sourceMaps();

if (mix.inProduction()) {
    mix.version()
        .options({
            // Optimize JS minification process
            terser: {
                cache: true,
                parallel: true,
                sourceMap: true
            }
        });
} else {
    // Uses inline source-maps on development
    mix.webpackConfig({
        devtool: 'inline-source-map'
    });
}

Assets

JS

  • /backend - Contains the backend specific javascript to be compiled.
  • /frontend - Contains the frontend specific javascript to be compiled.
  • bootstrap.js - The Laravel javascript bootstrap file.
  • plugins.js - Contains useful plugins and snippets that would be beneficial globally.

Sass

  • /backend - Contains backend .scss files ready to be compiled into css.
  • /frontend - Contains frontend .scss files ready to be compiled into css.

Lang

See Localization.

Views

  • /backend - The backend blade files.
  • /frontend - The frontend blade files
  • /includes - Blade files that are included in all master app layouts.
  • /vendor - Files generated from the vendor:publish command.

Testing

To run the tests run phpunit from the command line in the root of the project.

Misc.

Flash Messages

The messages.blade.php file is included after the body of all master app layouts in this project, and takes care of displaying any errors from a session, validation, etc using bootstrap alert boxes.

If not a validation error, or message bag error, then the following session variables are supported:

  • flash_success - Bootstrap alert-success box.
  • flash_warning - Bootstrap alert-warning box.
  • flash_info - Bootstrap alert-info box.
  • flash_danger - Bootstrap alert-danger box.
  • flash_message - Bootstrap alert box.

You will see these used in many controller methods, or in exception handling except they use Laravel's magic method syntax.

Magic Methods

This application makes use of Laravel's magic methods in many places, especially controllers.

For example, you may see a controller return that looks like this:

return redirect()->route('admin.auth.user.index')->withFlashSuccess(trans('alerts.backend.users.created'));

or:

return view('backend.auth.show')->withUser($user);

In both of these examples you will see a compound function call, that doesn't exist in the documentation.

  • withFlashSuccess
  • withUser

Laravel will convert both of these methods to a default with() method when it compiles, so both of the above examples will output the following code.

return redirect()->route('admin.auth.user.index')->with('flash_success', trans('alerts.backend.users.created'));

or:

return view('backend.auth.show')->with('user', $user);

Both methods work, but I think the magic makes it more elegant so I use that throughout.

Socialite

To configure socialite, add your credentials to your .env file. The redirects must follow the convention http://mysite.com/login/SERVICE. Available services are github, facebook, linkedin, bitbucket, twitter, and google. The links on the login page are generated based on which credentials are provided in the .env file.

Troubleshooting

If for any reason something goes wrong, try the following:

composer clear-all from the CLI

This will:

  • php artisan clear-compiled
  • php artisan cache:clear
  • php artisan route:clear
  • php artisan view:clear
  • php artisan config:clear
  • composer dumpautoload -o

Deployment

When pushing your app to production, you should make sure you run the following:


yarn prod

Compress all of you assets into a single file specified in webpack.mix.js.


composer cc

This will run the above clear-all command followed by the cache-all command:

  • php artisan config:cache
  • php artisan route:cache