Skip to content

Latest commit

 

History

History
1525 lines (1108 loc) · 45.6 KB

other.md

File metadata and controls

1525 lines (1108 loc) · 45.6 KB

Other

⬆️ Go to main menu ⬅️ Previous (API)

Localhost in .env

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

Time value in the past/future

If you want to have some time value in the past/future, you can build it by chaining various Laravel/Carbon helpers, like now()->[add or subtract something]->setTime()

$product = Product::factory()->create([
     'published_at' => now()->addDay()->setTime(14, 00),
]);

Do some work after a response has been sent to the browser

You can also use middleware to do some work after a response has been sent to the browser. Such middleware is called Terminable Middleware.

You can make a middleware terminable by defining a terminate method on a middleware.

This method will be automatically called after a response is sent to the browser. It will have both request and response as params.

class TerminatingMiddleware
{
    public function handle($request, Closure $next)
    {
        return $next($request);
    }
 
    public function terminate($request, $response)
    {
        // ...
    }
}

Tip given by @Laratips1

Redirect with URL fragment

Did you know you can add a URI fragment when redirecting to a route in Laravel?

Super useful when redirecting to a specific section of the page. E.g. reviews section on a product page.

return redirect()
     ->back()
     ->withFragment('contactForm');
     // domain.test/url#contactForm

return redirect()
     ->route('product.show')
     ->withFragment('reviews');
     // domain.test/product/23#reviews

Tip given by @ecrmnn

Use middleware to adjust incoming request

Laravel's middlewares are a great way to transform incoming requests. For example, I decided to rename a model in my application; instead of bumping the API version for a breaking change, I simply convert those requests using the old reference.

class ConvertLicenseeIntoContact
{
     public function handle(Request $request, Closure $next)
     {
          if($request->json('licensee_id')) {
               $request->json()->set('contact_id', $request->json('licensee_id'));
          }

          return $next($request);
     }
}

Tip given by @Philo01

Redirect away from Laravel app

Sometimes, you might need to redirect away from your Laravel application to other websites. In that case, there is a handy away method you can call on the redirect() method...

redirect()->away('https://www.google.com');

It creates a RedirectResponse without any additional URL encoding, validation, or verification.

Tip given by @Laratips1

Blade directive to show data in specific environment

Did you know Laravel has a 'production' blade directive that you can use to show data only when you are in a production environment?

There is also another 'env' directive that you can use to show data in the environment you specify.

@production
     // I am only visible to the production environment...
@endproduction

@env('staging')
     // I am only visible to the staging environment...
@endenv

@env(['staging', 'production'])
     // I am only visible to both the staging and production environment...
@endenv

Tip given by @Laratips1

Schedule Laravel job based on time zone

Do you know you can schedule laravel job based on time zone

Setting Timezone for One Command:

$schedule->command('reportg:generate')
         ->timezone('America/New_York')
         ->at('2:00');

If you are repeatedly assigning the same timezone to all of your schedule tasks, you may wish to define a scheduleTimezone method in you app\Console\Kernel class:

protected function scheduleTimezone()
{
     return 'America/Chicago';
}

Tip given by @binumathew

Use assertModelMissing instead assertDatabaseMissing

While testing model deletion, use assertModelMissing instead assertDatabaseMissing.

/** @test */
public function allowed_user_can_delete_task()
{
     $task = Task::factory()->for($this->project)->create();

     $this->deleteJson($task->path())
          ->assertNoContent();

     // Instead of assertDatabaseMissing to check if the model missing from the database
     $this->assertDatabaseMissing('tasks', ['id' => $task->id]);

     // use directly assertModelMissing
     $this->assertModelMissing($task);
}

Tip given by @h_ik04

Various options to format diffForHumans()

In Carbon, did you know you can add various options to format diffForHumans()? Read the docs for more examples.

$user->created_at->diffForHumans();

=> "17 hours ago"

$user->created_at->diffForHumans([
     'parts' => 2
]);

=> "17 hours 54 minutes ago"

$user->created_at->diffForHumans([
     'parts' => 3
     'join' => ', ',
]);

=> "17 hours, 54 minutes, 50 seconds ago"

$user->created_at->diffForHumans([
     'parts' => 3,
     'join' => ', ',
     'short' => true,
]);

=> "17h, 54m, 50s ago"

Create custom disks at runtime

Did you know that you can create custom disks at runtime without the need to have the config in your config/filesystems file?

This can be handy to manage files in custom paths without the need of adding them to the config.

