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');
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 and Prefix To Group Of Routes
Route::group(['prefix' => 'admin', 'middleware' => \App\Http\Middleware\MiddlewareLoggedInAsAdminAuth::class], function(){
// admin view for viewing posts in the system
Route::get('articles', function () {
$controller = new \App\Http\Controllers\ArticleController();
return $controller->showCreateArticlePage();
});
// admin view for editing/viewing a particular post
Route::get('articles/{uuid}', function ($uuid) {
$controller = new \App\Http\Controllers\ArticleController();
return $controller->editArticle($uuid);
});
});
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,
];
$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$|'
]);
|
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=
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
andpassword_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
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, ['accountUuid' => $accountUuid]);
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 someColumnName = :someValue";
return Score::hydrate(\DB::select($query, ['someValue' => $someValue]))->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
.env
file with your database connection details.
Database Queries
Where Clauses
The following queries all do the same thing:
User::where('email', $email)->exists();
User::where('email', '=', $email)->exists();
When you need to perform multiple where clauses:
$exists = MyModel::where('user_id', '=', $userId)
->where('course_id', '=', $courseId)->exists();
// My preferred way
$whereClauses = [
['user_id', '=', $userId],
['course_id', '=', $courseId],
];
$exists = MyModel::where($whereClauses)->exists();
The above examples are all for AND
clauses. If you need an OR
relationship then you need to use orWhere
. E.g.
$exists = MyModel::where('user_id', '=', $userId)
->orWhere('course_id', '=', $courseId)->exists();
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");
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);
Inserting Data
This is one way you could insert multiple rows into a database at once.
DB::table('users')->insert([
['email' => 'taylor@example.com', 'votes' => 0],
['email' => 'dayle@example.com', 'votes' => 0]
]);
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 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',
];
Also be sure to set (otherwise it will be cast to integer after calling save()
):
public $incrementing = false;
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,
);
}
Make Hidden Data Visible
Sometimes you need to return data in a JSON response that is usually hidden (and thus in the $hidden member variable of a model). This might include a user's email or password etc.
To make this data get returned, make use of the makeVisible()
method. Refer here.
Re-Inserting Models
Laravel models have an exists
attribute. If you have a model in your variables, you can re-save it to your database after deletion if you change it's exists
value to false.
For example:
$id='4b25d6bc-3225-4c19-8b63-4b2e981d550c';
$account = Account::findOrFail($id);
$accountDupe = Account::findOrFail($id);
$account->delete();
$accountDupe->exists = false;
$accountDupe->save();
Mass Inserting
If you have created a bunch of model objects that have not yet been inserted into the database, you can do them in one go like so:
$insertRows = array();
foreach ($models as $myModel)
{
$insertRows[] = $myModel->attributesToArray();
}
\App\MyModel::insert($insertRows);
Views
Error Bags
When working with Blade templates you may come across using $errors like so:
@if ($errors->has('email'))
<span class="help-block">
<strong>{{ $errors->first('email') }}</strong>
</span>
@endif
The $errors
will be an instance of the \Illuminate\Support\MessageBag
or the \Illuminate\Support\ViewErrorBag
classes.
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 bemale
,female
, orother
(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.
}
uuid
was apparently added in Laravel 5.7.
Required But Nullable
If you require a field to be sent across, but null
is a valid value for it, Laravel can get a bit "confused"
and won't work as you might expect if you were to use: required|nullable
, instead, just use present
. E.g.
$validationRules = [
'id' => 'uuid',
'name' => 'required|max:255',
'date_of_birth' => 'required',
'date_of_death' => 'present', // Required, but should be null if not dead yet.
];
Further Resources
Here is a link to all of the available validation rules.
Using Regular Expressions
Here is an example rule that uses a regular expression to validate that the id
field is provided, and consists of 9 numbers (possibly staring with 0).
$validationRules = [
'id' => ['required','regex:/^[0-9]{9}$/'],
];
Handling Forms
Redirect Back With Errors
If you had a form that was posting somewhere else, but there were validation failures etc, then you can just redirect back with the inputs that were provided and the error messages like so:
catch (\App\Exceptions\SomeCustomException $e)
{
$response = \redirect()->back()->withInput()->withErrors(["Something custom I checked for went wrong"]);
}
If Laravel validation failed, then you can use the validator's message bag:
catch (\App\Exceptions\ExceptionLaravelValidationFailed $validationFailedException)
{
$response = \redirect()->back()->withInput()->withErrors($validationFailedException->getValidator()->getMessageBag());
}
HTML To Show Errors
If you add snippets like this below your form input fields:
@if ($errors->has('form_input_name'))
<span class="help-block">
<strong>{{ $errors->first('form_input_name') }}</strong>
</span>
@endif
... then your errors from laravel validation failing will show up by the input fields they relate to.
Paginators
If you are working with a paginated resource, you may find it easier if you typehint it like so:
/* @var $paginatedAssets \Illuminate\Pagination\AbstractPaginator */
Creating Paginated Object
Sometimes you may have an array of objects that you need to paginate.
For this, you may find the LengthAwarePaginator
useful.
$paginatedAssets = new \Illuminate\Pagination\LengthAwarePaginator(
$assets, // array of asset objects
count($assets),
10,
null,
['path' => '/assets']
);
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:
- Laravel 5: Handle exceptions when request wants JSON.
- Stack Overflow - Show a 404 page if route not found in Laravel 5.1
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',
],
];
Generate UUID
\Illuminate\Support\Str::orderedUuid()->toString();
CRSF - 419 Expired
If you get a 419 "Page Expired" message after posting a form, make sure you have a hidden input field with the CRSF token like so:
<input type="hidden" name="_token" id="csrf-token" value="<?= csrf_token(); ?>">
Debugging / Gotchas
Visibility
I had a table that had a column for visible
that was true/false for whether the item should be visible or not.
Unfortunately visibility is also something built into Laravel, to do with whether an attribute should be returned in a JSON response.
This causes random issues, especially when I created the getVisible()
accessor, which was overriding Laravel's getVisible()
method.
The solution here was to change to is_visible
and change the accessor to getIsVisible()
.
References
- Stack Overflow - Disable Laravel's Eloquent Timestamps
- Stack Overflow - Laravel timestamp saving time as unix timestamp
- Stack Overflow - Laravel Checking If a Record Exists
- Stack Overflow - Why does Eloquent (in Laravel) cast my varchar uuid primary key to an integer resulting in errors?
- Laravel Cheatsheet
- tutorialspoint.com - Laravel - Session
- Laravel-news.com - Laravel firstOrFail forTheWin
- Stack Overflow - Disable rate limiter in Laravel?
- Laravel 5.8 Docs
- Stack Overflow - Apply Middleware to all routes except
setup/*
in Laravel 5.4 - Stack Overflow - Laravel 5.4 - Validation with Regex [duplicate]
- Stack Overflow - Laravel UUID generation
- Stack Overflow - Bulk Insertion in Laravel using eloquent ORM
- Stack Overflow - laravel select where and where condition
- Stack Overflow - How to Create Multiple Where Clause Query Using Laravel Eloquent?
- Laracasts - Paginate Eloquent collection
- ITSolutionStuff.com - Laravel redirect back with input and error message example
First published: 26th August 2018