Documentation

Last updated: December 10th, 2016

Events

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

A currently list of events fired are:

Frontend

  • UserConfirmed
  • UserLoggedIn
  • UserLoggedOut
  • UserRegistered

Backend

  • UserCreated
  • UserDeactivated
  • UserDeleted
  • UserPasswordChanged
  • UserPermanentlyDeleted
  • UserReactivated
  • UserRestored
  • UserUpdated
  • 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.

Frontend Subscribers

Currently, all the frontend 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.

Backend Subscribers

If you take a look at the backend subscribers, each item utilizes the history helper to log a row into the history table when that event is fired.

Code Example
public function onCreated($event) {
    history()->log(
        $this->history_slug,
        'trans("history.backend.users.created") '.$event->user->name,
        $event->user->id,
        'plus',
        'bg-green'
    );
}

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

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 is one custom exception that gets thrown called GeneralException.

This exception does nothing special except act as a way to change the default exception functionality when calling it.

Any custom exceptions live in the app\Exceptions directory.

You can see it in action in the Exception Handler class here.

Anytime you throw this 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 is currently one composer that ships with the project called GlobalComposer, and as you may have guessed, any variables bound in this class will be available to all views throughout the application both front and backend.

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\Access\User\User object, if they are not it will be false.

Routes

You should also read the Access Control section to fully understand the routes in this package.

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 appends 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 appends 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 appends 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.
    • Be subject to getting logged out after a set amount of time (timeout middleware), if enabled.

Note: Administrator has 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 access.routeNeedsPermission 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.
  • Any data table has its own dedicated controller.
  • Any data being binded or returned to views are using Laravel magic with() method just like elsewhere in the application.
    • Example: withUser($user) is converted to with('user', $user)

Middleware

Laravel Boilerplate ships with 4 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.

RouteNeedsRole

The RouteNeedsRole middleware is a route middleware and is called as access.routeNeedsRole and takes 2 parameters:

  • The role or roles
  • A boolen stating whether or not the request needs all of the roles (if multiple were supplied)

See examples of use under Access Control.

RouteNeedsPermission

The RouteNeedsPermission middleware is a route middleware and is called as access.routeNeedsPermission and takes 2 parameters:

  • The permission or permissions
  • A boolen stating whether or not the request needs all of the permissions (if multiple were supplied)

See examples of use under Access Control.

SessionTimeout

SessionTimeout middleware is a tool used on the admin middleware stack (though can be used globally) that logs the user out after a set period of time for security purposes.

It is enabled by default with a 5 minute timeout.

There are 2 environment variables you can set:

SESSION_TIMEOUT_STATUS=true
SESSION_TIMEOUT=600

If the environment variables are not set, it will fallback to the defaults set in app\config\misc.php.

As stated above this middleware is included by default in the admin middleware group but can be removed. Though it is more suggested to just turn it off.

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 getting logged out after a set amount of time (timeout middleware), if enabled.

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.

Example Request

We are going to look at the store method of the app\Http\Controllers\Backend\Access\User\UserController to see what's going on.

public function store(StoreUserRequest $request)
{
    $this->users->create(['data' => $request->except('assignees_roles'), 'roles' => $request->only('assignees_roles')]);
    return redirect()->route('admin.access.user.index')->withFlashSuccess(trans('alerts.backend.users.created'));
}

Disregard what's inside the method and instead look at the parameter list.

The StoreUserRequest is being injected to the controller, you do not have to do anything as it is parsed by Laravel automatically, and before the code inside the method even runs.

The request itself looks like this:

class StoreUserRequest extends Request
{

    public function authorize()
    {
        return access()->allow('manage-users');
    }

    public function rules()
    {
        return [
            'name'     => 'required|max:255',
            'email'    =>  ['required', 'email', 'max:255', Rule::unique('users')],
            'password' => 'required|min:6|confirmed',
        ];
    }
}

As you can see it does two things:

  • It authorizes the user against the give permission.
  • It validates the incoming request.

If the authorization fails the user will be redirected back with a message stating they do not have the required permissions.

If the validation fails the default Laravel functionality takes over which is:

"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."

Any controller method that needs to validate data or permission should use a request class for an extra layer of security.


Note: This project uses more general permissions for CRUD operations, meaning the default requests for a manage-users permission would be:

  • ManageUserRequest - for any requests that do not have validation but want to assure the user can manage-users
  • StoreUserRequest - for creating a user and assuring the user can manage-users
  • UpdateUserRequest - for updating a user and assuring the user can manage-users