$avatarDisk = Storage::build([
    'driver' => 'local',
    'root' => storage_path('app/avatars'),
]);
$avatarDisk->put('user_avatar.jpg', $image);

Tip given by @wendell_adriel

When (NOT) to run "composer update"

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.

Composer: Check for Newer Versions

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

Auto-Capitalize Translations

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']);

Carbon with Only Hours

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();

Single Action Controllers

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::class);

Artisan:

php artisan make:controller ShowProfile --invokable

Controller:

class ShowProfile extends Controller
{
    public function __invoke($id)
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}

Redirect to Specific Controller Method

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::class, 'method'], ['param' => $value]);

Use Older Laravel Version

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.

Add Parameters to Pagination Links

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() }}

Repeatable Callback Functions

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();

Request: has any

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';
    }
}

Simple Pagination

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);

Blade directive to add true/false conditions

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 can be used without queues

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));
    //
}

Use faker outside factories or seeders

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;
    }
}

Schedule things

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();
}

Search Laravel docs

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

Filter route:list

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

Blade directive for not repeating yourself

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

Artisan commands help

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

Disable lazy loading when running your tests

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

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

Request parameter default value

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

Pass middleware directly into the route without register it

Route::get('posts', PostController::class)
    ->middleware(['auth', CustomMiddleware::class])

Tip given by @sky_0xs

Transforming an array to CssClasses

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

"upcomingInvoice" method in Laravel Cashier (Stripe)

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_

Laravel Request exists() vs has()

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

There are multiple ways to return a view with variables

// 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'));

Schedule regular shell commands

We can schedule regular shell commands within Laravel scheduled command

// app/Console/Kernel.php

class Kernel extends ConsoleKernel
{
    protected function schedule(Schedule $schedule)
    {
        $schedule->exec('node /home/forge/script.js')->daily();
    }
}

Tip given by @anwar_nairi

HTTP client request without verifying

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

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' => '/database/migrations/task1']);
    }
}

"Str::mask()" method

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'); // [email protected]

        $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

Extending Laravel classes

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

Can feature

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

Temporary download URLs

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

If you have a complex array, you can use data_get() helper function to retrieve a value from a nested array using "dot" notation and wildcard.

$data = [
  0 => ['user_id' => 1, 'created_at' => 'timestamp', 'product' => {object Product}],
  1 => ['user_id' => 2, 'created_at' => 'timestamp', 'product' => {object Product}],
  2 => etc
];

// Now we want to get all products ids. We can do like this:

data_get($data, '*.product.id');

// Now we have all products ids [1, 2, 3, 4, 5, etc...]

In the example below, if either request, user or name are missing then you'll get errors.

$value = $payload['request']['user']['name'];

// The data_get function accepts a default value, which will be returned if the specified key is not found.

$value = data_get($payload, 'request.user.name', 'John')

Tip given by @mattkingshott

Customize how your exceptions are rendered

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

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

Reset all of the remaining time units

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

Be careful when constructing your custom filtered queries using GET parameters

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

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

You can send e-mails to a custom log file

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

Markdown made easy

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

Simplify if on a request with whenFilled() helper

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

Pass arguments to middleware

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

Get value from session and forget

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

$request->date() method

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

Use through instead of map when using pagination

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

Quickly add a bearer token to HTTP request

There’s a withToken method to attach the Authorization header to a request.

// Booo!
Http::withHeader([
    'Authorization' => 'Bearer dQw4w9WgXcq'
])

// YES!
Http::withToken('dQw4w9WgXcq');

Tip given by @p3ym4n

Copy file or all files from a folder

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

Sessions has() vs exists() vs missing()

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

Test that you are passing the correct data to a view

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

Use Redis to track page views

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

to_route() helper function

Laravel 9 provides shorter version of response()->route(), take a look on the following code:

// Old way
Route::get('redirectRoute', function() {
    return redirect()->route('home');
});

// Post Laravel 9
Route::get('redirectRoute', function() {
    return to_route('home');
});

This helper work in the same way as redirect()->route('home'), but it is more concise than an old way.

Tip given by @CatS0up

Pause a long running job when queue worker shuts down

When running a long job, if your queue worker gets shutdown by

  • Stopping the worker.
  • Sending signal SIGTERM (SIGINT for Horizon).
  • Pressing CTRL + C (Linux/Windows).

Then the job process may get corrupted while it is doing something.

