⬆️ Go to main menu ⬅️ Previous (API)
- Localhost in .env
- When (NOT) to run "composer update"
- Composer: check for newer versions
- Auto-Capitalize Translations
- Carbon with Only Hours
- Single Action Controllers
- Redirect to Specific Controller Method
- Use Older Laravel Version
- Add Parameters to Pagination Links
- Repeatable Callback Functions
- Request: has any
- Simple Pagination
- Data Get Function
- Blade directive to add true/false conditions
- Jobs can be used without queues
- Use faker outside factories or seeders
- Schedule things
- Search Laravel docs
- Filter route:list
- Blade directive for not repeating yourself
- Artisan commands help
- Disable lazy loading when running your tests
- Using two amazing helpers in Laravel will bring magic results
- Request parameter default value
- Pass middleware directly into the route without register it
- Transforming an array to CssClasses
- "upcomingInvoice" method in Laravel Cashier (Stripe)
- Laravel Request exists() vs has()
- There are multiple ways to return a view with variables
- Schedule regular shell commands
- HTTP client request without verifying
- Test that doesn't assert anything
- "Str::mask()" method
- Extending Laravel classes
- Can feature
- Temporary download URLs
- Dealing with deeply-nested arrays
- Customize how your exceptions are rendered
- The tap helper
- Reset all of the remaining time units
- Scheduled commands in the console kernel can automatically email their output if something goes wrong
- Be careful when constructing your custom filtered queries using GET parameters
- Dust out your bloated route file
- You can send e-mails to a custom log file
- Markdown made easy
- Simplify if on a request with whenFilled() helper
- Pass arguments to middleware
- Get value from session and forget
- $request->date() method
- Use through instead of map when using pagination
- Quickly add a bearer token to HTTP request
- [Copy file or all files from a folder](#copy-file-or-all-files-from a-folder)
- Sessions has() vs exists() vs missing()
- Test that you are passing the correct data to a view
- Use Redis to track page views
Don't forget to change APP_URL
in your .env
file from http://localhost
to the real URL, cause it will be the basis for any links in your email notifications and elsewhere.
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:9PHz3TL5C4YrdV6Gg/Xkkmx9btaE93j7rQTUZWm2MqU=
APP_DEBUG=true
APP_URL=http://localhost
Not so much about Laravel, but... Never run composer update
on production live server, it's slow and will "break" repository. Always run composer update
locally on your computer, commit new composer.lock
to the repository, and run composer install
on the live server.
If you want to find out which of your composer.json
packages have released newer versions, just run composer outdated
. You will get a full list with all information, like this below.
phpdocumentor/type-resolver 0.4.0 0.7.1
phpunit/php-code-coverage 6.1.4 7.0.3 Library that provides collection, processing, and rende...
phpunit/phpunit 7.5.9 8.1.3 The PHP Unit Testing framework.
ralouphie/getallheaders 2.0.5 3.0.3 A polyfill for getallheaders.
sebastian/global-state 2.0.0 3.0.0 Snapshotting of global state
In translation files (resources/lang
), you can specify variables not only as :variable
, but also capitalized as :VARIABLE
or :Variable
- and then whatever value you pass - will be also capitalized automatically.
// resources/lang/en/messages.php
'welcome' => 'Welcome, :Name'
// Result: "Welcome, Taylor"
echo __('messages.welcome', ['name' => 'taylor']);
If you want to have a current date without seconds and/or minutes, use Carbon's methods like setSeconds(0)
or setMinutes(0)
.
// 2020-04-20 08:12:34
echo now();
// 2020-04-20 08:12:00
echo now()->setSeconds(0);
// 2020-04-20 08:00:00
echo now()->setSeconds(0)->setMinutes(0);
// Another way - even shorter
echo now()->startOfHour();
If you want to create a controller with just one action, you can use __invoke()
method and even create "invokable" controller.
Route:
Route::get('user/{id}', 'ShowProfile');
Artisan:
php artisan make:controller ShowProfile --invokable
Controller:
class ShowProfile extends Controller
{
public function __invoke($id)
{
return view('user.profile', [
'user' => User::findOrFail($id)
]);
}
}
You can redirect()
not only to URL or specific route, but to a specific Controller's specific method, and even pass the parameters. Use this:
return redirect()->action('SomeController@method', ['param' => $value]);
If you want to use OLDER version instead of the newest Laravel, use this command:
composer create-project --prefer-dist laravel/laravel project "7.*"
Change 7.* to whichever version you want.
In default Pagination links, you can pass additional parameters, preserve the original query string, or even point to a specific #xxxxx
anchor.
{{ $users->appends(['sort' => 'votes'])->links() }}
{{ $users->withQueryString()->links() }}
{{ $users->fragment('foo')->links() }}
If you have a callback function that you need to re-use multiple times, you can assign it to a variable, and then re-use.
$userCondition = function ($query) {
$query->where('user_id', auth()->id());
};
// Get articles that have comments from this user
// And return only those comments from this user
$articles = Article::with(['comments' => $userCondition])
->whereHas('comments', $userCondition)
->get();
You can check not only one parameter with $request->has()
method, but also check for multiple parameters present, with $request->hasAny()
:
public function store(Request $request)
{
if ($request->hasAny(['api_key', 'token'])) {
echo 'We have API key passed';
} else {
echo 'No authorization parameter';
}
}
In pagination, if you want to have just "Previous/next" links instead of all the page numbers (and have fewer DB queries because of that), just change paginate()
to simplePaginate()
:
// Instead of
$users = User::paginate(10);
// You can do this
$users = User::simplePaginate(10);
If you have an array complex data structure, for example a nested array with objects. You can use data_get()
helper function retrieves a value from a nested array or object using "dot" notation and wildcard:
// We have an array
[
0 =>
['user_id' =>'some user id', 'created_at' => 'some timestamp', 'product' => {object Product}, etc],
1 =>
['user_id' =>'some user id', 'created_at' => 'some timestamp', 'product' => {object Product}, etc],
2 => etc
]
// Now we want to get all products ids. We can do like this:
data_get($yourArray, '*.product.id');
// Now we have all products ids [1, 2, 3, 4, 5, etc...]
New in Laravel 8.51: @class
Blade directive to add true/false conditions on whether some CSS class should be added. Read more in docs
Before:
<div class="@if ($active) underline @endif">`
Now:
<div @class(['underline' => $active])>
@php
$isActive = false;
$hasError = true;
@endphp
<span @class([
'p-4',
'font-bold' => $isActive,
'text-gray-500' => ! $isActive,
'bg-red' => $hasError,
])></span>
<span class="p-4 text-gray-500 bg-red"></span>
Tip given by @Teacoders
Jobs are discussed in the "Queues" section of the docs, but you can use Jobs without queues, just as classes to delegate tasks to.
Just call $this->dispatchNow()
from Controllers
public function approve(Article $article)
{
//
$this->dispatchNow(new ApproveArticle($article));
//
}
If you want to generate some fake data, you can use Faker even outside factories or seeds, in any class.
Keep in mind: to use it in production, you need to move faker from "require-dev"
to "require"
in composer.json
use Faker;
class WhateverController extends Controller
{
public function whatever_method()
{
$faker = Faker\Factory::create();
$address = $faker->streetAddress;
}
}
You can schedule things to run daily/hourly in a lot of different structures.
You can schedule an artisan command, a Job class, an invokable class, a callback function, and even execute a shell script.
use App\Jobs\Heartbeat;
$schedule->job(new Heartbeat)->everyFiveMinutes();
$schedule->exec('node /home/forge/script.js')->daily();
use App\Console\Commands\SendEmailsCommand;
$schedule->command('emails:send Taylor --force')->daily();
$schedule->command(SendEmailsCommand::class, ['Taylor', '--force'])->daily();
protected function schedule(Schedule $schedule)
{
$schedule->call(function () {
DB::table('recent_users')->delete();
})->daily();
}
If you want to search Laravel Docs for some keyword, by default it gives you only the TOP 5 results. Maybe there are more?
If you want to see ALL results, you may go to the Github Laravel docs repository and search there directly. https://github.com/laravel/docs
New in Laravel 8.34: php artisan route:list
gets additional flag --except-path
, so you would filter out the routes you don't want to see. [See original PR](New in Laravel 8.34: php artisan route:list
gets additional flag --except-path
, so you would filter out the routes you don't want to see. See original PR
If you keep doing the same formatting of the data in multiple Blade files, you may create your own Blade directive.
Here's an example of money amount formatting using the method from Laravel Cashier.
"require": {
"laravel/cashier": "^12.9",
}
public function boot()
{
Blade::directive('money', function ($expression) {
return "<?php echo Laravel\Cashier\Cashier::formatAmount($expression, config('cashier.currency')); ?>";
});
}
<div>Price: @money($book->price)</div>
@if($book->discount_price)
<div>Discounted price: @money($book->dicount_price)</div>
@endif
If you are not sure about the parameters of some Artisan command, or you want to know what parameters are available, just type php artisan help [a command you want]
.
If you don't want to prevent lazy loading when running your tests you can disable it
Model::preventLazyLoading(!$this->app->isProduction() && !$this->app->runningUnitTests());
Tip given by @djgeisi
Using two amazing helpers in Laravel will bring magic results...
In this case, the service will be called and retried (retry). If it stills failing, it will be reported, but the request won't fail (rescue)
rescue(function () {
retry(5, function () {
$this->service->callSomething();
}, 200);
});
Tip given by @JuanDMeGon
Here we are checking if there is a per_page (or any other parameter) value then we will use it, otherwise, we will use a default one.
// Isteand of this
$perPage = request()->per_page ? request()->per_page : 20;
// You can do this
$perPage = request('per_page', 20);
Tip given by @devThaer
Route::get('posts', PostController::class)
->middleware(['auth', CustomMiddleware::class])
Tip given by @sky_0xs
use Illuminate\Support\Arr;
$array = ['p-4', 'font-bold' => $isActive, 'bg-red' => $hasError];
$isActive = false;
$hasError = true;
$classes = Arr::toCssClasses($array);
/*
* 'p-4 bg-red'
*/
Tip given by @dietsedev
You can show how much a customer will pay in the next billing cycle.
There is a "upcomingInvoice" method in Laravel Cashier (Stripe) to get the upcoming invoice details.
Route::get('/profile/invoices', function (Request $request) {
return view('/profile/invoices', [
'upcomingInvoice' => $request->user()->upcomingInvoice(),
'invoices' => $request-user()->invoices(),
]);
});
Tip given by @oliverds_
// https://example.com?popular
$request->exists('popular') // true
$request->has('popular') // false
// https://example.com?popular=foo
$request->exists('popular') // true
$request->has('popular') // true
Tip given by @coderahuljat
// First way ->with()
return view('index')
->with('projects', $projects)
->with('tasks', $tasks)
// Second way - as an array
return view('index', [
'projects' => $projects,
'tasks' => $tasks
]);
// Third way - the same as second, but with variable
$data = [
'projects' => $projects,
'tasks' => $tasks
];
return view('index', $data);
// Fourth way - the shortest - compact()
return view('index', compact('projects', 'tasks'));
We can schedule regular shell commands within Laravel scheduled command
// app/Console/Kernel.php
class Kernel extends ConsoleKernel
{
protected function shedule(Schedule $shedule)
{
$shedule->exec('node /home/forge/script.js')->daily();
}
}
Tip given by @anwar_nairi
Sometimes, you may want to send HTTP request without verifying SSL in your local environment, you can do like so:
return Http::withoutVerifying()->post('https://example.com');
If you want to set multiple options, you can use withOptions
.
return Http::withOptions([
'verify' => false,
'allow_redirects' => true
])->post('https://example.com');
Tip given by @raditzfarhan
Test that doesn't assert anything, just launch something which may or may not throw an exception
class MigrationsTest extends TestCase
{
public function test_successful_foreign_key_in_migrations()
{
// We just test if the migrations succeeds or throws an exception
$this->expectNotToPerformAssertions();
Artisan::call('migrate:fresh', ['--path' => '/databse/migrations/task1']);
}
}
Laravel 8.69 released with "Str::mask()" method which masks a portion of string with a repeated character
class PasswordResetLinkController extends Controller
{
public function sendResetLinkResponse(Request $request)
{
$userEmail = User::where('email', $request->email)->value('email'); // username@domain.com
$maskedEmail = Str::mask($userEmail, '*', 4); // user***************
// If needed, you provide a negative number as the third argument to the mask method,
// which will instruct the method to begin masking at the given distance from the end of the string
$maskedEmail = Str::mask($userEmail, '*', -16, 6); // use******domain.com
}
}
Tip given by @Teacoders
There is a method called macro on a lot of built-in Laravel classes. For example Collection, Str, Arr, Request, Cache, File, and so on.
You can define your own methods on these classes like this:
Str::macro('lowerSnake', function (string $str) {
return Str::lower(Str::snake($str));
});
// Will return: "my-string"
Str::lowerSnake('MyString');
Tip given by @mmartin_joo
If you are running Laravel v8.70
, you can chain can()
method directly instead of middleware('can:..')
// instead of
Route::get('users/{user}/edit', function (User $user) {
...
})->middleware('can:edit,user');
// you can do this
Route::get('users/{user}/edit', function (User $user) {
...
})->can('edit' 'user');
// PS: you must write UserPolicy to be able to do this in both cases
Tip given by @sky_0xs
You can use temporary download URLs for your cloud storage resources to prevent unwanted access. For example, when a user wants to download a file, we redirect to an s3 resource but have the URL expire in 5 seconds.
public function download(File $file)
{
// Initiate file download by redirecting to a temporary s3 URL that expires in 5 seconds
return redirect()->to(
Storage::disk('s3')->temporaryUrl($file->name, now()->addSeconds(5))
);
}
Tip given by @Philo01
Dealing with deeply-nested arrays can result in missing key / value exceptions. Fortunately, Laravel's data_get() helper makes this easy to avoid. It also supports deeply-nested objects.
Deeply-nested arrays are a nightmare when they may be missing properties that you need.
In the example below, if either request
, user
or name
are missing then you'll get errors.
$value = $payload['request']['user']['name']
Instead, use the data_get()
helper to access a deeply-nested array item using dot notation.
$value = data_get($payload, 'request.user.name');
We can also avoid any errors caused by missing properties by supplying a default value.
$value = data_get($payload, 'request.user.name', 'John');
Tip given by @mattkingshott
You can customize how your exceptions are rendered by adding a 'render' method to your exception.
For example, this allows you to return JSON instead of a Blade view when the request expects JSON.
abstract class BaseException extends Exception
{
public function render(Request $request)
{
if ($request->expectsJson()) {
return response()->json([
'meta' => [
'valid' => false,
'status' => static::ID,
'message' => $this->getMessage(),
],
], $this->getCode());
}
return response()->view('errors.' . $this->getCode(), ['exception' => $this], $this->getCode());
}
}
class LicenseExpiredException extends BaseException
{
public const ID = 'EXPIRED';
protected $code = 401;
protected $message = 'Given license has expired.'
}
Tip given by @Philo01
The tap
helper is a great way to remove a separate return statement after calling a method on an object. Makes things nice and clean
// without tap
$user->update(['name' => 'John Doe']);
return $user;
// with tap()
return tap($user)->update(['name' => 'John Doe']);
Tip given by @mattkingshott
You can insert an exclamation into the DateTime::createFromFormat
method to reset all of the remaining time units
// 2021-10-12 21:48:07.0
DateTime::createFromFormat('Y-m-d', '2021-10-12');
// 2021-10-12 00:00:00.0
DateTime::createFromFormat('!Y-m-d', '2021-10-12');
2021-10-12 21:00:00.0
DateTime::createFromFormat('!Y-m-d H', '2021-10-12');
Tip given by @SteveTheBauman
Scheduled commands in the console kernel can automatically email their output if something goes wrong
Did you know that any commands you schedule in the console kernel can automatically email their output if something goes wrong
$schedule
->command(PruneOrganizationsCOmmand::class)
->hourly()
->emailOutputOnFailure(config('mail.support'));
Tip given by @mattkingshott
if (request()->has('since')) {
// example.org/?since=
// fails with illegal operator and value combination
$query->whereDate('created_at', '<=', request('since'));
}
if (request()->input('name')) {
// example.org/?name=0
// fails to apply query filter because evaluates to false
$query->where('name', request('name'));
}
if (request()->filled('key')) {
// correct way to check if get parameter has value
}
Tip given by @mc0de
Dust out your bloated route file and split it up to keep things organized
class RouteServiceProvider extends ServiceProvider
{
public function boot()
{
$this->routes(function () {
Route::prefix('api/v1')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::prefix('webhooks')
->namespace($this->namespace)
->group(base_path('routes/webhooks.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
if ($this->app->environment('local')) {
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/local.php'));
}
});
}
}
Tip given by @Philo01
In Laravel you can send e-mails to a custom log file.
You can set your environment variables like this:
MAIL_MAILER=log
MAIL_LOG_CHANNEL=mail
And also configure your log channel:
'mail' => [
'driver' => 'single',
'path' => storage_path('logs/mails.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
Now you have all your e-mails in /logs/mails.log
It's a good use case to quickly test your mails.
Tip given by @mmartin_joo
Laravel provides an interface to convert markdown in HTML out of the box, without the need to install new composer packages.
$html = Str::markdown('# Changelogfy')
Output:
<h1>Changelogfy</h1>
Tip given by @paulocastellano
We often write if statements to check if a value is present on a request or not.
You can simplify it with the whenFilled()
helper.
public function store(Request $request)
{
$request->whenFilled('status', function (string $status)) {
// Do something amazing with the status here!
}, function () {
// This it called when status is not filled
});
}
Tip given by @mmartin_joo
You can pass arguments to your middleware for specific routes by appending ':' followed by the value. For example, I'm enforcing different authentication methods based on the route using a single middleware.
Route::get('...')->middleware('auth.license');
Route::get('...')->middleware('auth.license:bearer');
Route::get('...')->middleware('auth.license:basic');
class VerifyLicense
{
public function handle(Request $request, Closure $next, $type = null)
{
$licenseKey = match ($type) {
'basic' => $request->getPassword(),
'bearer' => $request->bearerToken(),
default => $request->get('key')
};
// Verify license and return response based on the authentication type
}
}
Tip given by @Philo01
If you need to grab something from the Laravel session, then forget it immediately, consider using session()->pull($value)
. It completes both steps for you.
// Before
$path = session()->get('before-github-redirect', '/components');
session()->forget('before-github-redirect');
return redirect($path);
// After
return redirect(session()->pull('before-github-redirect', '/components'))
Tip given by @jasonlbeggs
New in this week's Laravel v8.77: $request->date()
method.
Now you don't need to call Carbon manually, you can do something like: $post->publish_at = $request->date('publish_at')->addHour()->startOfHour();
Link to full pr by @DarkGhostHunter
When you want to map paginated data and return only a subset of the fields, use through
rather than map
. The map
breaks the pagination object and changes it's identity. While, through
works on the paginated data itself
// Don't: Mapping paginated data
$employees = Employee::paginate(10)->map(fn ($employee) => [
'id' => $employee->id,
'name' => $employee->name
])
// Do: Mapping paginated data
$employees = Employee::paginate(10)->through(fn ($employee) => [
'id' => $employee->id,
'name' => $employee->name
])
Tip given by @bhaidar
There’s a withToken
method to attach the Authorization
header to a request.
// Booo!
Http::withHreader([
'Authorization' => 'Bearer dQw4w9WgXcq'
])
// YES!
Http::withToken('dQw4w9WgXcq');
Tip given by @p3ym4n
You can use the readStream
and writeStream
to copy a file (or all files from a folder) from one disk to another keeping the memory usage low.
// List all the files from a folder
$files = Storage::disk('origin')->allFiles('/from-folder-name');
// Using normal get and put (the whole file string at once)
foreach($files as $file) {
Storage::disk('destination')->put(
"optional-folder-name" . basename($file),
Storage::disk('origin')->get($file)
);
}
// Best: using Streams to keep memory usage low (good for large files)
foreach ($files as $file) {
Storage::disk('destination')->writeStream(
"optional-folder-name" . basename($file),
Storage::disk('origin')->readStream($file)
);
}
Tip given by @alanrezende
Do you know about has
, exists
and missing
methods in Laravel session?
// The has method returns true if the item is present & not null.
$request->session()->has('key');
// THe exists method returns true if the item ir present, event if its value is null
$request->session()->exists('key');
// THe missing method returns true if the item is not present or if the item is null
$request->session()->missing('key');
Tip given by @iamharis010
Need to test that you are passing the correct data to a view? You can use the viewData method on your response. Here are some examples:
/** @test */
public function it_has_the_correct_value()
{
// ...
$response = $this->get('/some-route');
$this->assertEquals('John Doe', $response->viewData('name'));
}
/** @test */
public function it_contains_a_given_record()
{
// ...
$response = $this->get('/some-route');
$this->assertTrue($response->viewData('users')->contains($userA));
}
/** @test */
public function it_returns_the_correct_amount_of_records()
{
// ...
$response = $this->get('/some-route');
$this->assertCount(10, $response->viewData('users'));
}
Tip given by @JuanRangelTX
Tracking something like page views with MySQL can be quite a performance hit when dealing with high traffic. Redis is much better at this. You can use Redis and a scheduled command to keep MySQL in sync on a fixed interval.
Route::get('{project:slug', function (Project $project) {
// Instead of $project->increment('views') we use Redis
// We group the views by the project id
Redis::hincrby('project-views', $project->id, 1);
})
// Console/Kernel.php
$schedule->command(UpdateProjectViews::class)->daily();
// Console/Commands/UpdateProjectViews.php
// Get all views from our Redis instance
$views = Redis::hgetall('project-views');
/*
[
(id) => (views)
1 => 213,
2 => 100,
3 => 341
]
*/
// Loop through all project views
foreach ($views as $projectId => $projectViews) {
// Increment the project views on our MySQL table
Project::find($projectId)->increment('views', $projectViews);
}
// Delete all the views from our Redis instance
Redis::del('project-views');
Tip given by @Philo01