If you were to get more granular, and have separate permissions for all stages of CRUD, you may have controller methods that are injecting the following request classes:

  • ViewUserRequest - access()->can('view-users')
  • StoreUserRequest - access()->can('store-users')
  • EditUserRequest - access()->can('edit-users')
  • UpdateUserRequest - access()->can('update-users')
  • DeleteUserRequest - access()->can('delete-users')

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.

The majority of the models share the same characteristics:

  • The $table property houses the table name, whether static or pulling from a config file.
  • The $fillable property defines the columns that can be mass assigned. This is preferred over guarded as it is more specific.
  • Some models have a $hidden property to hide certain information from being displayed, in such ways as in an API.
  • Some models have a $dates property to cast certain columns as Carbon objects, such as when using the Laravel SoftDeletes trait.
  • Many models have traits that extend or include other functionality, see below.

Attributes

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

This is also where the system builds the action buttons for each object type, if applicable. See Action Buttons.

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.

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.

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.

AccessServiceProvider

The AccessServiceProvider bootstraps the services for the access control system. Specifically:

  • Autoloads the Access facade so it does not need to be included in config\app.php
  • Registers the access blade extensions.

This file shouldn't need to be modified out of the box.

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 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.

BladeServiceProvider

The BladeServiceProvider registers any custom blade directives you want to be able to use in your blade files.

Laravel Boilerplate ships with 1 non-access related blade extensions:

  • A @langRTL directive, which checks to see if the current language in the session wants Right-to-Left support so you can update things accordingly.

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.

HistoryServiceProvider

The HistoryServiceProvider registers the facade for the History classes.

MacroServiceProvider

The MacroServiceProvider extends the Macro facade used by the LaravelCollective macro class.

The macros themselves are stored in app\Helpers\Macros directory and can be used anywhere.

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.

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 can extend a base repository class to get included helper methods such as find, getAll, getCount.
  • Extending the base repository class also gives parent methods for save, update, delete, forceDelete, restore, and query.
  • If you extend the base repository you must have a constant called MODEL that is set to the model that you will be working with.
  • If you extend the base repository you have access to $this->query() in the child class, which is the same as calling the model facade.
  • In the case of PermissionRepository there are no methods, however because it does extend base repository you have access to all of the methods stated above.

Access Control

Laravel Boilerplate ships with a custom and feature-rich access control system that stems both the frontend and backend.

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
  • Login Throttling
  • Administrator Management
    • Searchable User Index
    • Activate/Deactivate Users
    • Soft & Permanently Delete Users
    • Resend User Confirmation E-mails
    • Change Users Password
    • Create/Manage Roles
    • Manage Users Roles/Permissions
    • "Login As" User

Configuration

/*
 * Users table used to store users
 */
'users_table' => 'users',

/*
 * Role model used by Access to create correct relations.
*/
'role' => Role::class,

/*
 * Roles table used by Access to save roles to the database.
 */
'roles_table' => 'roles',

/*
 * Permission model used by Access to create correct relations.
 */
'permission' => Permission::class,

/*
 * Permissions table used by Access to save permissions to the database.
 */
'permissions_table' => 'permissions',

/*
 * permission_role table used by Access to save relationship between permissions and roles to the database.
 */
'permission_role_table' => 'permission_role',

/*
 * role_user table used by Access to save assigned roles to the database.
 */
'role_user_table' => 'role_user',

/*
 * Configurations for the user
 */
'users' => [
    /*
     * The role the user is assigned to when they sign up from the frontend, not namespaced
     */
    'default_role' => 'User',
    //'default_role' => 2, //You can also use an ID

    /*
     * Whether or not the user has to confirm their email when signing up
     */
    'confirm_email' => true,

    /*
     * Whether or not the users email can be changed on the edit profile screen
     */
    'change_email' => false,
],

/*
 * Configuration for roles
 */