By checking with app('queue.worker')->shouldQuit, we can determine if the worker is shutting down. This way, we can save the current process and requeue the job so that when the queue worker runs again, it can resume from where it left.

This is very useful in the Containerized world (Kubernetes, Docker etc.) where the container gets destroyed and re-created anytime.

<?php

namespace App\Jobs;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Log;

class MyLongJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public $timeout = 3600;

    private const CACHE_KEY = 'user.last-process-id';

    public function handle()
    {
        $processedUserId = Cache::get(self::CACHE_KEY, 0); // Get last processed item id
        $maxId = Users::max('id');

        if ($processedUserId >= $maxId) {
            Log::info("All users have already been processed!");
            return;
        }

        while ($user = User::where('id', '>', $processedUserId)->first()) {
            Log::info("Processing User ID: {$user->id}");

            // Your long work here to process user
            // Ex. Calling Billing API, Preparing Invoice for User etc.

            $processedUserId = $user->id;
            Cache::put(self::CACHE_KEY, $processedUserId, now()->addSeconds(3600)); // Updating last processed item id

            if (app('queue.worker')->shouldQuit) {
                $this->job->release(60); // Requeue the job with delay of 60 seconds
                break;
            }
        }

        Log::info("All users have processed successfully!");
    }
}

Tip given by @a-h-abid

Freezing Time in Laravel Tests

In your Laravel tests, you might sometimes need to freeze the time.

This is particularly useful if you're trying to make assertions based on timestamps or need to make queries based on dates and/or times.

// To freeze the time, you used to be able to write this at the time top of your tests:
Carbon::setTestNow(Carbon::now());
// You could also use the "travelTo" method:
$this->travelTo(Carbon::now());
// You can now use the new "freezeTime" method to keep your code readable and obvious:
$this->freezeTime();

Tip given by @AshAllenDesign

New squish helper

New in Laravel from 9.7 squish helper.

$result = Str::squish(' Hello   John,         how   are   you?    ');
// Hello John, how are you?

Tip given by @mattkingshott

Specify what to do if a scheduled task fails or succeeds

You can specify what to do if a scheduled task fails or succeeds.

$schedule->command('emails:send')
        ->daily()
        ->onSuccess(function () {
            // The task succeeded
        })
        ->onFailure(function () {
            // The task failed
        });

Tip given by @cosmeescobedo

Scheduled command on specific environments

Running Laravel scheduled command on specific environments.

// Not good
if (app()->environment('production', 'staging')) {
    $schedule->command('emails:send')
        ->daily();
}
// Better
$schedule->command('emails:send')
        ->daily()
        ->onEnvironment(['production', 'staging']);

Tip given by @nguyenduy4994

Add conditionable behavior to your own classes

You can use the Conditionable Trait to avoid using if/else and promote method chaining.

<?php

namespace App\Services;

use Illuminate\Support\Traits\Conditionable;

class MyService
{
    use Conditionable;

    // ...
}
<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Http\Requests\MyRequest;
use App\Services\MyService;

class MyController extends Controller
{
    public function __invoke(MyRequest $request, MyService $service)
    {
        // Not good
        $service->addParam($request->param);

        if ($request->has('something')) {
            $service->methodToBeCalled();
        }

        $service->execute();
        // ---

        // Better
        $service->addParam($request->param)
            ->when($request->has('something'), fn ($service) => $service->methodToBeCalled())
            ->execute();
        // ---

        // ...
    }
}

Perform Action when Job has failed

In some cases, we want to perform some action when job has failed. For example, send an email or a notification.

For this purpose, we can use failed() method in the job class, just like the handle() method:

namespace App\Jobs\Invoice;
use Illuminate\Bus\Batchable;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Throwable;

class CalculateSingleConsignment implements ShouldQueue
{
    use Batchable, Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    // ... __construct() method, handle() method, etc.

    public function failed(Throwable $exception)
    {
        // Perform any action here when job has failed
     }
}

Tip given by @pauloimon

Ignore Database when Job has failed

If you ever need to bypass database when a job fails, you can do one of the below things to skip database:

  • Set env QUEUE_FAILED_DRIVER with value null. Works from Laravel 8 and above.
  • Set the failed value to null in config/queue.php file, replacing the array (like below code). This one works for Laravel 7 and older.
    'failed' => null,

Why you would want this? For applications where you do not need to store failed jobs and they needs to have very high TPS, skipping database can be very favourable as we are not hitting database, saving times & prevent database going down.

Tip given by @a-h-abid