Programster's Blog

Tutorials focusing on Linux, programming, and open-source

Laravel 5 Cheatsheet

Create New Project

mkdir my-project-name
cd my-project-name
composer create-project laravel/laravel . --prefer-dist

To make sure you have the necessary packages on Ubuntu 16.04, you may need to run:

sudo apt update && \
sudo apt install -y php7.0-mbstring php7.0-xml

Running Locally

You can test locally by running:

sudo php -S localhost:80 public/index.php

Then just go to localhost in your browser.

Routing

There are several routing files under the top level routes directory:

  • api.php - routes for an API.
  • channels.php - for websockets and broadcasting.
  • console.php - routes for if you are building a CLI application.
  • web.php - routes for a traditional web application that isn't an API.

There are two main ways to define a route. The first is by actually passing a callback that gets executed.

Route::get('/', function () {
    return view('welcome');
});

The alternative "shorthand" is to pass a string that represents the controller and method to execute.

Route::get('/my/web/path', 'MyController@method');

Official routing docs.

Patterns

If you have routes with variables in the path, then you can use something like the following to pick them up.

Route::pattern('uuid', '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}');

Route::get('users/{uuid}', function ($uuid) { 
    $userController = new App\Http\Controllers\UserController(); 
    return $userController->getUser($uuid);
});

Middleware

Apply Middleware To Specific Route

Route::get('', function () { 
    $controller = new \App\Http\Controllers\AccountController(); 
    return $controller->getAccounts();
})->middleware(\App\Http\Middleware\MiddlewareApiAuth::class);

... or if you want to run multiple middlware:

Route::get('', function () { 
    $controller = new \App\Http\Controllers\AccountController(); 
    return $controller->getAccounts();
})->middleware([\App\Http\Middleware\MiddlewareClass1::class, \App\Http\Middleware\MiddlewareClass2::class]);

Apply Middleware To Group Of Routes

Route::middleware([\App\Http\Middleware\MiddlewareClass1::class, \App\Http\Middleware\MiddlewareClass2::class])->group(function () {
    Route::get('/', function () {
        //...
    });

    Route::get('user/profile', function () {
        // ...
    });
});

Apply Middleware To All Routes In Controller Except Specific Ones

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
    public function __construct(){
        $this->middleware('auth',['except' => ['login','setup','setupSomethingElse']]);
    }
}

Apply Middleware To Every Route

Open your app/Http/Kernel.php file and add your middleware to the $middleware property of the class like so:

protected $middleware = [
    \App\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
    \App\Http\Middleware\TrimStrings::class,
    \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    \App\Http\Middleware\TrustProxies::class,
    Middleware\MyMiddlewareClass::class,
];

If you apply middleware this way, you cannot rely on getting the route name for an "exceptions list", as the route will not have been evaluated yet. E.g. $request->route() will return null from within the middleware. See here for more info.

Apply Middleware To All Routes Except Those In Exception List

Unfortunately, there is not a built-in "Laravel way" to do this right now. I found that the solution for me was to apply the middleware to every route as shown above, and then add logic to the middleware class'es body like so:

public function handle($request, Closure $next)
{
    $authPassed = false;
    $path = $_SERVER['REQUEST_URI'];

    foreach (PATHS_WHERE_HEADERS_NOT_POSSIBLE as $pattern)
    {
        $match = preg_match($pattern, $path);

        if ($match === 1)
        {
            $authPassed = true;
        }
    }

    // ... other checks here if authPassed is false

    if ($authPassed === true)
    {
        $response = $next($request);
    }

    return $response;
}

I defined my exception paths like so:

$uuidRegexp = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}';

define('PATHS_WHERE_HEADERS_NOT_POSSIBLE', [
    '|^' . $uuidRegexp . '/resource/my-action$|'
]);

I am using | character to represent the start and end of the regular expression because I needed to use / in the path. However, if you need | for a regexp OR, then you will need to use something else.

Artisan CLI Tool

Create A Controller

php artisan make:controller MyController

Create a RESTful Resource Controller

php artisan make:controller MyController --resource

Create Model

php artisan make:model MyModel

Create Exception