'roles' => [
    /*
     * Whether a role must contain a permission or can be used standalone as a label
     * Set this to false if you want to be able to create roles with no permissions to use more as groups
     */
    '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',

/*
 * Application captcha specific settings
 */
'captcha' => [
    /*
     * Whether the registration captcha is on or off
     */
    'registration' => env('REGISTRATION_CAPTCHA_STATUS', false),
],

Middleware

Included route middleware let you authorize by either a role or permission:

Route::group(['middleware' => 'access.routeNeedsPermission:view-backend'], function()
{
    Route::group(['prefix' => 'access'], function ()
        {
            /* User Management */
            Route::resource('users', 'Backend\Access\UserController');
        });
});

The following middleware ships with the boilerplate:

Note:

  • Administrator role gets all roles/permissions by default.
  • You can provide a list of roles or permissions separated by a semi-colon to be used as an array:
//User may have either of the following permissions
access.routeNeedsPermission:view-backend;manage-users

//User may have either of the following roles
access.routeNeedsRole:Executive,User

When using role or permissions list, an optional second parameter is available to say whether or not the user needs to possess all of the supplied roles or permissions, defaulted to false.

//User MUST HAVE view-backend and manage-users permissions
access.routeNeedsPermission:view-backend;manage-users,true

//User MUST HAVE both Executive and User roles
access.routeNeedsRole:Executive;User,true

See also SessionTimeout.

Custom Middleware

If you would like to create your own middleware, the following methods are available:

Note: A helper method access() is also available. E.g. access()->hasRole('Administrator') which resolves the Access facade out of the service container. You can modify this function to add more functionality in app/helpers.php.

/**
     * Checks if the user has a Role by its name.
     * @param string $name
     * @return bool
*/
Access::hasRole($role);

/**
     * Checks to see if the user has an array of roles, and whether or not all must return true to authenticate
     * @param array $roles
     * @param boolean $needsAll
     * @return bool
*/
Access::hasRoles($roles, $needsAll);

/**
     * Check if user has a permission by its name.
     * This also has a wrapper function called hasPermission which takes the same arguments
     * @param string $permission.
     * @return bool
*/
Access::allow($permission);

/**
     * Check an array of permissions and whether or not all are required to continue
     * This also has a wrapper function called hasPermissions which takes the same arguments
     * @param array $permissions
     * @param boolean $needsAll
     * @return bool
*/
Access::allowMultiple($permissions, $needsAll);

Access:: by default uses the currently authenticated user. You can also do:

$user->hasRole($role);
$user->hasRoles($roles, $needsAll);
$user->allow($permission);
$user->allowMultiple($permissions, $needsAll);
$user->hasPermission($permission); //Wrapper function for allow()
$user->hasPermissions($permissions, $needsAll); //Wrapper function for allowMultiple()

There are also various methods that wrap the auth class:

access()->user()
access()->guest()
access()->logout()
access()->id()
access()->loginUsingId($id)

Note: access() can be replaced with the facade Access:: where needed.

Don't Forget

The global composer registers a variable called $logged_in_user that can be used anywhere in the application, and will return the currently logged in user object if logged in, or false if not.

This is good to avoid doing:

@if(access()->user())

Everywhere, and instead shorten it to:

@if($logged_in_user)

Now if you need to change the way you get your logged in user, you only have to do it one place instead of all over your application.

Blade Extensions

Access comes with @blade extensions to help you show and hide data by role or permission without clogging up your code with unwanted if statements:

Note: These are just helper 'if' statements for your views, if you need more control in your view you should consider using the Access API to decide which view to show from your controller.


@role

Accepts a single Role Name or ID

@role('User')
    This content will only show if the authenticated user has the `User` role.
@endauth

@role(1)
    This content will only show if the authenticated user has the role with an ID of `1`.
@endauth

@roles

Accepts an array of Role Names or IDs

@needsroles(['Administrator', 'User'])
    This content will only show if the authenticated user has BOTH the `Administrator` role AND the `User` role.
@endauth

@needsroles([1, 2])
    This content will only show if the authenticated user has BOTH roles with ID's of `1` AND `2`.
@endauth

@permission

Accepts a single Permission Name or ID

@permission('view-backend')
    This content will only show if the authenticated user has the `view-backend` permission.
@endauth

@permission(1)
    This content will only show if the authenticated user permission with an ID of `1`.
@endauth

@permissions

Accepts an array of Permission Names or IDs

@permissions(['view-backend', 'view-some-content'])
    This content will only show if the authenticated user has the `view-backend` permission OR the `view-some-content` permission.
@endauth

@permissions([1, 2])
    This content will only show if the authenticated user has the permission with ID of `1` OR `2`.
@endauth

@needspermissions

Accepts an array of permissions or permission IDs and only returns true if the user has all permissions provided.

@needspermissions(['view-backend', 'view-some-content'])
    This content will only show if the authenticated user has BOTH the `view-backend` permission AND the `view-some-content` permission.
@endauth

@needspermissions([1, 2])
    This content will only show if the authenticated user has BOTH permissions with ID's of `1` AND `2`.
@endauth

Note: You can also use @else for an if/else statement.

If you want to show or hide a specific section you can do so in your layout files the same way:

@role('User')
    @section('special_content')
@endauth

@permission('can_view_this_content')
    @section('special_content')
@endauth

Note: You can add more extensions by appending to App\Providers\AccessServiceProvider@registerBladeExtensions

History Management

The history Facade is a seamless way to log any activity being done by your users, either to keep a digital trail for accountability, or to display for a little wow factor.

The API has a single function that can be called globally, where ever you need to log something:

public function log($type, $text, $entity_id = null, $icon = null, $class = null, $assets = null);

An example from the source:

history()->log(
    $this->history_slug,
    'trans("history.backend.users.created") <strong>'.$event->user->name.'</strong>'
    $event->user->id,
    'plus',
    'bg-green'
);

Lets look line by line:

history()->log(
    $this->history_slug, /* The Type, in this case it is the string 'User'. */
    'trans("history.backend.users.created") <strong>'.$event->user->name.'</strong>' /* The text of the item, you can use trans functions and they will be processed when displaying, not before being inserted into the database. */
    $event->user->id, /* The ID of the entity being logged, in this case the User with the ID of 1 */
    'plus', /* The class of the font-awesome icon being used in the row. */
    'bg-green' /* The background color of the icon. */
);

This call in the system ouputs:

screenshot

With the following database row:

screenshot

As you can see:

  • The type_id is a foreign key to the history_types table, in this case refrencing 'User'.
  • The user_id is the user that performed the action, or the logged in user.
  • The entity_id is the object that the action was performed on.
  • The text is the copy to display, again the trans will be converted when the render function displays the logs, that way they can dynamically change with the site language.
  • There are no assets in this case, scroll to assets for documentation.
  • The created/updated timestamps are when the item was logged.

This particular example is done in an event subscriber, which is a clean place to log these types of events and not to clutter up controller or repository code. But they can be placed anywhere as the history() helper is global.

Assets

There is an optional last parameter in the log function called assets. Assets are a way of linking the entities in the log text to a respective route.

For example, maybe you want John Doe in the above example to link to his respective user detail page.

Building off the above example, in order to link the assets we would do this:

history()->log(
    $this->history_slug,
    'trans("history.backend.users.created") <strong>$1</strong>'
    $event->user->id,
    'plus',
    'bg-green',
    [
        'link' => ['admin.access.user.show', $event->user->name, $event->user->id],
    ]
);

When the above code is rendered the $1 variable in the text will be converted to a link pointing to the user show method with the user's ID.

Notes:

  • You can have up to 3 routes for now, named link, link2, link3.
  • The order they come is the order of the assets.
  • As of right now only route() is supported.
  • Link syntax is [route, title, params] (Params can be an array or single id)

Here is a fictional example showing how to link 2 entities:

history()->log(
    $this->history_slug,
    'trans("history.tasks.created") <strong>$1</strong> for <strong>$2</strong>',
    $event->task->id,
    'plus',
    'bg-green',
    [
        'link' => ['admin.clients.task.show', $event->task->title, [$event->client->id, $event->task->id]],
        'link2' => ['admin.clients.show', $event->client->name, $event->client->id],
    ]
);

The above would output something such as:

Admin Istrator created task website update for Google.

Where website update is linked to the task detail page, and Google is linked to the client detail page.

Rendering

There are currently 3 types of rendering available:

Note: all items are sorted by their created_at timestamp in descending order (last first).

render($limit = null, $paginate = true, $pagination = 10)

The render() function renders all history from all entities and types in one list. This is good for a dashboard, where you want to show a history of all that's going on in the application.

See this line in the backend dashboard for reference.

Parameters:

  • $limit - The amount to rows to grab (take() method)
  • $paginate - If paginating or not
  • $pagination - Amount of items to render per page

Examples:

  • render() - Renders global history with pagination of 10 items per page
  • render(5, false) - Renders only 5 most recent history from the system with no pagination
  • render(null, true, 2) - Renders global pagination with 2 items per page

renderType($type, $limit = null, $paginate = true, $pagination = 10)

The renderType() function renders all history of a given type. This is good for the index page of a model type where you want a list of all history happening to a given type, that's not entity specific.

This method is almost identical to render() with the addition of the now first parameter $type which can be a string or history type ID.

See this line in the user index for reference.

Parameters:

  • $type (Required) - The history type, either a string or an ID
  • $limit - The amount to rows to grab (take() method)
  • $paginate - If paginating or not
  • $pagination - Amount of items to render per page

Examples:

  • renderType('User') - Render history with pagination of 10 items per page of the User type
  • renderType('User', 5, false) - Renders 5 most recent history from the User type with no pagination
  • renderType('User', null, true) - Renders regular pagination for User type with the default 10 per page

renderEntity($type, $entity_id, $limit = null, $paginate = true, $pagination = 10)

The renderEntity() function renders all history of a given entity of type. This is good for the show/detail page of a model type where you want a list of all history happening to that specific entity of that specific type.

This method is almost identical to renderType() with the addition of the now second parameter $entity_id which is the ID of the entity being shown.

See this line in the user detail page for reference.

Parameters:

  • $type (Required) - The history type, either a string or an ID
  • $entity_id (Required) - The ID of the entity of the given type being rendered
  • $limit - The amount to rows to grab (take() method)
  • $paginate - If paginating or not
  • $pagination - Amount of items to render per page

Examples:

  • renderEntity('User', 5) - Render history with pagination of 10 items per page for the User with ID of 5
  • renderEntity('User', 5, 5, false) - Renders 5 most recent history for entity with ID of 5 from the User category with no pagination
  • renderEntity('User', 5, null, true, 5) - Renders pagination with 5 items per page with the entity with id of 5 from the User category

Pagination

There is a property of the history class that lets you set the type of pagination:

private $paginationType = "simplePaginate";

You can use any Laravel pagination supported, right now it's just simplePaginate and paginate.

Editing The List

The outer markup of the lists generated can be modified in resources\views\backend\history\partials\list.blade.php.

The inner markup of the lists generated can be modified in resources\views\backend\history\partials\item.blade.php.

Search Stub

The search box on the sidebar of the backend is functional, though does not search anything. Implementing the actual search functionality is up to you.

The Search Functionality Stub includes:

  • The search form that when fired triggers the
  • Search route that leads to the
  • Search controller with a index method

If no search term is supplied, it will redirect to the dashboard with an error.

There is a $results variable in the index method that is the only thing you need to make functional.

After that the $results variable is passed to the search view and you can look through your results as you wish.

Blade Extensions

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

There are a few misc. helper classes we have written that we couldn't find a good place for, and didn't want messy controllers with so we extracted them out as Helper classes.

They are located in the app\Helpers directory.

Any helpers that were deemed useful globally throughtout the application have folders in the root, and helpers that were to specific to a section have Frontend or Backend folders like many other places throughout the file structure.

Each one serves a specific purpose. There are 3 that currently ship with a default installation.

  • Auth\Auth
  • Frontend\Auth\Socialite
  • Macros

Auth\Auth

Currently houses helper functions to deal with the "Login As" functionality of the backend portal, specifically managing sessions.


Frontend\Auth\Socialite

Houses helper methods to display the social login links based on what is set in the environment file. As well as a function that returns the currently supported socialite providers.


Macros

Houses the macro traits that are registered in the MacroServiceProvider.

Helper Globals

There is an app\helpers.php file which is autoloaded with composer that registers a few global functions for your convenience.


app_name()

{{ app_name() }} returns the config app.name value.

access()

Global function for the Access:: facade.

if (access()->allow('this-permission'))

history()

Global function for the History:: facade.

history()->log(...)

gravatar()

Global function for the Gravatar:: facade.

return gravatar()->get($user->email, ['size' => 50]);

includeRouteFiles($folder)

Loops through a folder and requires all PHP files - See Routes.

Resources

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

gulpfile.js

The gulpfile that ships with the project is well documented and pretty self-explanatory, it currently looks like:

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

require('laravel-elixir-vue-2');
require('./elixir-extensions');

/*
 |--------------------------------------------------------------------------
 | Elixir Asset Management
 |--------------------------------------------------------------------------
 |
 | Elixir provides a clean, fluent API for defining some basic Gulp tasks
 | for your Laravel application. By default, we are compiling the Sass
 | file for your application as well as publishing vendor resources.
 |
 */

elixir((mix) => {
    /**
     * Copy needed files from /node directories
     * to /public directory.
     */
    mix.copy(
        'node_modules/font-awesome/fonts',
        'public/build/fonts/font-awesome'
    )
    .copy(
        'node_modules/bootstrap-sass/assets/fonts/bootstrap',
        'public/build/fonts/bootstrap'
    )

    /**
     * Process frontend SCSS stylesheets
     */
    .sass([
        'frontend/app.scss',
        'plugin/sweetalert/sweetalert.scss'
    ], 'resources/assets/css/frontend/app.css')

    /**
     * Combine pre-processed frontend CSS files
     */
    .styles([
        'frontend/app.css'
    ], 'public/css/frontend.css')

    /**
     * Pack it up
     * Saves to a dist folder in resources, it is then combined and placed in public
     */
    .webpack('frontend/app.js', './resources/assets/js/dist/frontend.js')

    /**
     * Combine frontend scripts
     */
    .scripts([
        'dist/frontend.js',
        'plugin/sweetalert/sweetalert.min.js',
        'plugins.js'
    ], 'public/js/frontend.js')

    /**
     * Process backend SCSS stylesheets
     */
    .sass([
        'backend/app.scss',
        'plugin/toastr/toastr.scss',
        'plugin/sweetalert/sweetalert.scss'
    ], 'resources/assets/css/backend/app.css')

    /**
     * Combine pre-processed backend CSS files
     */
    .styles([
        'backend/app.css'
    ], 'public/css/backend.css')

    /**
     * Pack it up
     * Saves to a dist folder in resources, it is then combined and placed in public
     */
    .webpack('backend/app.js', './resources/assets/js/dist/backend.js')

    /**
     * Make RTL (Right To Left) CSS stylesheet for the backend
     */
    .rtlCss()

    /**
     * Combine backend scripts
     */
    .scripts([
        'dist/backend.js',
        'plugin/sweetalert/sweetalert.min.js',
        'plugin/toastr/toastr.min.js',
        'plugins.js',
        'backend/custom.js'
    ], 'public/js/backend.js')

    /**
     * Combine pre-processed rtl CSS files
     */
    .styles([
        'rtl/bootstrap-rtl.css'
    ], 'public/css/rtl.css')

    /**
     * Apply version control
     */
    .version([
        "public/css/frontend.css",
        "public/js/frontend.js",
        "public/css/backend.css",
        "public/css/backend-rtl.css",
        "public/js/backend.js",
        "public/css/rtl.css"
    ])

    /**
     * Run tests
     */
    .phpUnit();
});

Assets

CSS

This folder holds the compiled sass files speficied in the gulpfile before they are versioned and made public. You should not edit the frontend/backend folders as anytime you recompile your assets they will be overwritten.

Note: The rtl folder is a static css file and not compiled from sass.

Note: The generated backend-rtl.css in public/css is a file is created from the rtlCss() function in gulpfile.js that is an Elixir extension imported from ./elixir-extensions.js.

JS

  • /backend - Contains the backend specific (Admin LTE) javascripts to be compiled.
  • /components - Contains the Vue components for backend and frontend.
  • /dist - Contains the compiled javascripts ready to be versioned.
  • /frontend - Contains the frontend specific javascripts to be compiled.
  • /plugin - Contains any javscript plugins that would be benificial globally.
  • bootstrap.js - The Laravel javascript bootstrap file.
  • plugins.js - Contains useful plugins and snippets that would be benificial globally.

Sass

  • /backend - Contains backend .scss files (Admin LTE) ready to be compiled into css.
  • /frontend - Contains frontend .scss files ready to be compiled into css.
  • /plugin - Contains plugin .scss files associated with the plugin folder in the js directory.
  • _helpers.scss - A place to put helper scss methods that can be used globally.

Lang

See Localization.

Views

  • /backend - The backend blade files.
  • /errors - The error code 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

The test suite is currently comprised of over 100 tests and 500 assertions to test all of the sections in this documentation.

We will not go into detail here since they change so often, but with a fresh installation everything should pass if you have the right configurations.

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.access.user.index')->withFlashSuccess(trans('alerts.backend.users.created'));

or:

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

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

  • withFlashDanger
  • withUsers

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.access.user.index')->with('flash_danger', trans('alerts.backend.users.created'));

or:

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

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

Action Buttons

Anywhere throughout the system that there is a table that allows CRUD operations, with associated buttons, the buttons (called action buttons) are held within a trait on the model.

We use this approach to make it easy to output buttons for each resource without having duplicate markup in multiple places, plus it takes care of the entity ID's and other stuff, such as converting all delete buttons for links that trigger a form instead of a form themselves. (This uses a method included in the global javascript plugin file.)

For example the buttons to the right of the users datatable in the backend, are generated in >this trait.

public function getShowButtonAttribute()
{
    return '<a href="' . route('admin.access.user.show', $this) . '" class="btn btn-xs btn-info"><i class="fa fa-search" data-toggle="tooltip" data-placement="top" title="' . trans('buttons.general.crud.view') . '"></i></a> ';
}

This outputs the show button for each user row in the table with their respective ID and looks like this .

On interesting button to look at is the delete button:

public function getDeleteButtonAttribute()
{
    if ($this->id != access()->id()) {
        return '<a href="' . route('admin.access.user.destroy', $this) . '"
             data-method="delete"
             data-trans-button-cancel="' . trans('buttons.general.cancel') . '"
             data-trans-button-confirm="' . trans('buttons.general.crud.delete') . '"
             data-trans-title="' . trans('strings.backend.general.are_you_sure') . '"
             class="btn btn-xs btn-danger"><i class="fa fa-trash" data-toggle="tooltip" data-placement="top" title="' . trans('buttons.general.crud.delete') . '"></i></a> ';
    }

    return '';
}

Ignoring the if statement for this example, you can see that the delete button is actually a link. But that's insecure, isn't it? Actually no, take note of the data-method="delete" property of the link.

If you look at the source of the delete button you will see this:

<a data-method="delete" data-trans-button-cancel="Cancel" data-trans-button-confirm="Delete" data-trans-title="Are you sure?" class="btn btn-xs btn-danger" style="cursor:pointer;" onclick="$(this).find("form").submit();"><i class="fa fa-trash" data-toggle="tooltip" data-placement="top" title="" data-original-title="Delete"></i>
<form action="http://l5boilerplate.dev/admin/access/user/1" method="POST" name="delete_item" style="display:none">
   <input type="hidden" name="_method" value="delete">
   <input type="hidden" name="_token" value="YOUR_CSRF_TOKEN">
</form>
</a>

Because of this function, all data-method="delete" property get a form injected inside them that calls a DELETE route, it also takes care of popping up an "are you sure you want to delete?" alert box before resuming.

Note: The other data-* methods on the link take care of injecting the appropriate language into the sweetalert alert box that pops up which looks like this:

screenshot

Finally each attribute trait has a last method called getActionButtonsAttribute() which concatinates all of the button methods together and displays all of the buttons at once like:

$user->action_buttons

displays:

Note: The getActionButtonsAttribute() method is another example of Laravel magic.

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.


BitBucket

You must set permissions of your OAuth consumer to at least Account: Read and Repositories: Read

Also: In order for this option to work, email must be nullable on the users table, as well as the unique email table key removed. Do this at your own risk. There is no other option I know of for now.


GitHub

No known quirks, should work as is.


Google

If you are getting an Access Not Configured error message:

Activate the Google+ API from the Google Developers Console.


Facebook

For the Given URL is not allowed by the Application error message:

  1. Go to basic settings for your app
  2. Select Add Platform
  3. Select Website
  4. Put URL in the Site URL

Linked In

r_basicprofile and r_emailaddress must be selected in Default Application Permissions.

The callback URL must be submitted under the OAuth 2.0 section.


Twitter

For Twitter to grab the user's email address for you application, it must be whitelisted as explained here: https://dev.twitter.com/rest/reference/get/account/verify_credentials


Other

If you are getting a cURL error 60 on localhost, follow these directions.

Troubleshooting

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

Delete the composer.lock file

Run the dumpautoload command

$ composer dumpautoload -o

If the above fails to fix, and the command line is referencing errors in compiled.php, do the following:

Delete the storage/framework/compiled.php file

If all of the above don't work please report here.

Deployment

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


gulp --production

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


Config Caching php artisan config:cache

Caches all of your configuration files into a single file.


Route Caching php artisan route:cache

If your application is exclusively using controller based routes, you should take advantage of Laravel's route cache. Using the route cache will drastically decrease the amount of time it takes to register all of your application's routes. In some cases, your route registration may even be up to 100x faster!