php artisan make:exception NameOfMyExceptionClass

Create Middleware

php artisan make:middleware MyMiddlewareClass

Create Seeder

php artisan make:seeder MyTableSeeder

Run Migrations

php artisan migrate

Reset Database Migrations

If you wish to create your database from scratch again, you can use:

php artisan migrate:refresh

Create A new App Key

php artisan key:generate --show

This will output something like:

base64:9J5F/51QKxxhAxbHvmvJtLIoop6ywOywiev9hWF3wLY=

You need to include the whole thing as the value. E.g. including the base64: part.

Database Migrations

  • Database migrations can be found under database/migrations.
  • By default, you will have two migrations in there that create the users and password_resets tables.
  • Migration filenames are prefixed with the year, month, day, and time in order for the migration tool to know which order to run the migrations.
    • Using the date/time rather than an incrementing integer should hopefully reduce the likelihood of an issue arising when merging two different branches that each created a migration script.

Create A Migration

The following command will create a migration script in your migrations table for creating a table called x.

php artisan make:migration create_x_table

If you don't use create_tableName_table naming convention, then you won't get the starting code within the up/down methods for creating/destroying the table.

Using Raw SQL

If you need use raw SQL, you can just use DB::statement() like below:

public function up()
{
    $createTableQuery = 
        "CREATE TABLE `broker` (
            `id` int unsigned NOT NULL AUTO_INCREMENT,
            `name` varchar(255) NOT NULL,
            `redirect_url` text NOT NULL,
            `secret` varchar(30) NOT NULL,
            `modified_timestamp` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
            PRIMARY KEY (`id`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8";

    DB::statement($createTableQuery);
}

If you need to get data out of the database though, you will need to use something like below:

$query = 
    "SELECT * FROM score_history WHERE user_id IN (" . 
        "SELECT uuid FROM users WHERE account_id='$accountUuid'" . 
    ")";

$result = \DB::select($query);

The trouble with using \DB::select() is that you will be returned an array of stdClass objects instead of objects of your model. In order to get an array of your model objects, you would need to use the hydrate method like:

$query = "SELECT * FROM scores WHERE ....";
return Score::hydrate(\DB::select($query))->all();

Laravel Style Migration Example

The following example will create a table that links sections to user-groups and will automatically delete rows in the table when either user-groups or content items are deleted.

public function up()
{
    Schema::create('content_user_group', function (Blueprint $table) {
        $table->increments('id');
        $table->integer('content_id')->unsigned()->nullable(false);
        $table->integer('user_group_id')->unsigned()->nullable(false);
        $table->foreign('content_id')->references('id')->on('content')->onDelete('cascade');
        $table->foreign('user_group_id')->references('id')->on('user_group')->onDelete('cascade');
        $table->timestamps();
    });
}

Run Migrations

You can execute migrations with:

php artisan migrate

If you're just getting started, don't forget to edit the .env file with your database connection details.

Database Queries

Checking Uniqueness

Often you want to check if a record already exists before attempting an insert. For example, when adding a new user, you wish to check that a user with the same email doesn't already exist. For this, do the following:

if (User::where('email', '=', $email)->exists()) 
{
    throw new Exception("A user with that email already exists.");
}

Loading By Unique Value

If you have a unique attribute on your table and you want to load based on that, you can put something like this in your model:

/**
 * Fetch an account from the email address
 * @param string $email - the email of the account we wish to fetch.
 * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
 */
public static function loadFromEmail(string $email) : Account
{
    return Account::where('email', '=', $email)->firstOrFail(); 
}

The beauty of this is that you no longer need to use type hinting comments as you have a return type on your function.

Pluck

You can use ->pluck() to just get the values of one column. For example, the code below will get you a collection object of all the titles of posts where the category_id is 3.

$postTitles = DB::table("posts")
    ->where("category_id", 3)
    ->pluck("title");

The specific collection object is one of Illuminate\Support\Collection

Converting Collection To Array

Queries will return Laravel collections. However, you often want the array form instead. You can do this by executing the ->all() method on the collection. The example below will get an array of all the post titles.

$postTitles = DB::table("posts")
    ->where("category_id", 3)
    ->pluck("title")
    ->all();

Raw Select

Sometimes you need to do a complicated select query and it may be easier just to write it raw. Here is an example:

$rawSelectQuery = 
    "SELECT `id` FROM `myTable`" . 
    " WHERE `type_id`={$myTypeObject->id}" . 
    " AND `id` in (" . 
        "SELECT `some_id` FROM `table2` WHERE `someColumnName`={$someValue}" . 
    ")" . 
    " OR `id` in (SELECT `some_id` FROM `global_items`)";

$results = \DB::select($rawSelectQuery);

Models

Manually Specifying Table Name

If Laravel can't correctly guess your table name from your model's name, then you may need to manually specify it by setting the $table attribute.

protected $table = 'my_table_name';

Using UUID Primary Keys

If you are using PostgreSQL UUIDs as your primary key, be sure to add the following to your models:

protected $primaryKey = 'uuid';

Also, be sure to add the uuid to your model's $casts attribute so it remains a string rather than trying to be casted to an integer.

protected $casts = [
    'uuid' => 'string',
];

Soft Deletes

To make use of Laravel's soft-delete functionality, just make sure to add the \Illuminate\Database\Eloquent\SoftDeletes trait like so:

class User extends Authenticatable
{
    use \Illuminate\Database\Eloquent\SoftDeletes;

Then make sure to have the deleted_at column in the relevant table, allowing null values. If you set Laravel to using unix timestamps (below), then it needs to be an integer type.

Unix Timestamps for created_at and updated_at

If you would rather not use the SQL timestamp type, and prefer to work with unix timestamp integers, you can get Laravel to do this by editing your model and adding:

public function getDateFormat() { return 'U'; }

Disabling Timestamps

By default, Laravel models are expecting the tables to have the created_at and updated_at columns. If you don't want to have these, then add the following to your model:

public $timestamps = false;

API - Returning Null Attributes

If you are building an API and you have a model whose attributes may have a null value, then those attributes will not be returned in requests by default. If you would prefer to get the attribute back in your JSON, but with a value of null, then you need to override the toArray() method like so:

public function toArray() : array
{
    return array(
        'uuid' => $this->uuid,
        'name' => $this->name,
        'email' => $this->email,
        'status' => $this->status,
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
        'deleted_at' => $this->deleted_at,
    );
}

Views

@todo

Controllers

@todo

Validation

Here is a snippet example of something that would go in a controller. It will verify that:

  • if an id is supplied, it is a UUID, but it doesn't have to be provided.
  • a name is required and must be a string no longer than 255 characters
  • a gender string is required and must be male, female, or other (enum in the database).
$validationRules = [
    'id' => 'uuid',
    'name' => 'required|max:255',
    'gender' => 'required|in:male,female,other',
];

$data = request()->all();
$validator = \Validator::make($data, $validationRules);

if ($validator->fails())
{
    $messageBag = $validator->getMessageBag();
    // do something with it here.
}

Validation support for uuid was apparently added in Laravel 5.7.

Here is a link to all of the available validation rules.

Sessions

Store Session Data

$request->session()->put('key', 'value');

Retrieve Session Data

$value = $request->session()->get('key');

Delete

$request->session()->forget('key');

Testing

Run testing with this command from inside your application where the vendor directory is under.

vendor/bin/phpunit

Filenames

Remember that all test filenames need to end with Test. E.g. MyTest.php, and not TestUsers.php.

Misc

Custom Exception And Missing Route Handling

If you're building an API and you want unhandled exceptions/errors to be returned in JSON, or want to do something custom for when routes are not found (404 pages), then edit the app/Exceptions/Handler.php file according to these Stack Overflow posts:

Generate Random String

$randomString = \Illuminate\Support\Str::random($length=16)

Disable Throttling

If you are building an API that is going to hit more than 60 requests per second, you will want to remove or increase the default rate-limiting of requests. You can do this by finding the following text in the /app/Http/Kernel.php file and commenting out or increasting the throttle line.

        'api' => [
            'throttle:60,1',
            'bindings',
        ],
    ];

References

Last updated: 28th June 2019
First published: 26th August 2018