Daily Laravel/PHP tips I share on my X and LinkedIn. For more in-depth articles about Laravel, feel free to check out my blog.
When a bound instance in the Laravel container is rebound, a rebinding event is triggered. You can listen to this event to ensure that the classes using the instance stay up to date. You can achieve this by using the rebinding method or simply using the refresh shortcut π
<?php
namespace App\Providers;
use App\Services\PaymentService;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Foundation\Application;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton('payment.service', function (Application $app) {
$paymentService = new PaymentService();
$paymentService->setTaxService($app['tax.service']);
// Will set a new TaxService instance for the Payment Service
$app->refresh('tax.service', $paymentService, 'setTaxService');
return $paymentService;
});
}
}
Laravel accessors allow you to transform your model attributes when retrieving them. But sometimes, you may wish to get the original value. Well, Laravel provides a method just for that: getRawOriginal
π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class User extends Model
{
protected function username(): Attribute
{
return Attribute::make(
function (string $value, array $attributes) {
return sprintf('%s#%s', $value, $attributes['app_id']);
}
);
}
}
$user = User::create([
'username' => 'oussama',
]);
$user->getOriginal('username'); // oussama#1234
$user->getRawOriginal('username'); // oussama
Route Model Binding allows you to inject the model instances directly into your routes. It is usually used within a controller, but did you know you could also access the model instance in your Form Request?
<?php
// Route definition
Route::put('/post/{post}', [PostsController::class, 'create']);
// Form Request defintion
class PostController extends FormRequest
{
public function authorize(): bool
{
// The post instance, if found
return $this->user()->can('create', $this->route('post'));
}
public function rules(): array
{
return [
// ...
];
}
}
Did you know that you can map exceptions to other exceptions? This is helpful when you have custom exceptions that you'd like to be handled like internal ones, or when you want to wrap third-party exceptions and map them to your own π
<?php
namespace App\Exceptions;
use App\Exceptions\MyCustomException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
class Handler extends ExceptionHandler
{
// MyCustomException is now handled as if it were ModelNotFoundException
protected $exceptionMap = [
MyCustomException::class => ModelNotFoundException::class,
];
}
When defining a custom exception, there is a secret way to map the exception to another one, and this is done by defining the getInnerException
method π
Curious about how the mapping is done? Take a look at Illuminate\Foundation\Exceptions\Handler::mapException()
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class MyCustomException extends Exception
{
// MyCustomException is now handled as if it were ModelNotFoundException
public function getInnerException(): Throwable
{
return new ModelNotFoundException;
}
}
In Laravel, the route() helper is used to generate paths for named routes; by default, it generates the absolute path. Did you know that if you pass False as your third parameter, it will generate a relative path? Now you do π
<?php
route('tips', $tip->id); // https://oussama-mater.tech/tips/1
route('tips', $tip->id, false); // /tips/1
Did you know that Laravel allows you to define dynamic "where" conditions? For example, you could do, whereNameAndAge(name_value, age_value)
π€―
Make sure to add the method name to your model's PHPDoc so your IDE does not complain, that's a bit too much magic for it to understand.
Curious about how it's done? Take a look at Illuminate\Database\Query\Builder::dynamicWhere()
<?php
// select * from `users` where `name` = 'oussama' and `last_name` = 'mater'"
User::whereNameAndLastName('oussama', 'mater')->first();
Laravel automatically resolves model factories if they exist in the Database\Factories namespace. Sometimes you wish to move them. For example, if you're using Domain-Driven Design (DDD), you may want each domain to have its own factories. In such cases, you can instruct Laravel on how to resolve the factory by defining a newFactory method in your model. By doing so, you can perform additional logic too! For example return different factories for different environments.
<?php
namespace App\Models;
use Database\Factories\UserFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// ...
protected static function newFactory()
{
// ... custom logic
return new UserFactory();
}
}
When updating your Laravel models, you always trigger "Model Events," which are hooks enabling you to perform extra actions. You can disable this behavior by updating them quietly π€«
<?php
$user = Auth::user();
$user->name = 'Oussama Mater';
// Won't trigger Model Events
$user->saveQuietly();
$user->deleteQuietly();
$user->forceDeleteQuietly();
$user->restoreQuietly();
Did you know that when using eager loading with relationships, you may specify the exact columns you need? This will decrease memory usage π
<?php
$users = Post::with('author:id,name')->get();
Did you know you can check whether or not two models are the same by using the is()
helper? π
<?php
$user = User::find(1);
$sameUser = User::find(1);
$differentUser = User::find(2);
$user->is($sameUser); // true
$user->is($differentUser); // false
While Laravel's whereHas
is excellent for retrieving records based on a specified relationship along with additional query constraints, there's a shortcut called "whereRelation" that accomplishes the same task π
<?php
// Before
User::whereHas('comments', function ($query) {
$query->where('created_at', '>', now()->subDay());
})->get();
// After
User::whereRelation('comments', 'created_at', '>', now()->subDay())->get();
At times, certain tasks, like sending emails, don't necessarily need to be queued and processed by a worker. In such cases, you can make use of "dispatchAfterResponse()." True to its name, this method dispatches the job right after the server responds to the user. A quick response for the client without burdening the worker with minor tasks π
<?php
use App\Jobs\SendNotification;
SendNotification::dispatchAfterResponse();
Did you know that Laravel ships with the 'allRelatedId()' method to help you fetch all IDs for a belongsToMany relationship? Now you do π
<?php
class User extends Model
{
public function roles()
{
return $this->belongsToMany(Role::class);
}
}
$user = User::find(1);
$roleIds = $user->roles()->pluck('id')->toArray();
$roleIds = $user->roles()->allRelatedIds()->toArray();
Did you know you can listen to all executed queries in Laravel? This is not only useful for quick debugging; for instance, you can send a Slack notification if the query is slower than expectedπ
<?php
DB::listen(function (QueryExecuted $query) {
dump($query->sql); // select * from `users` where `users`.`id` = ? limit 1
dump($query->bindings); // [0 => 1]
dump($query->time); // 6.05
});
Did you know that Laravel provides the "missing()" method to customize the default route model binding behavior when a model is not found? π
<?php
use App\Http\Controllers\LocationsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
Route::get('/locations/{location:slug}', [LocationsController::class, 'show'])
->name('locations.view')
->missing(function (Request $request) {
return Redirect::route('locations.index');
});
Did you know you could pass multiple arguments to the isset() function?
<?php
if(isset($var1) && isset($var2)) { ... }
if(isset($var1, $var2)) { ... }
Did you know that the Laravel HTTP Client comes with a fluent method called "withToken()" that you can use to set bearer tokens? π
<?php
// The following code
Http::withHeaders([
'Authorization' => 'Bearer eyJhbGciOiJIUz...',
]);
// Is equivalent to this
Http::withToken('eyJhbGciOiJIUz...');
It is true that Laravel provides methods like "info()" and "error()" to send output to the console, but since Laravel 9, the core itself started using "termwind", which you can see when seeding your database or in case of an error, and you can leverage that as well by using the "components" attributes.
<?php
/**
* Execute the console command.
*/
public function handle()
{
// A standard error message
$this->error('The old fashioned way.');
// A much cooler error message
$this->components->error('What Laravel uses internally.');
// And more
$this->components->info('Hello, World!');
$this->components->warn('Hello, World!');
$this->components->alert('Hello, World!');
// choice, confirm, ask, warn, task, line, bulletList ..
return Command::SUCCESS;
}
Did you know that Laravel comes with a Prunable trait to permanently remove records, including the soft-deleted ones, based on a condition you define? π
<?php
// Define the pruning condition
class User extends Authenticatable
{
use Prunable;
public function prunable()
{
return static::query()
->whereNull('email_verified_at')
->where('created_at', '<', now()->subMonths(6));
}
}
// Schedule the pruning command to run daily for example
$schedule->command(PruneCommand::class)->daily();
Did you know that Laravel ships with the "preventStrayRequests()" method to avoid making actual requests during testing? This is handy not only for third-party APIs but also for local APIs. While your tests may pass locally because the entire environment is up and running, they could fail in the CI pipeline. This happens because, in the CI pipeline, typically only the service you are testing is active, so making actual requests to unavailable APIs will cause your suite to fail.
<?php
use Illuminate\Support\Facades\Http;
// Define in your setUp()
Http::preventStrayRequests();
// Now in your tests
Http::fake([
'example.com/*' => Http::response('ok'),
]);
// An "ok" response is returned ...
Http::get('https://example.com/users');
// An exception is thrown ...
Http::get('https://oussama-mater.tech');
Did you know that Laravel ships with a "whereBelongsTo" to get the parent model? This will make the code much more readable π
<?php
// This
$posts = Post::where('user_id', $user->id)->get();
// Or this is okay
$posts = Post::whereUserId($user->id)->get();
// But this is more readable
$posts = Post::whereBelongsTo($user)->get();
I'm sure you've trimmed a string at least once. While the built-in "trim" function that PHP provides is good, it may not be sufficient if you want to eliminate extra spaces between text. In such cases, give the "squish" method a go π
<?php
use Illuminate\Support\Str;
$joke = Str::squish(' PHP is dead π€£ ');
// PHP is dead π€£
Did you know that you can use "whenQueryingForLongerThan" to monitor slow queries? You can set the threshold in milliseconds. If a query exceeds the threshold, you can send notifications or grab a coffee with the mastermind behind the query π
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
DB::whenQueryingForLongerThan(500, function (Connection $connection, QueryExecuted $event) {
// Mastermind behind this query, let's grab a coffee ..
});
}
}
Sometimes, you might want to skip executing a command based on a specific condition. Laravel includes the "skip" method to accomplish exactly that π
<?php
$schedule->command('emails:send')->daily()->skip(function () {
return Carbon::today()->isHoliday();
});
Since Laravel uses FakerPHP for generating fake data, you can employ both, "numerify" and "bothify", to generate data in a specific pattern π
<?php
$faker = Faker\Factory::create();
// 2067-7317-8584-7068
$creditCardNumber = $faker->numerify('####-####-####-####');
// TRK-cwq-546-shx
$trackingNumber = $faker->bothify('TRK-???-###-???');
When defining foreign IDs, Laravel offers multiple methods, one of which is "foreignIdFor()". This method snake cases the model name and appends "id" to it. Not only does it make your code more readable, but you can quickly navigate to the model from the migration π
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Schema::table('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->foreignIdFor(User::class); // user_id
$table->timestamps();
});
Be careful when using whereYear
; even if your column is indexed, it won't be used, and the database will perform a full table scan. Instead, opt for using ranges π
<?php
DB::table('user_posts')
->select('user_id', DB::raw('COUNT(*) as count'))
->whereYear('created_at', 2023)
->groupBy('user_id')
->get();
// select `user_id`, COUNT(*) as count from `user_posts` where year(`created_at`) = ? group by `user_id`
DB::table('user_posts')
->select('user_id', DB::raw('COUNT(*) as count'))
->whereBetween('created_at', ['2023-01-01 00:00:00', '2024-01-01 00:00:00'])
->groupBy('user_id')
->get();
// select `user_id`, COUNT(*) as count from `user_posts` where `created_at` between ? and ? group by `user_id`
Did you know that you can use "withCount" to get the count of a related relationship without loading it? This is really useful, for example, when displaying statistics π
<?php
$users = User::withCount(['posts'])->get();
// $users->posts_count
While Laravel offers a plethora of validation rules, there are times when you need custom ones. These are usually written in a custom class. But, did you know you can perform inline validation also? π
<?php
request()->validate([
'email' => [
'required',
'email',
function ($attribute, $value, $fail) {
if (substr($value, -12) !== '@example.com') {
$fail($attribute, 'The email must belong to example.com domain');
}
},
],
]);
If you are using Laravel 10 and upwards, there is an elegant way to read JSON files by using "File::json()". You can also pass the flags that you would normally pass to "json_decode()", in case you want to throw an exception π
<?php
// Laravel < 10
$data = json_decode(File::get('data.json'), flags: JSON_THROW_ON_ERROR);
// Laravel >= 10
$data = File::json('data.json', JSON_THROW_ON_ERROR);
Laravel's route model binding is a powerful feature that automatically injects your model into your controller. By default, it uses the ID to fetch the instance. If you wish, you can implement custom logic for resolution by defining "resolveRouteBinding" π
<?php
// In your model
class Post extends Model
{
public function resolveRouteBinding($value, $field = null)
{
return $this
->where('slug', $value)
->orWhere('uuid', $value)
->firstOrFail();
}
}
/*
Now both these will work
/post/resolve-route-binding-is-cool
/post/638fd97b-4a96-469e-b497-9e553f13c8ef
*/
Route::get('/post/{post}', fn (Post $post) => $post);
Sometimes we are forced to use a try-catch block just to ignore an expected exception that an external service throws upon failure. Laravel provides a more elegant solution for such scenarios through the "rescue" helper π
<?php
// If an exception is thrown, we default to "en"
try {
$language = LanguageDetector::detect($request->ip());
} catch (Exception $e) {
$language = 'en';
}
// A cleaner way to achieve the same, and the exception will still be reported
$language = rescue(fn () => LanguageDetector::detect($request->ip()), 'en');
Did you know that Laravel ships with a method called "toQuery"? This method allows you to update a collection using a single query statement by running a "whereIn" π
<?php
$users = User::where('status', 'VIP')->get();
// Instead of this
foreach ($users as $user) {
$user->update(['status' => 'Administrator']);
}
// Do this instead
$users->toQuery()->update(['status' => 'Administrator']);
When working with mailables, we often send them to MailHog or Mailtrap to quickly preview the rendered email. Did you know that Laravel allows you to preview emails in the browser as if they were regular Blade files? π
<?php
Route::get('/mailable', function () {
$invoice = App\Models\Invoice::find(1);
return new App\Mail\InvoicePaid($invoice);
});
Laravel 9 and onward ship with 2 schema methods, 'whenTableDoesntHaveColumn' and 'whenTableHasColumn', which allow you to drop or create a column if it exists or not. This is really helpful when you have multiple environments where schemas can get out of sync quickly.
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Will create the email column only if it does not exist
Schema::whenTableDoesntHaveColumn('users', 'email', function(Blueprint $table){
$table->string('email')->unique();
});
// Will drop the email column only if it exists
Schema::whenTableHasColumn('users', 'email', function(Blueprint $table){
$table->dropColumn('email');
});
Sometimes, you just wish to report internal exceptions. For instance, if you want to know exactly where that 404 error is being thrown, you can do so by overriding the internalDontReport
array π
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
// The ModelNotFoundException exception will now be reported
class Handler extends ExceptionHandler
{
protected $internalDontReport = [
AuthenticationException::class,
AuthorizationException::class,
BackedEnumCaseNotFoundException::class,
HttpException::class,
HttpResponseException::class,
//ModelNotFoundException::class,
MultipleRecordsFoundException::class,
RecordsNotFoundException::class,
SuspiciousOperationException::class,
TokenMismatchException::class,
ValidationException::class,
];
}
Sometimes, when validating nested arrays, you may have custom rules that require access to the value of the item being validated. Laravel 9 and onwards comes with 'forEach,' enabling you to do just that π
<?php
use App\Rules\HasPermission;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
$validator = Validator::make($request->all(), [
'companies.*.id' => Rule::forEach(function (string|null $value, string $attribute) {
return [
Rule::exists(Company::class, 'id'),
new HasPermission('manage-company', $value),
];
}),
]);
Did you know that if you want to update a model without modifying its updated_at
timestamp, you can use the 'withoutTimestamps' method? π
<?php
// The updated_at column will not be updated
Post::withoutTimestamps(fn () => $post->increment(['reads']));
Did you know that Laravel comes with the "inRandomOrder" method, which sorts query results randomly? π
$randomUser = DB::table('users')
->inRandomOrder()
->first();
Did you know that Laravel allows you to define callbacks to be executed based on the result of a scheduled task? This helps log failures or execute related actions on success π
<?php
use Illuminate\Console\Scheduling\Schedule;
$schedule->command('emails:send')
->daily()
->onSuccess(function () {
// The task succeeded...
})
->onFailure(function () {
// The task failed...
});
Eloquent models injected into jobs are auto-serialized for the queue and may trigger a ModelNotFoundException
if deleted while waiting for a worker; you can quietly discard such jobs by setting deleteWhenMissingModels
to true π
<?php
use Illuminate\Contracts\Queue\ShouldQueue;
class UpdateSearchIndex implements ShouldQueue
{
/**
* Delete the job if its models no longer exist.
*
* @var bool
*/
public $deleteWhenMissingModels = true;
}
Laravel automatically updates "updated_at" in many-to-many relationships, and it also ships with "setTouchedRelations" method to manually update related models in one-to-one and one-to-many relationships π
<?php
$user = User::firstOrFail();
$user->setTouchedRelations(['posts']);
// The 'updated_at' of all related posts will be updated
$user->save();
Did you know that Laravel ships with the "push" method, allowing you to save models and all related relationships recursively, without having to go over them one by one? π
<?php
$post = Post::find(1);
$post->comments[0]->message = 'Message';
$post->comments[0]->author->name = 'Author Name';
// Instead of these
$post->comments[0]->author->save();
$post->comments[0]->save();
// You can do this
$post->push();
Did you know that Laravel allows you to save multiple related models at once by using the 'saveMany' method? π
<?php
$post = Post::find(1);
$post->comments()->saveMany([
new Comment(['message' => 'A new comment.']),
new Comment(['message' => 'Another new comment.']),
]);
Did you know that Laravel allows you to query JSON fields in databases that support JSON column types? π
<?php
// Will return all users where the preferences.dining.meal field is equal to 'salad'
DB::table('users')
->where('preferencesβdiningβmeal', 'salad')
->get();
// Will return all users where the languages array contains 'en' and 'de'
DB::table('users')
->whereJsonContains('optionsβlanguages', ['en', 'de'])
->get();
// Will return all users where the languages array has more than one element
DB::table('users')
->whereJsonLength('optionsβlanguages', '>', 1)
->get();
Sometimes, you may need to load a large amount of data, but you don't need the hydrated models. In these scenarios, you can use the "toBase()" method provided by Laravel to save on memory usage π
<?php
// This collection will consist of PHP objects
// and will not include hydrated models
$users = User::toBase()->get();
Sometimes, you only need a single value instead of the entire row. Laravel comes with the "value()" method, which return the value of the column directly π
<?php
$email = DB::table('users')->where('name', 'John')->value('email');
dd($email); // [email protected]
Did you know that you can group resource controllers using the "resources()" method? It makes routing even cleaner! π
<?php
use App\Http\Controllers\PostsController;
use App\Http\Controllers\PhotosController;
// Instead of this
Route::resource('posts', PostsController::class);
Route::resource('photos', PhotosController::class);
// Do this
Route::resources([
'posts' => PostsController::class,
'photos' => PhotosController::class,
]);
Laravel v10.47.0 has just been released, featuring four new methods: "whereAll," "whereAny," "orWhereAll," and "orWhereAny." These methods allow you to compare a value against multiple columns π
<?php
$search = 'ous%';
// Instead of this
User::query()
->where(function($query) use ($search) {
$query
->where('first_name', 'LIKE', $search)
->where('last_name', 'LIKE', $search);
})
->get();
// You can now do this
User::query()
->whereAll(['first_name', 'last_name'], 'LIKE', $search)
->get();
User::query()
->whereAny(['first_name', 'last_name'], 'LIKE', $search)
->get();
// Which results in the following queries
// select * from `users` where (`first_name` LIKE 'ous%' and `last_name` LIKE 'ous%')
// select * from `users` where (`first_name` LIKE 'ous%' or `last_name` LIKE 'ous%')
// You can also use "orWhereAll" and "orWhereAny".
When building console commands, you can improve the user experience by implementing auto-completion for the user. This can be done using the "anticipate" method provided by Laravel π
<?php
// You can use arrays
$animal = $this->anticipate("What's your favourite animal?", ['dogs', 'cats']);
// Or run a closure, triggered every time a user types a character
$animal = $this->anticipate("What's your favourite animal?", function (string $input) {
return Animal::query()
->where('name', 'LIKE', "$input%")
->pluck('name')
->toArray();
});
Did you know that the Laravel scheduler allows you to email the output of a command to an email address of your choosing? π
<?php
$schedule->command(SendEmailsCommand::class)
->daily()
->emailOutputTo('[email protected]');
// You can also use `emailOutputOnFailure`.
Did you know that the Laravel authentication component comes with multiple events you can listen to? Whether a user attempts to log in, or fails to do so, you can handle these events as you wish π
<?php
protected $listen = [
Illuminate\Auth\Events\Registered::class => [],
Illuminate\Auth\Events\Attempting::class => [],
Illuminate\Auth\Events\Authenticated::class => [],
Illuminate\Auth\Events\Login::class => [],
Illuminate\Auth\Events\Failed::class => [],
Illuminate\Auth\Events\Validated::class => [],
Illuminate\Auth\Events\Verified::class => [],
Illuminate\Auth\Events\Logout::class => [],
Illuminate\Auth\Events\CurrentDeviceLogout::class => [],
Illuminate\Auth\Events\OtherDeviceLogout::class => [],
Illuminate\Auth\Events\Lockout::class => [],
Illuminate\Auth\Events\PasswordReset::class => [],
];
Did you know that you can use the "scan" helper to parse a string input into a collection according to a format supported by the built-in sscanf PHP function? π
<?php
$rgb = '#402A2A';
[$r, $g, $b] = str($rgb)->scan('#%2x%2x%2x');
// $r = 64, $g = 42, $b = 42
Did you know that you can conditionally hide console commands? This is useful when you are building packages and you want a command to be executed only once π
<?php
namespace Package\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Package\PackageServiceProvider;
class Install extends Command
{
protected $signature = 'package:install';
public function __construct()
{
parent::__construct();
if (file_exists(config_path('package-config.php'))) {
$this->setHidden();
}
}
public function handle()
{
Artisan::call('vendor:publish', ['--provider' => PackageServiceProvider::class]);
$this->info('Package was installed successfully π');
}
}
If you have optional columns in your table that you want to seed randomly, FakerPHP (what Laravel uses under the hood) allows for optional values out of the box π
<?php
fake()->optional()->randomDigit(); // a random digit, but also null sometimes
fake()->optional($weight = 0.1)->randomDigit(); // 90% chance of NULL
fake()->optional($weight = 0.9)->randomDigit(); // 10% chance of NULL
fake()->optional($weight = 10)->randomDigit; // 90% chance of NULL
fake()->optional($weight = 100)->randomDigit; // 0% chance of NULL
fake()->optional($weight = 0.5, $default = false)->randomDigit(); // 50% chance of FALSE
fake()->optional($weight = 0.9, $default = 'abc')->word(); // 10% chance of 'abc'
You can chain commands in your scheduler using the then()
method, an undocumented feature that I often find myself using π
<?php
$schedule->command('db:backup')
->daily()
->then(function(){
$this->command('notify:slack');
});
Laravel 11 has introduced a new helper called "once," which computes a result once and caches it. Subsequent calls will return the previously cached result. You can use this instead of static properties for better code readability! π
<?php
function random(): int
{
return once(function () {
return random_int(1, 1000);
});
}
random(); // 123
random(); // 123 (cached result)
random(); // 123 (cached result)
Laravel 11 has introduced a new way of testing failed, released or deleted jobs, which was a challenging in previous versions! You can now simply chain "withFakeQueueInteractions" on your job and assert one of the mentioned actions π
<?php
use App\Jobs\ProcessPodcast;
$job = (new ProcessPodcast)->withFakeQueueInteractions();
$job->handle();
$job->assertReleased(delay: 30);
$job->assertDeleted();
$job->assertFailed();
Did you know that Laravel allows you to define sequences when using factories? This makes setting up complex tests a breeze π
<?php
$users = User::factory()
->count(2)
->sequence(
['name' => 'First User'],
['name' => 'Second User'],
)
->create();
In Laravel versions 10 and below, we couldn't limit eager loaded relationships natively. Well, guess what? In Laravel 11, we can! π
<?php
User::with([
'posts' => fn ($query) => $query->limit(5)
])->paginate();
In Laravel versions 10 and below, retrying failed concurrent requests wasn't possible. Well, guess what? In Laravel 11, we can! π
<?php
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->as('first')->retry(2)->get('http://localhost/first'),
$pool->as('second')->retry(2)->get('http://localhost/second'),
$pool->as('third')->retry(2)->get('http://localhost/third'),
]);
In Laravel versions 10 and below, we had to define casts as properties which made it a bit messy to pass arguments. In Laravel 11, we can define casts as a method! π
<?php
namespace App\Models;
use App\Casts\Json;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
// Laravel β€ 10 π
protected $casts = [
'statuses' => AsEnumCollection::class.':'.ServerStatus::class,
];
// Laravel 11 π
protected function casts(): array
{
return [
'statuses' => AsEnumCollection::of(ServerStatus::class),
];
}
}
Did you know that Laravel ships with a method called withExists
which allows you to check if a model has a relationship or not? This is really helpful for conditional logics π
<?php
$user = User::query()
->withExists('posts as is_author')
->get();
/*
select
`users`.*,
exists (select * from `posts` where `users`.`id` = `posts`.`user_id`) as `is_author`
from `users`
*/
$user->is_author; // Will be either true or false.
Did you know that Laravel ships with the "whereKey" method? It makes your "where in" statements more readable, and well, you don't have to remember the name of the primary key π
<?php
// π Instead of doing this
Post::whereIn('id', [1,2,3])->get();
Post::whereNotIn('id', [1,2,3])->get();
// π You can do this
Post::whereKey([1,2,3])->get();
Post::whereKeyNot([1,2,3])->get();
I'm sure we've all encountered column name ambiguity when building queries at least once. To avoid that, you can use the "qualifyColumn" method, which prefixes the column with the table name π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Team extends Model
{
use HasFactory;
protected $fillable = [
'name',
];
public function servers(): HasMany
{
// Use qualifyColumn() to avoid collision
// with the 'name' column on the Team model
return $this->hasMany(Server::class)->orderBy(
(new Server)->qualifyColumn('name')
);
}
}
Did you know that Laravel ships with two helpers, "throw_if" and "throw_unless," which not only make your code shorter but also much more readable? π
<?php
// This is okay
if (!Auth::user()->isAdmin()) {
throw new AuthorizationException;
}
// This reads better
// Throw the exception if they're not the admin.
throw_if(!Auth::user()->isAdmin(), AuthorizationException::class);
// Throw the exception unless they're an admin.
throw_unless(Auth::user()->isAdmin(), AuthorizationException::class);
Did you know that Laravel ships with two cool helpers, "blank" and "filled"? You can now have a standardized way to test if a variable is empty or not, regardless of its type. Even collections are supported! π
<?php
// Will return true
blank('');
blank(' ');
blank(null);
blank(collect());
blank([]);
// Will return false
blank(0);
blank('string');
blank(true);
blank(false);
blank([1]);
// For the inverse, you can make use of filled()
Did you know that Laravel comes with a Lottery class that allows you to execute callbacks based on odds? I found this to be extremely helpful, especially when implementing A/B tests.
<?php
use Carbon\CarbonInterval;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Lottery;
// 1 out of every 100 slow queries will be reported.
DB::whenQueryingForLongerThan(
CarbonInterval::seconds(2),
Lottery::odds(1, 100)->winner(fn () => report('Querying > 2 seconds.')),
);
Sanctum is a powerful package for managing API tokens. Sometimes you may wish the token model had more methods. Well, guess what? You can extend the model and register your custom one instead! π
<?php
use App\Models\Sanctum\PersonalAccessToken;
use Laravel\Sanctum\Sanctum;
/**
* Bootstrap any application services.
*/
public function boot(): void
{
// Custom token model that adds new methods such as "lastUsedAt()"
Sanctum::usePersonalAccessTokenModel(PersonalAccessToken::class);
}
// Now this will return an instance of the custom model
$token = $request->user()->currentAccessToken();
Sometimes, you may want to retrieve all the models that do not have a relationship. While you can achieve this using a query builder, Laravel already ships with the "doesntHave" method, which reads so well π
<?php
use App\Models\Post;
// Posts that do not have the "comments" relationship
$posts = Post::doesntHave('comments')->get();
/*
select *
from `posts`
where
not exists (
select *
from comments
where
`posts`.`id` = `comments`.`post_id`
)
*/
Did you know that Laravel ships with a fluent wrapper for the PHP "sleep" method? This not only makes the code readable but also testable, as you can fake the Sleep class π
<?php
// Pause execution for 90 seconds
Sleep::for(1.5)->minutes();
// Pause execution for 2 seconds
Sleep::for(2)->seconds();
// Pause execution for 500 milliseconds
Sleep::for(500)->milliseconds();
// Pause execution for 5,000 microseconds
Sleep::for(5000)->microseconds();
// Pause execution until a given time
Sleep::until(now()->addMinute());
// Alias of PHP's native "sleep" function
Sleep::sleep(2);
// Alias of PHP's native "usleep" function
Sleep::usleep(5000);
// You can also chain methods
Sleep::for(1)->second()->and(10)->milliseconds();
Sometimes you may want to mute all events fired by a model. Laravel ships with the "withoutEvents" method that accomplishes exactly that π
<?php
use App\Models\User;
// This will mute all model events π€
$user = User::withoutEvents(function () {
User::findOrFail(1)->delete();
return User::find(2);
});
Did you know that Laravel allows you to unguard a model? While you can still use the forceFill method, this approach enables you to unguard multiple models at once, which is really useful for seeding the database π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
protected $fillable = ['name'];
}
Model::unguard();
// You can have *multiple* models unguarded
Flight::create([
'name' => 'flight',
'not_in_fillable' => true,
]);
Model::reguard();
Sometimes we want to ensure a collection has a single item. Instead of calling the count method on the collection, did you know there is an elegant method called "containsOneItem" that does the same? π
<?php
// Instead of this
collect([1])->count() === 1;
// You can do this
collect([1])->containsOneItem(); // true
collect([])->containsOneItem(); // false
collect([1, 2])->containsOneItem(); // false
I know we've all redirected users back at some point, and we typically use the "request()->back()" method. However, you can simply call the "back()" helper function π
<?php
// Instead of this
return redirect()->back($status = 302, $headers = [], $fallback = '/');
// You can do this
return back($status = 302, $headers = [], $fallback = '/');
We all use many-to-many relationships. In those cases, the intermediate table is accessed via the pivot attribute. Renaming it to something more expressive can make the code more readable π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class User extends Model
{
public function podcasts(): BelongsToMany
{
return $this->belongsToMany(Podcast::class)
->as('subscription')
->withTimestamps();
}
}
$users = User::with('podcasts')->get();
foreach ($users->flatMap->podcasts as $podcast) {
// Instead of $podcast->pivot->created_at
echo $podcast->subscription->created_at;
}
Sometimes, you may want to limit the number of words in a string. Well, Laravel comes with a handy helper "words" to do exactly that! π
<?php
use Illuminate\Support\Str;
return Str::words("Don't refactor without tests.", 2);
// Don't refactor ...
return Str::words("Don't refactor without tests.", 2, ' π');
// Don't refactor π
// You can also use fluent string π
return str("Don't refactor without tests.")->words(2);
// Don't refactor ...
Sometimes we need to update a value by incrementing or decrementing it. Usually, we would write a query to achieve this, but Laravel comes with elegant methods to do so π
<?php
// Increment votes by 1
DB::table('users')->increment('votes');
// Increment votes by 5
DB::table('users')->increment('votes', 5);
// Decrement votes by 1
DB::table('users')->decrement('votes');
// Decrement votes by 5
DB::table('users')->decrement('votes', 5);
// Increment votes by 1 and set the name to John
DB::table('users')->increment('votes', 1, ['name' => 'John']);
// You can increment multiple columns at once
DB::table('users')->incrementEach([
'votes' => 5, // Will increment votes by 5
'balance' => 100, // Will increment balance by 100
]);
// You can also use them with Eloquent
User::query()->incrementEach([
'votes' => 5, // Will increment votes by 5
'balance' => 100 // Will increment balance by 100
]);
The Laravel HTTP Client uses Guzzle under the hood, providing access to statistics for each request you make, including total time, download speed and much more π
<?php
$stats = Http::get('https://oussama-mater.tech')->handlerStats();
/*
All the statistics related to the request:
[
"http_code" => 200
"total_time" => 8.430478
"speed_download" => 135.0
"speed_upload" => 0.0
...
]
*/
Did you know that Laravel factories allow you to define states? You can use multiple states to describe the object and apply discrete modifications to it. This also makes the code more readable! π
<?php
use Illuminate\Database\Eloquent\Factories\Factory;
// Define the state in the user factory
public function suspended(): Factory
{
return $this->state(function (array $attributes) {
return [
'account_status' => Status::SUSPENDED,
];
});
}
// Now you can use it like so
$user = User::factory()->suspended()->create();
Did you know that Laravel 9.32 and above come with a handy helper called "Benchmark", allowing you to measure the number of milliseconds it takes for the given callbacks to complete? π
<?php
use App\Models\User;
use Illuminate\Support\Benchmark;
// This will measure and return the value
Benchmark::measure(fn () => User::find(1)); // 0.1 ms
// This will measure and dump the value
Benchmark::dd(fn () => User::find(1)); // 0.1 ms
// For both measure() and dd(), you can pass an array
Benchmark::dd([
'Scenario 1' => fn () => User::count(), // 0.5 ms
'Scenario 2' => fn () => User::all()->count(), // 20.0 ms
]);
// You can also save the values
[$count, $duration] = Benchmark::value(fn () => User::count());
When using Laravel Model Binding, you may wish to include the soft deleted models as well. Luckily, Laravel comes with a handy routing method called "withTrashed" to do exactly that π
<?php
use App\Models\User;
// Now soft-deleted users will be included.
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
Sometimes, we wish to check if the value of a given model key has been affected by a change or not. Laravel ships with the "originalIsEquivalent()" method to do exactly that π
<?php
$user = User::firstOrFail(); // ['name' => 'old']
$user->name = 'old'; // Keep the old value
$user->originalIsEquivalent('name'); // true
$user->name = 'new'; // Change the value
$user->originalIsEquivalent('name'); // false
When using collections, sometimes we want to retrieve the value of an existing key but insert it if it does not exist. While this can be achieved using the "get" and "put" methods, Laravel offers a handy method called "getOrPut" that does the same π
$collection = collect(['price' => 100]);
// Check and set value if not exists
if (!$collection->has('name')) {
$collection->put('name', 'Desk');
}
$value = $collection->get('name');
// Or use getOrPut() for the same operation
$value = $collection->getOrPut('name', 'Desk');
When working with Laravel collections, you may want to flatten a multi-dimensional collection into a single-level collection, or vice versa. Luckily, there are 2 methods just for that, "dot" and "undot" π
$collection = collect(['products' => ['desk' => ['price' => 100]]]);
$dotted = $collection->dot(); // ['products.desk.price' => 100]
$undotted = $collection->undot(); // ['products' => ['desk' => ['price' => 100]]]
Did you know that Laravel ships with a cool collection method "times," which allows you to create a collection by invoking a closure N times? This could be helpful when working with days or generating random strings π
$collection = Collection::times(10, function (int $number) {
return $number * 9;
});
$collection->all();
// [9, 18, 27, 36, 45, 54, 63, 72, 81, 90]
Sometimes, when defining factories, you may want to pick a random element from an array. Since Laravel uses FakerPHP under the hood, you can do this by calling the "randomElement" method π
// Get a random subscription type
$random = fake()->randomElement(['basic', 'premium']);
// Get a random letter
$random = fake()->randomElement(['a', 'b', 'c']);
Did you know that Laravel comes with the "withFragment" method, which allows you to add a URI fragment when redirecting? π
<?php
return redirect()
->back()
->withFragment('testimonials');
// exmaple.com/#testimonials
return redirect()
->route('product.show')
->withFragment('reviews');
// exmaple.com/product/1#reviews
When defining gates or policies, for security reasons, we often opt to return a 404 instead of a 403. Laravel provides the "denyAsNotFound()" method for this purpose π
<?php
use App\Models\User;
use Illuminate\Auth\Access\Response;
use Illuminate\Support\Facades\Gate;
Gate::define('edit-settings', function (User $user) {
return $user->isAdmin
? Response::allow()
: Response::denyAsNotFound();
});
Ever found yourself in need of masking an email or a verification code for security purposes? Laravel ships with the "mask" method to do exactly that! You can always combine it with other methods to avoid leaking the length of the string π
<?php
use Illuminate\Support\Str;
Str::mask('[email protected]', '*', 3); // joh*************
Str::mask('98552157', '*', -5, 4); // 985****7
If you work with numbers, the longer they are, the harder they are to read. Did you know you can use underscores for better readability? π
<?php
$amount = 1000;
$amount = 1_000;
$amount = 100000;
$amount = 100_000;
$amount = 100000000;
$amount = 100_000_000;
Have you ever wanted to display human-readable dates instead of exact ones? Like "1 day ago" or "a month ago"? Laravel allows you to do exactly that by using the "diffForHumans" method π
<?php
// 5 days ago
$post->created_at->diffForHumans();
// 5 days 23 minutes ago
$post->created_at->diffForHumans(['parts' => 2]);
// 5 days 24 minutes 36 seconds ag
$post->created_at->diffForHumans(['parts' => 3]);
Did you know that @pestphp ships with "Higher Order Expectations"? It allows you to perform expectations on the properties and/or methods of the given object. This results in a much cleaner code
<?php
// Instead of this π«
expect($user->name)->toBe('John');
expect($user->country)->toBe('us');
expect($user->isAdmin())->toBeFalse();
// Do this π
expect($user)
->name->toBe('John')
->country->toBe('us')
->isAdmin()->toBeFalse();
One of the cool features in Pest is "intercepting" exceptions. You can override built-in expectations with your own implementation and still fallback to the default behavior for regular cases π
<?php
use Illuminate\Database\Eloquent\Model;
use App\Models\User;
// In your tests/Expectations.php file
expect()->intercept('toBe', Model::class, function (Model $expected) {
expect($this->value->is($expected))
// You can also set a custom message
->toBeTrue(message: 'Failed asserting that both models are the same.');
});
// Now you can use it in your tests π
test('models', function () {
$user = User::find(1);
$sameUser = User::find(1);
expect($user)->toBe($sameUser);
});
// It will still work as it used to π
test('old expectation', function () {
expect('hello-world')->toBe('hello-world');
});
Sometimes, when dealing with nested loops, you may want to keep track of the parent's iteration. Blade makes it incredibly easy, as you have access to the parent loop variable π
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
// This is the first iteration of the parent loop.
@endif
@endforeach
@endforeach
Sometimes, you may want to redirect your users away from your Laravel application. Luckily, there is a helper method called "away" that allows you to do exactly that π
<?php
redirect()->away('https://blog.oussama-mater.tech');
We often test whether or not a request parameter is filled to decide to perform logic. Laravel ships with a cool helper called "whenFilled" to do just that! π
<?php
$request->whenFilled('name', function (string $input) {
// The "name" value is filled...
}, function () {
// The "name" value is not filled...
});
When working with time, you may want to have a sharp hour, like 18:00:00. Since Laravel uses Carbon under the hood, you have access to "startOfHour," which does exactly that π
<?php
// 2024-04-26 18:46:39
echo now();
// 2024-04-26 18:46:00
echo now()->setSeconds(0);
// 2024-04-26 18:00:00
echo now()->setSeconds(0)->setMinutes(0);
// 2024-04-26 18:00:00
echo now()->startOfHour();
Laravel supports "Higher Order Messages" with collections, which are cool shortcuts that we use. But did you know that you can make use of them when writing eloquent queries? π
// tip-100.php
<?php
// Instead of this π«
User::popular()->orWhere(function (Builder $query) {
$query->active();
})->get()
// You can do this π
User::popular()->orWhere->active()->get();
Did you know that Blade allows for short attribute syntax when passing to components? π
// Instead of this π«
<x-profile :user-id="$userId"></x-profile>
// You can do this π
<x-profile :$userId></x-profile>
When using a whereIn query with non-user input, opt for whereIntegerInRaw. This speeds up your query by skipping PDO bindings and Laravel's security measures against SQL injection π
<?php
// Instead of using whereIn()
Product::whereIn('id', range(1, 10000))->get();
// Use WhereIntegerInRaw()
Product::whereIntegerInRaw('id', range(1, 10000))->get();
When defining factories, you may want to use a single model for all the relationships instead of creating a new one for each of them. Laravel ships with a cool method "recycle" to do exactly that π
<?php
$airlines = Airline::factory()->count(3)->create();
// Only 3 airlines will be used for the 100 tickets
Ticket::factory()
->count(100)
->recycle($airlines) // You can pass a single one
->create();
Sometimes you may wish to update a bunch of records or create them if they do not exist. Laravel ships with a cool method "upsert" to do exactly that π
<?php
/*
This will update the price of all the records that match
the given departure and destination or create them
*/
Flight::upsert([
['departure' => 'Oakland', 'destination' => 'San Diego', 'price' => 99],
['departure' => 'Chicago', 'destination' => 'New York', 'price' => 150]
], uniqueBy: ['departure', 'destination'], update: ['price']);
Sometimes when you write tests, you might end up with some flaky ones that are just unstable; those tests fail once in a thousand. PestPHP comes with a really cool helper, "repeat," to catch those π
<?php
it('can repeat a test', function() {
$result = /** Some code that may be unstable */;
expect($result)->toBeTrue();
})->repeat(100); // Repeat the test 100 times
Did you know that Laravel ships with the "view" method, which allows you to render a view directly without having to define a closure? It's much more elegant! π
<?php
use Illuminate\Support\Facades\Route;
// Instead of this π₯±
Route::get('/welcome', function () {
return view('welcome', ['foo' => 'bar']);
});
// You can do this π
Route::view('/welcome', 'welcome', ['foo' => 'bar']);
Sometimes, your table might not have the "created_at" and "updated_at" columns. You can instruct Laravel not to update them by setting "timestamps" to false π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Flight extends Model
{
/**
* Indicates if the model should be timestamped.
*
* @var bool
*/
public $timestamps = false;
}
Sometimes, when paginating your models, you may only need a "next" and "previous" buttons. For this, you can use the "simplePaginate" method instead of the regular paginate π
<?php
$users = User::paginate(15);
/*
Will result in the following keys
[
"current_page",
"data",
"first_page_url",
"from",
"last_page",
"last_page_url",
"links",
"next_page_url",
"path",
"per_page",
"prev_page_url",
"to",
"total",
];
*/
$users = User::simplePaginate(15);
/*
Will result in the following keys
[
"current_page",
"data",
"first_page_url",
"from",
"next_page_url",
"path",
"per_page",
"prev_page_url",
"to",
];
*/
Did you know that Laravel automatically boots your traits if they follow the boot[TraitName]
convention? This allows you to easily define shared logic for model events. And here's a secret: that's where multi-tenancy starts π
<?php
trait Sluggable
{
public static function bootSluggable()
{
static::saving(function ($model) {
$model->slug = str($model->title)->slug()->toString();
});
}
}
class Post extends Model
{
use Sluggable; // The trait will be booted automatically
}
Did you know you can use Blade to render views as strings wherever you want? This is helpful as you can use Blade to build dynamic strings or even shell scripts, similar to how Envoy does π
<?php
// This renders the blade file welcome.blade.php into an HTML string
$rendered = view('welcome', ['foo' => 'bar'])->render();
When jobs fail, you may want to send notifications or perform some cleanups. Luckily, Laravel allows you to define a "failed" method to do exactly that π
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Throwable;
class ProcessPodcast implements ShouldQueue
{
use InteractsWithQueue, Queueable, SerializesModels;
// ...
public function failed(?Throwable $exception): void
{
// Handle job failure, such as sending notifications
}
}
Did you know that you can schedule jobs based on specific time zones? You can do so by chaining the "timezone" method π
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->timezone('Africa/Tunis')
->at('9:00')
Sometimes you may need singleton resources that can't be created but only shown or edited, like a user profile, for example. Laravel ships with a singleton helper to allow you to define these routes π
// Image 4: tip-114.php
<?php
use App\Http\Controllers\ProfileController;
use Illuminate\Support\Facades\Route;
Route::singleton('profile', ProfileController::class);
/*
This will result in the following routes:
GET /profile
GET /profile/edit
PUT/PATCH /profile
*/
Did you know that Laravel ships with the "once" method for authenticating the user only for the current request? This can be handy in various scenarios, such as building single-use links or RESTful APIs π
<?php
if (Auth::once($credentials)) {
// ...
}
Sometimes you may want to check if certain records do not exist in the database. While checking the count or using the exists() method can do the trick, Laravel ships with the "doesntExist" method to do it elegantly π
// Image 5: tip-116.php
<?php
// This is okay π
if (User::count() === 0) {
}
// This is good π
if (! User::exists()) {
}
// This is better π
if (User::doesntExist()) {
}
Sometimes you may need to reuse the same base query for multiple filtering. Laravel ships with a "clone" method to do exactly that π
<?php
// Base query, common conditions
$query = User::query()->where('created_at', '<', now()->subMonths(3));
$verified_users = $query->clone()->whereNotNull('email_verified_at')->get();
// This can be customized further if needed
$unverified_users = $query->clone()->whereNull('email_verified_at')->get();
When writing tests, we often use assertDatabaseMissing
to check whether a model has been deleted. Did you know that Laravel ships with a cool helper called assertModelMissing
to do exactly that? π
<?php
use App\Models\User;
$user = User::factory()->create();
$user->delete();
// Instead of this π₯±
$this->assertDatabaseMissing('users', [
'email' => $user->email,
]);
// You can do this π
$this->assertModelMissing($user);
Sometimes you may want to ensure that the collection items are all of a specific type. While map
paired with an instanceof
check might do the trick, Laravel already ships with the ensure
method to do that π
<?php
// Instead of this π₯±
return $collection->each(function ($item) {
if (!$item instanceof User) {
throw new UnexpectedValueException('π');
}
});
// You can do this π
return $collection->ensure(User::class);
// Or allow multiple types, which is equivalent to OR
return $collection->ensure([User::class, Customer::class]);
// You can also ensure primitive types
return $collection->ensure('int');
Sometimes, when validating a request, you may want to stop at the first failure. Laravel allows you to do this by setting the "stopOnFirstFailure" property to true on the form request class π
<?php
/**
* Indicates if the validator should stop on the first rule failure.
*
* @var bool
*/
protected $stopOnFirstFailure = true;
Sometimes, when validating a field, you may want to stop at the first validation failure. Laravel ships with a rule called "bail" to do exactly that π
<?php
// When validating the title, if the required rule fails,
// Laravel won't test against the other rules.
$request->validate([
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);
Since Laravel uses FakerPHP under the hood, you can use the "valid()" modifier to ensure that the generated fake data follows certain rules π
<?php
// This will only generate even numbers
$evenNumber = fake()->valid(fn (int $digit) => $digit % 2 === 0)->randomDigit();
Sometimes, you may want to hide model attributes that were not defined in the "hidden" array. Laravel allows you to do this on the fly using the "makeHidden" method π
<?php
$users = User::all()->makeHidden(['address', 'phone_number']);
Eloquent API resources are automatically wrapped in a "data" object. Sometimes, you may want to remove this wrapping. Laravel includes the "withoutWrapping" method to do exactly that π
<?php
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
JsonResource::withoutWrapping();
}
}
/*
[
{
"id": 1,
"name": "Eladio Schroeder Sr.",
"email": "[email protected]"
},
{
"id": 2,
"name": "Liliana Mayert",
"email": "[email protected]"
}
]
*/
Usually, when working with the HTTP Client, we manually collect the JSON response from the API. However, did you know that Laravel ships with the "collect" method directly on the HTTP Client? π
<?php
use Illuminate\Support\Facades\Http;
// Instead of this π
$response = Http::get('http://example.com')->json();
collect($response);
// Do this π
$collection = Http::get('http://example.com')->collect();
When working with nested arrays, Laravel provides a cool helper called "data_get". This helper allows you to use "dot" syntax and wildcards to retrieve values π
<?php
$data = [
'product-one' => ['name' => 'Desk 1', 'price' => 100],
'product-two' => ['name' => 'Desk 2', 'price' => 150],
];
data_get($data, 'product-one.name'); // ['Desk 1'];
data_get($data, '*.name'); // ['Desk 1', 'Desk 2'];
We often need to check if a request contains certain values. Did you know that Laravel ships with two cool methods, "has" and "hasAny", to perform these checks elegantly? π
<?php
// True if ALL of the values are present
if ($request->has(['name', 'email'])) {
//
}
// True if ANY of the values are present
if ($request->hasAny(['name', 'email'])) {
//
}
Did you know that you can bind typed variadics to the container? Laravel ships with 3 methods to allow you to do so: "when()", "needs()", and "give()". You can keep using DI without worries! π
<?php
use App\Models\Filter;
use App\Services\Logger;
class Firewall
{
protected array $filters;
public function __construct(
protected Logger $logger,
Filter ...$filters,
) {
$this->filters = $filters;
}
}
// In the service provider
$this->app->when(Firewall::class)
->needs(Filter::class)
->give(function (Application $app) {
return [
$app->make(NullFilter::class),
$app->make(ProfanityFilter::class),
$app->make(TooLongFilter::class),
];
});
Sometimes you may want to check if every element in the collection passes a condition. Luckily, Laravel ships with the "every" method to do exactly that π
<?php
$result = collect([1, 2, 3])->every(fn (int $value, int $key) => $value > 2);
// $result will be false
$result = collect([])->every(fn (int $value, int $key) => $value > 2);
// Since the collection is empty, $result will be true
Sometimes, when working with collections, you may want to remove an element by its key. Luckily, collections come with the "forget" method to do exactly that π
<?php
$collection = collect(['name' => 'John Doe', 'framework' => 'laravel']);
$collection->forget('name');
$collection->all(); // ['framework' => 'laravel']
Did you know that you can spell numbers in different locales using the "Number" helper that Laravel ships with? π
<?php
use Illuminate\Support\Number;
$number = Number::spell(102); // one hundred and two
$number = Number::spell(88, locale: 'fr'); // quatre-vingt-huit
Sometimes you may want to format numbers for your users in a human-readable format. The "Number" helper allows you to do just that π
<?php
use Illuminate\Support\Number;
$number = Number::forHumans(1000); // 1 thousand
$number = Number::forHumans(489939); // 490 thousand
$number = Number::forHumans(1230000, precision: 2); // 1.23 million
Sometimes, when working with collections, you may want to skip all the elements until a condition is met. Laravel comes with the "skipUntil" method to do exactly that π
<?php
$collection = collect([1, 2, 3, 4]);
$subset = $collection->skipUntil(function (int $item) {
return $item >= 3;
});
$subset->all(); // [3, 4]
When working with collections, you might want to merge two collections by their index, combining the values of the first index, then the second, and so on. Luckily, Laravel includes the "zip" method to do exactly that π
<?php
$collection = collect(['Chair', 'Desk']);
// This will merge values by index, so "Chair" with 100, and "Desk" with 200
$zipped = $collection->zip([100, 200]);
$zipped->all(); // [['Chair', 100], ['Desk', 200]]
While working with collections, you might want to execute some logic when the collection is not empty. Instead of manually checking, Laravel ships with a cool method, "whenNotEmpty()", to do exactly that π
<?php
$collection = collect(['michael', 'tom']);
$collection->whenNotEmpty(function (Collection $collection) {
return $collection->push('adam');
});
$collection->all(); // ['michael', 'tom', 'adam']
$collection = collect(); // empty collection
$collection->whenNotEmpty(function (Collection $collection) {
return $collection->push('adam');
});
$collection->all(); // []
Laravel's HTTP client wraps Guzzle, which allows you to make concurrent requests to speed things up. This is very helpful for various cases, such as health checks! π
<?php
use Illuminate\Http\Client\Pool;
use Illuminate\Support\Facades\Http;
$responses = Http::pool(fn (Pool $pool) => [
$pool->get('http://localhost/first'),
$pool->get('http://localhost/second'),
$pool->get('http://localhost/third'),
]);
return $responses[0]->ok() &&
$responses[1]->ok() &&
$responses[2]->ok();
Did you know that Laravel allows you to search collection items? You can even pass a condition to search for the first element that meets it π
<?php
$collection = collect([2, 4, 6, 8]);
$collection->search('4'); // 1 (the index)
$collection->search('4', strict: true); // false (not found)
$collection->search(fn (int $item, int $key) => $item > 5); // 2 (the index)
Laravel allows you to apply global scopes to your models, but sometimes you may wish to disable them for a specific query. You can do this by chaining the "withoutGlobalScope" method π
<?php
// Remove all of the global scopes
User::withoutGlobalScopes()->get();
// Remove a single global scope
User::withoutGlobalScope(FirstScope::class)->get();
// Remove some of the global scopes
User::withoutGlobalScopes([
FirstScope::class, SecondScope::class
])->get();
When Laravel's maintenance mode is on, all scheduled commands won't run. If you wish to change this behavior, you can chain the "evenInMaintenanceMode()" method π
<?php
// Command will run even when maintenance mode is on
Schedule::command('emails:send')->evenInMaintenanceMode();
Scheduled commands run sequentially. If you have a long-running task, it could take longer than anticipated and cause a delay for other tasks. Luckily, in such cases, you can use the "runInBackground" method π
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('analytics:report')
->daily()
->runInBackground();
Did you know that Laravel ships with a cool helper called "literal" that allows you to create a PHP object using named arguments? π
<?php
$obj = literal(
name: 'Joe',
languages: ['PHP', 'Ruby'],
);
$obj->name; // 'Joe'
$obj->languages; // ['PHP', 'Ruby']
When writing middlewares, we often abort the request if a condition is met. For such cases, "abort_if" allows you to do exactly that! π
<?php
// Instead of this π
if (!Auth::user()->isAdmin()) {
abort(403);
}
// Do this π
abort_if(!Auth::user()->isAdmin(), 403);
When working with collections, whether regular or Eloquent, if you want to get the first item that matches the condition and ensure it is the only one, use the "sole" method π
<?php
// Returns 2
collect([1, 2, 3, 4])->sole(fn (int $value, int $key) => $value === 2);
// Throws: Illuminate\Support\MultipleItemsFoundException 2 items were found.
collect([1, 2, 2, 4])->sole(fn (int $value, int $key) => $value === 2);
When creating users, we often use the Hash facade, but did you know that Laravel comes with a "hashed" cast that will automatically hash your user's password? π
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected function casts(): array
{
return [
'password' => 'hashed',
];
}
}
$user = User::create([
// ...
'password' => 'password', // Instead of Hash::make('password')
]);
Sometimes, you may want to execute some actions when no record is found, beyond just creating a new instance. The "createOr" method allows you to do exactly that π
<?php
$user = User::where('email', $request->input('email'))->firstOr(function () {
// Execute some logic
// Create and return a new user
return User::create([
// ...
]);
});
When working with files, you may need to prepend or append content. Luckily, Laravel ships with two helpers to do exactly that π
<?php
Storage::prepend('file.log', 'Prepended Text');
Storage::append('file.log', 'Appended Text');
Did you know Laravel ships with a "percentage" helper to get the percentage of any representative value? π
<?php
use Illuminate\Support\Number;
$percentage = Number::percentage(10); // 10%
$percentage = Number::percentage(10, precision: 2); // 10.00%
$percentage = Number::percentage(10.123, maxPrecision: 2); // 10.12%
$percentage = Number::percentage(10, precision: 2, locale: 'de'); // 10,00%
Did you know Laravel ships with a "fileSize" helper to get the file size representation of a given byte value as a string? π
<?php
use Illuminate\Support\Number;
$size = Number::fileSize(1024); // 1 KB
$size = Number::fileSize(1024 * 1024); // 1 MB
$size = Number::fileSize(1024, precision: 2); // 1.00 KB
When writing tests, we sometimes need to "freeze" time to make assertions. Laravel provides an elegant method "freezeTime" to do exactly that π
<?php
// Instead of this π₯±
Carbon::setTestNow(now());
// Do this π
$this->freezeTime();
When users log out, you might want to ask them if they want to log out from other devices while keeping the current one. Luckily, Laravel ships with the "logoutOtherDevices" method that does exactly that π
<?php
use Illuminate\Support\Facades\Auth;
Auth::logoutOtherDevices($currentPassword);
We often work with Base64 strings, especially when building API integrations. Laravel comes with built-in helpers to work with Base64 right out of the box π
<?php
use Illuminate\Support\Str;
$base64 = Str::toBase64('Laravel'); // TGFyYXZlbA==
$base64 = str('Laravel')->toBase64(); // TGFyYXZlbA==
$base64 = Str::fromBase64('TGFyYXZlbA=='); // Laravel
$base64 = str('TGFyYXZlbA==')->fromBase64(); // Laravel
When writing middlewares, we often need to abort the request if a condition is met. For such cases, "abort_unless" allows you to do exactly that! π
<?php
// Instead of this π₯±
if (!Auth::user()->isAdmin()) {
abort(403);
}
// Do this π
abort_unless(Auth::user()->isAdmin(), 403);
We often order models in ascending or descending order using the "orderBy" method. But did you know that Laravel comes with two methods, "latest" and "oldest," that do exactly that? π
<?php
// Instead of this π₯±
User::orderBy('created_at', 'desc')->get();
User::orderBy('created_at', 'asc')->get();
// Do this π
User::latest()->get();
User::oldest()->get();
// You can specify keys other than created_at
User::latest('id')->get();
User::oldest('id')->get();
Did you know that Laravel ships with a middleware "SetCacheHeaders", which you can use to set cache headers, such as "max_age" and "etag"? π
<?php
Route::middleware('cache.headers:public;max_age=2628000;etag')->group(function () {
Route::get('/privacy', function () {
// ...
});
Route::get('/terms', function () {
// ...
});
});
Sometimes, you might want to ignore errors when inserting data. Laravel comes with the "insertOrIgnore" method that does exactly that π
<?php
DB::table('users')->insertOrIgnore([
['id' => 1, 'email' => '[email protected]'],
['id' => 2, 'email' => '[email protected]'],
]);
// insert ignore into `users` (`email`, `id`) values (?, ?), (?, ?)
Did you know that the Laravel Scheduler allows you to execute commands in the operating system? π
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::exec('node /home/forge/script.js')->daily();
If you are using static analysis tools like PHPStan (which you should), you can make use of typed configs. Not only does this assist the tools, but it also ensures that the config matches the required type π
<?php
Config::string('config-key'); // config()->string('config-key')
Config::integer('config-key'); // config()->integer('config-key')
Config::float('config-key'); // config()->float('config-key')
Config::boolean('config-key'); // config()->boolean('config-key')
Config::array('config-key'); // config()->array('config-key')
Did you know that Laravel ships with the "plural" method to help you pluralize words? This is really helpful when you want to show a quantity-dependent message to your users. π
<?php
use Illuminate\Support\Str;
// The count is optional, if 1 is passed, the same word will be returned
$plural = Str::plural('car', count: 2); // cars
// Also available as a fluent method
$plural = str('child')->plural(); // children
Since Laravel uses FakerPHP under the hood, you can generate fake user agents for your tests. π
<?php
echo fake()->userAgent();
// Mozilla/5.0 (iPad; CPU OS 8_0_2 like Mac OS X; sl-SI) AppleWebKit/532.22.4 (KHTML, like Gecko) Version/3.0.5 Mobile/8B114 Safari/6532.22.4
echo fake()->chrome();
// Mozilla/5.0 (X11; Linux i686) AppleWebKit/5352 (KHTML, like Gecko) Chrome/38.0.899.0 Mobile Safari/5352
echo fake()->firefox();
// Mozilla/5.0 (Windows 98; Win 9x 4.90; sl-SI; rv:1.9.1.20) Gecko/20220314 Firefox/35.0
echo fake()->safari();
// Mozilla/5.0 (Windows; U; Windows NT 5.1) AppleWebKit/531.4.3 (KHTML, like Gecko) Version/5.1 Safari/531.4.3
echo fake()->opera();
// Opera/8.71 (Windows NT 4.0; nl-NL) Presto/2.11.217 Version/12.00
echo fake()->internetExplorer();
// Mozilla/5.0 (compatible; MSIE 5.0; Windows NT 5.1; Trident/4.0)
echo fake()->msedge();
// Mozilla/5.0 (iPhone; CPU iPhone OS 13_1 like Mac OS X) AppleWebKit/535.2 (KHTML, like Gecko) Version/15.0 EdgiOS/98.01106.3 Mobile/15E148 Safari/535.2
Did you know you can ping URLs before and after your command has run? This is really useful if you want to notify an external service or a webhook. π
<?php
Schedule::command('emails:send')
->daily()
->pingBefore($webhookUrl)
->thenPing($webhookUrl);
Did you know that you can pass multiple IDs to the find()
method? Laravel also ships with a slightly more readable method, findMany()
, which does the same thing! π
<?php
// Instead of this
$users = User::query()->whereIn('id', [1, 2, 3])->get();
// Do this
$users = User::find([1, 2, 3]);
// Even better, find() calls findMany() internally, so skip it and be expli
$users = User::findMany([1, 2, 3]);
Did you know Laravel ships with the isDirty()
method, which allows you to check if one or more attributes have changed since the last time you retrieved the model? π
<?php
use App\Models\User;
$user = User::create([
'first_name' => 'John',
'last_name' => 'Doe',
'age' => 20,
]);
$user->age = 21;
$user->isDirty(); // true
$user->isDirty('age'); // true
$user->isDirty('first_name'); // false
$user->isDirty(['first_name', 'age']); // true
$user->isDirty(['first_name', 'last_name']); // false
$user->save();
$user->isDirty(); // false
Did you know that Laravel ships with the "logoutCurrentDevice" method, which allows you to log out only the currently authenticated device? π
<?php
use Illuminate\Support\Facades\Auth;
Auth::logoutCurrentDevice();
Sometimes, you might need to customize the default timestamp columns, or you might already have an old table for which you are creating a model. Luckily, it is super simple to do so π
<?php
class Flight extends Model
{
const CREATED_AT = 'creation_date';
const UPDATED_AT = 'updated_date';
}
Did you know you can configure Laravel to throw an exception when attempting to fill an unfillable attribute? This is helpful during development to catch missed or forgotten attributes before getting to prod π
<?php
use Illuminate\Database\Eloquent\Model;
Model::preventSilentlyDiscardingAttributes(! $this->app->isProduction());
We've all been in a situation where we want to check if a record exists so we can update it, or create it if it does not. Laravel ships with the updateOrCreate
method to do exactly that π
<?php
$flight = Flight::updateOrCreate(
// If we can't find a flight with the following criteria,
['departure' => 'Oakland', 'destination' => 'San Diego'],
// we will create it with the following data
['price' => 99, 'discounted' => 1]
);
Did you know that Laravel ships with the destroy
method, which allows you to delete records by their primary key? π
<?php
// Instead of this π’
Flight::find(1)->delete()
// Do this π
Flight::destroy(1);
Flight::destroy(1, 2, 3); // works with variadic arguments
Flight::destroy([1, 2, 3]); // arrays
Flight::destroy(collect([1, 2, 3])); // and also collections!
Did you know that Laravel comes with a cool time helper that allows you to travel into the future or the past in your tests? π
<?php
// Travel into the future...
$this->travel(5)->milliseconds(); // Travel 5 milliseconds into the future
$this->travel(5)->seconds();
$this->travel(5)->minutes();
$this->travel(5)->hours();
$this->travel(5)->days();
$this->travel(5)->weeks();
$this->travel(5)->years();
// Travel into the past...
$this->travel(-5)->hours(); // Travel 5 years into the past
// Travel to an explicit time...
$this->travelTo(now()->subHours(6));
// Return back to the present time...
$this->travelBack();
Sometimes you may need to apply casts while executing a query. Luckily, you can do that on the fly with the "withCasts" method that Laravel ships with π
<?php
$users = User::select([
'users.*',
'last_posted_at' => Post::selectRaw('MAX(created_at)')
->whereColumn('user_id', 'users.id')
])->withCasts([
// Casting the raw string 'last_posted_at' to a datetime
'last_posted_at' => 'datetime'
])->get();
When working with validated input, we often need to extract only a few items from the request. Instead of manually unsetting or filtering, use Laravel's "safe()" method to do this elegantly π
<?php
$validated = $request->safe()->only(['name', 'email']);
$validated = $request->safe()->except(['name', 'email']);
$validated = $request->safe()->all();
We often use the "firstOrFail" method to get a single value from the resulting model. Did you know that Laravel ships with the "valueOrFail" method, which allows you to do exactly that? π
<?php
// Instead of this
$flight = Flight::where('legs', '>', 3)->firstOrFail();
$flightName = $flight->name;
// Do this
$flightName = Flight::where('legs', '>', 3)->valueOrFail('name');
We often redirect our users to specific routes using the "redirect()" method. Did you know there is a shorter and more expressive method called "to_route"? π
<?php
// Instead of this π₯±
return redirect()->route('profile');
// You can do this π
return to_route('profile');
Did you know that Laravel ships with a "password" method that generates random, strong passwords? This is helpful when you want to suggest passwords for your users π
<?php
use Illuminate\Support\Str;
// The password will include letters, numbers, symbols, and spaces.
// The default length is 32 characters.
$password = Str::password(); // '?;D7zlsMmZ87R0aBmIH.>GU77nagX26U'
$password = Str::password(12); // 'q_2j00<#gr{'
Did you know that Laravel ships with the "abbreviate" method, which allows you to format numbers in a human-readable way, with an abbreviation for the units? π
<?php
use Illuminate\Support\Number;
$number = Number::abbreviate(1000); // 1K
$number = Number::abbreviate(489939); // 490K
$number = Number::abbreviate(1230000, precision: 2); // 1.23M
Laravel 11.14 introduces 2 new string helpers that allow you to remove characters from the beginning or end of a string π
<?php
use Illuminate\Support\Str;
$url = Str::chopStart('http://laravel.com', ['https://', 'http://']); // 'laravel.com'
$url = Str::chopEnd('app/Models/Photograph.php', '.php'); // 'app/Models/Photograph'
When working with dynamic forms, you might want to validate certain inputs only if another input is checked. Laravel ships with the "exclude_if" validation rule, which does exactly that π
<?php
use Illuminate\Support\Facades\Validator;
// 'appointment_date' and 'doctor_name' will not be validated if
// 'has_appointment' is false
$validator = Validator::make($data, [
'has_appointment' => 'required|boolean',
'appointment_date' => 'exclude_if:has_appointment,false|required|date',
'doctor_name' => 'exclude_if:has_appointment,false|required|string',
]);
When working with strings, we often need to find and replace occurrences of multiple strings. Laravel ships with an elegant method "swap" to do exactly that π
<?php
use Illuminate\Support\Str;
// The swap method is also available as a fluent method.
$string = Str::swap([
'Tacos' => 'Burritos',
'great' => 'fantastic',
], 'Tacos are great!');
// $string: Burritos are fantastic!
Since Laravel uses FakerPHP for generating fake data, you can use "lexify" to generate strings in a specific pattern π
<?php
fake()->lexify(); // 'sakh', 'qwei', 'adsj'
fake()->lexify('id-????'); // 'id-xoqe', 'id-pqpq', 'id-zpeu'
Have you ever needed to code a unique identifier for a request, such as for caching purposes? Laravel ships with the "fingerprint" method that allows you to generate a unique identifier for your requests π
<?php
// Generates a unique fingerprint using the domain, URI, method and IP address
request()->fingerprint() // fbb969117edfa916b86dfb67fd11decf1e336df0
We often need to retrieve an item from the session and then delete it. While you can use the usual combo of get and forget, Laravel ships with the "pull" method that does exactly that π
<?php
// Instead of this π«
$value = session()->get('key', 'default-value');
session()->forget('key');
// Do this π
$value = session()->pull('key', 'default-value');
Have you ever needed to count the words in a string? Laravel ships with the "countWords" method to do exactly that π
<?php
use Illuminate\Support\Str;
Str::wordCount('Okay, this helper is so cool!'); // 6
Did you know that Laravel ships with two helpers, "last" and "head"? They allow you to retrieve the first and last elements of an array π
<?php
$array = [100, 200, 300];
$first = head($array); // 100
$last = last($array); // 300
We often need to check the application environment. While you can use the environment method to do so, Laravel ships with elegant methods "isProduction" and "isLocal" to do exactly that π
<?php
// Could be better π«
app()->environment() === 'production'
// Okay π
app()->environment('production')
// Better π
app()->isProduction()
// Also applies for 'local' environment with "isLocal()"
Have you ever needed to normalize the validated data before using it? Laravel Form Requests come with a "passedValidation" hook which allows you to tweak the validated data π
<?php
/**
* Handle a passed validation attempt.
*/
protected function passedValidation(): void
{
$this->replace([
'name' => ucwords(strtolower($this->name)),
]);
}
When working with arrays, we sometimes need to filter out null values. Laravel ships with an elegant helper "whereNotNull" to do exactly that π
<?php
use Illuminate\Support\Arr;
$array = [0, null, 'hello, world'];
$filtered = Arr::whereNotNull($array); // [0 => 0, 2 => "hello, world"]
Sometimes you may want to truncate long descriptions for display. Laravel ships with the "limit" method to do just that, and in the upcoming version, you can preserve whole words for a better UX π
<?php
use Illuminate\Support\Str;
Str::limit('This will be a long description', 10); // This will ...
Str::limit('This will be a long description', 10, ' ( ... )'); // This will ( ... )
// In the upcoming Laravel version we can preserve words as well π
$before = Str::limit('We can preserve words', 8) // We can p...
$after = Str::limit('We can preserve words', 8, preserveWords: true) // We can...
We often need to get the first record matching a where statement. While "where()" combined with "first()" does the job, Laravel ships with a shortcut "firstWhere()" to do exactly that π
<?php
// Instead of this π
$user = User::query()->where('name', 'john')->first();
// Do this π
$user = User::query()->firstWhere('name', 'john');
Sometimes you may need to exclude a middleware from a specific route. You can do this using the "withoutMiddleware()" method π
<?php
use App\Http\Middleware\EnsureTokenIsValid;
Route::middleware([EnsureTokenIsValid::class])->group(function () {
Route::get('/', function () {
// ...
});
Route::get('/profile', function () {
// ...
})->withoutMiddleware([EnsureTokenIsValid::class]); // Excludes the middleware
});
Sometimes you may need to work with IP addresses. Laravel uses the HttpFoundation component from Symfony under the hood, which comes with useful IP helpers π
use Symfony\Component\HttpFoundation\IpUtils;
$ipv4 = '192.168.1.1';
IpUtils::checkIp4($ipv4); // true, "checkIp6" is also available
IpUtils::isPrivateIp($ipv4); // true, works with IPv6 as well
IpUtils::anonymize($ipv4); // '192.168.1.0', works with IPv6 as well
When testing APIs, we often need to check if the response contains a specific key with the expected data. Laravel ships with the "assertJsonFragment" to do exactly that π
<?php
Route::get('/users', function () {
return [
'users' => [
[
'name' => 'John Doe',
],
],
];
});
$response->assertJsonFragment(['name' => 'John Doe']);
Sometimes you might have multiple response formats that you return. You can use the "getAcceptableContentTypes" method to map your response to what's best for the user π
<?php
request()->getAcceptableContentTypes();
// [
// 0 => "text/html",
// 1 => "application/xhtml+xml",
// 2 => "image/avif",
// 3 => "image/webp",
// 4 => "image/apng",
// 5 => "application/xml",
// 6 => "x/e",
// 7 => "application/signed-exchange",
// ];
We use factories a lot. Did you know about the for[Relation] and has[Relation] magic methods? You just need to make sure you have the relationship set up in your model and you are set π
<?php
// You need to have User and Post factories.
$user = User::factory()
// The User model has a hasMany "posts" relationship.
->hasPosts(3)
->create();
$posts = Post::factory()
->count(3)
// The Post model has a belongsTo "user" relationship.
// The array is optional. Use it to override an attribute if needed.
->forUser([
'name' => 'John Doe',
])
->create();
Since Laravel uses FakerPHP under the hood, you can generate fake credit card numbers for your tests π
<?php
fake()->creditCardNumber(); // '5151791946409422'
fake()->creditCardNumber('Visa'); // 4147628831960830
fake()->creditCardNumber('Visa', formatted: true); // 4147-6288-3196-0830
// You can even generate card types
fake()->creditCardType(); // MasterCard
Since Laravel uses FakerPHP under the hood, you can generate random currency codes. This is really useful for fintech apps π
<?php
fake()->currencyCode();
// 'TND', 'AED', 'SAR', 'KZT'
We use Blade a lot, and if I have one thing to complain about, it's type hints. However, we can solve this issue by defining a @php block for all the variables used π
@php
/* @var App\Models\Flight $flight */
@endphp
<div>
// Your IDE will provide type hints for the property
{{ $flight->name }}
</div>
Did you know that when validating dates with Laravel, you can pass strings like "today" or "tomorrow" instead of actual dates? This makes the validation rules much more readable π
<?php
// You can use any string supported by strtotime() date validation
$rules = [
'start_date' => 'required|date|after:tomorrow',
'end_date' => 'required|date|after_or_equal:start_date',
'past_date' => 'required|date|before:yesterday',
'deadline' => 'required|date|before_or_equal:today',
];
Eager loading can significantly improve performance. Use the "preventLazyLoading" method to ensure all relationships are eager-loaded during development and customize its behavior for violations π
<?php
use Illuminate\Database\Eloquent\Model;
// In your AppServiceProvider
public function boot(): void
{
// This ensures lazy loading is prevented in your dev environment
Model::preventLazyLoading(! $this->app->isProduction());
// You can customize the behavior for lazy loading violations
Model::handleLazyLoadingViolationUsing(function (Model $model, string $relation) {
$class = $model::class;
info("Attempted to lazy load [{$relation}] on model [{$class}].");
});
}
We often need to check if a given string is valid JSON. Laravel provides an elegant method, "isJson", to help you with this. It uses the new "json_validate" function in PHP 8.3, and "json_decode" for earlier versions π
<?php
use Illuminate\Support\Str;
// The cool thing is, on the day you upgrade to PHP 8.3, if you haven't already,
// you will automatically be using the new "json_validate()" function
// instead of "json_decode()"
Str::isJson('[1,2,3]'); // true
Str::isJson('{"first": "John", "last": "Doe"}'); // true
Str::isJson('{first: "John", last: "Doe"}'); // false
Ever needed to count the occurrences of a word in a sentence? Laravel ships with the "substrCount" method to do exactly that π
<?php
use Illuminate\Support\Str;
Str::substrCount('My name is Bond, James Bond', 'Bond'); // 2
Need to drop some framework-specific columns? You donβt have to specify them manually, Laravel provides shortcuts to do exactly that π
<?php
Schema::table('users', function (Blueprint $table) {
$table->dropColumn(['created_at', 'updated_at']);
$table->dropTimestamps();
$table->dropColumn(['deleted_at']);
$table->dropSoftDeletes();
$table->dropColumn(['remember_token']);
$table->dropRememberToken();
$table->dropColumn(['morphable_id', 'morphable_type']);
$table->dropMorphs('morphable');
});
Laravel v11.20 introduces a new "deduplicate" method which allows you to remove duplicates from spaces or any character you choose π
<?php
Str::deduplicate('Laravel Framework') // Laravel Framework
Str::deduplicate('Laravel Frameworkkkkk', 'k') // Laravel Framework
If you want to be absolutely sure that a key exists in your .env file, use the "getOrFail()" method. It will throw a runtime exception if the key is missing. This is really useful for API keys π
<?php
use Illuminate\Support\Env;
return [
'postmark' => [
'token' => Env::getOrFail('POSTMARK_TOKEN'),
],
];
// If the POSTMARK_TOKEN key is missing from your .env file,
// an exception will be thrown with the message:
// "Environment variable [POSTMARK_TOKEN] has no value."
We often use form requests for validation. Did you know you can customize the redirect location upon failure? You are never limited to redirecting users back to the previous page π
<?php
// You can use a path
protected $redirect = '/dashboard';
// Or named routes
protected $redirectRoute = 'dashboard';
// Or even use actions
protected $redirectAction = [DashboardController::class, 'index'];
If you are using MySQL/MariaDB as your database, you can leverage invisible columns. These columns remain hidden in 'SELECT * ' statements, which is perfect for handling sensitive information and precomputed data π
<?php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
// Note that this works for MariaDB/MySQL
Schema::table('users', function (Blueprint $table) {
// Useful for storing secrets
$table->string('secret')->nullable()->invisible();
// Or for precomputed columns
$table->string('posts_count')->default(0)->invisible();
});
// The attribute is not even present on the Eloquent object
$user = User::first();
$user->secret; // null
// You have to explicitly select the column
$user = User::select('secret')->first();
$user->secret; // SUEGQs8klF30CLSi
Did you know that you can schedule your commands for specific environments? If you frequently use "schedule:work", you'll find this helpful for excluding commands from the dev environment π
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('newsletter:send')
->environments('production') // You can also pass an array of environments
->weekends();
Building an API with Laravel? You can retrieve the bearer token using the "bearerToken" method on the request object without having to manually parse it π
<?php
// Instead of this π
$token = substr(request()->header('Authorization'), 7);
// Do this π
$token = request()->bearerToken();
When passing a model to a job, consider using the "WithoutRelations" attribute to skip serializing the relationships if you don't need them. This will keep the payload minimal and memory efficient π
<?php
use Illuminate\Queue\Attributes\WithoutRelations;
public function __construct(
#[WithoutRelations]
public Podcast $podcast
) {}
Did you know that Laravel can handle generated columns in migrations out of the box? No need to write raw SQL in your migration to create these columns π
<?php
return new class extends Migration
{
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
// ...
$table->decimal('unit_price');
$table->integer('quantity');
// Works on MariaDB / MySQL / PostgreSQL / SQLite
$table->decimal('full_price')->storedAs('unit_price * quantity');
// ...
});
}
};
In most cases, when seeding the database, you don't need to fire model events. You can use the "WithoutModelEvents" trait to mute those events, making your seeders slightly faster π
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
class DatabaseSeeder extends Seeder
{
use WithoutModelEvents; // Will mute all model events
public function run(): void
{
$this->call([
UserSeeder::class,
PostSeeder::class,
CommentSeeder::class,
]);
}
}
We often need to refresh the database when testing the code. In such cases, you can lazily refresh your database, so migrations are run only when you are hitting the db. This will help speed up your tests π
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\LazilyRefreshDatabase;
use Tests\TestCase;
class ExampleTest extends TestCase
{
use RefreshDatabase;
// use LazilyRefreshDatabase;
public function test_basic_example(): void
{
$response = $this->get('/');
// ...
}
}
When working with "hasOne" or "belongsTo" relationships, we often check whether they are nullable before accessing their properties. In such cases, you can use default models to ensure you never get null values π
<?php
public function user(): BelongsTo
{
return $this->belongsTo(User::class)->withDefault([
'name' => 'Guest Author',
]);
}
// Now, instead of checking this every time
Post::first()->user->name ?? 'Guest Author'; // 'Guest Author'
// You can be confident that when a user is null, the default will be used
Post::first()->user->name; // 'Guest Author'
Sometimes you may want to permanently remove soft-deleted models. You can use "forceDelete()" for that, or the new "forceDestroy()" method introduced in Laravel v11.21 π
<?php
// Laravel < v11.20
$flight = Flight::find(1);
$flight->forceDelete();
// Laravel v11.21
Flight::forceDestroy(1); // You can also pass an array of IDs
Sometimes, you may want to exclude an input from the validated array. Instead of manually unsetting it, you can use the "exclude" rule, which does exactly that π
<?php
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
'captcha' => 'required|exclude',
]);
dd($validated); // only 'title' and 'body' are set on $validated
}
Tired of high bounce rates from invalid emails? Laravel comes with the "dns" validation rule to ensure you're getting real emails. It won't magically fix the issue, but it definitely improves deliverability π
<?php
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'email' => 'required|email:dns'
]);
}
// In your tests, make sure to replace fake()->email() with fake()->freeEmail()
// to avoid flaky tests. freeEmail() will always generate an email with
// valid DNS records.
When validating arrays, it's a better UX to tell the user which item has failed rather than throwing a generic message. For this, you can use the ":index" and ":position" placeholders π
<?php
use Illuminate\Support\Facades\Validator;
$validator = Validator::make($request->all(), [
'photos.*.description' => 'required',
], [
'photos.*.description.required' => 'Please describe photo #:position.',
]);
// This will result in "Please describe photo #1".
// You can use :index to start from 0, and if you have deeply nested arrays,
// you can use :second-index, :second-position, :third-index, :third-position
Often, we need to conditionally mark an input as checked. While this can be done manually, Laravel provides a cool blade directive "checked" to do exactly that π
<input type="checkbox" name="active" value="active"
{{ old('active', $user->active) ? 'checked' : '' }}
@checked(old('active', $user->active)) />
We've all used the "filter" method on collections, but did you know that if no callback is passed, it will filter out all the falsy values? π
<?php
$collection = collect([1, 2, 3, null, false, '', 0, []]);
$collection->filter()->all(); // [1, 2, 3]
When working with soft-deleted models, you may need to get only the trashed records. While you can manually filter the query using the "deleted_at" column, there's an "onlyTrashed()" method to do exactly that π
<?php
// Instead of manually specifying 'deleted_at'
$trashedUsers = User::query()->whereNotNull('deleted_at')->get();
// You can use onlyTrashed(), which is both easier and more readable
$trashedUsers = User::query()->onlyTrashed()->get();
We've all used PHP's "implode" function, but did you know about the "join" helper? It does the same thing but also allows you to customize the last separator π
<?php
// We've all done this, the regular implode()
collect(['a', 'b', 'c'])->join(', ', ''); // 'a, b, c'
// But did you know you can specify the last separator?
collect(['a', 'b', 'c'])->join(', ', ', and '); // 'a, b, and c'
// And it's smart enough to handle edge cases
collect(['a'])->join(', ', ', and '); // 'a'
collect([])->join(', ', ', and '); // ''
While Laravel offers batches to dispatch jobs, sometimes you just want to fire and forget. In that case, you can bulk dispatch instead of doing it one by one π
<?php
// If you are using the database driver, this will result in N queries
$users->each(fn(User $user) => GenerateInvoice::dispatch($user));
// This will result in a single query
$jobs = $users->map(fn(User $user) => new GenerateInvoice($user))->toArray();
Queue::bulk($jobs);
Users tend to use the same password for all websites, which puts them in danger if their password has been leaked. You can make sure that the user inputs an uncompromised password using the "uncompromised" rule π
<?php
// Under the hood, uncompromised uses the Have I Been Pwned public API
// with k-anonymity, where only a prefix of 5 characters of the hash will be sent over
Password::min(8)
->mixedCase()
->numbers()
->letters()
->uncompromised()
Do you find yourself writing multiple if statements to call different methods on a class? Consider making your class "conditionable" by using Laravel's "Conditionable" trait π
<?php
use Illuminate\Support\Traits\Conditionable;
class NotificationService
{
use Conditionable;
public function sendEmailNotification()
{
echo "Email notification sent.";
}
public function sendSmsNotification()
{
echo "SMS notification sent.";
}
}
$notificationService = new NotificationService;
$shouldSendEmail = true;
$shouldSendSms = false;
// Instead of this
if ($shouldSendEmail) {
$notificationService->sendEmailNotification();
}
if ($shouldSendSms) {
$notificationService->sendSmsNotification();
}
// You can do this
$notificationService
->when($shouldSendEmail, fn(NotificationService $service) => $service->sendEmailNotification())
->when($shouldSendSms, fn(NotificationService $service) => $service->sendSmsNotification());
Did you know you can run database seeders within your tests? This allows you to keep your tests clean by moving setup logic to seeders π
<?php
use function Pest\Laravel\seed;
uses(LazilyRefreshDatabase::class);
// For feature tests with PHPUnit, you can simply call $this->seed()
test('orders can be created', function () {
// Run the DatabaseSeeder ...
seed();
// Run a specific seeder ...
seed(OrderStatusSeeder::class);
// Run an array of specific seeders ...
seed([
OrderStatusSeeder::class,
TransactionStatusSeeder::class,
]);
});
When defining factories, you may sometimes need to generate a random digit while excluding a specific one. Since Laravel uses FakerPHP under the hood, you can use "randomDigitNot" to do exactly that π
<?php
// This will return a digit from 0 to 9
fake()->randomDigit();
// You can also exclude a digit
fake()->randomDigitNot(3); // This will return any digit from 0 to 9, except 3
Did you know that you can add multiple columns after a specific column using the "after" method? π
<?php
Schema::table('users', function (Blueprint $table) {
$table->after('password', function (Blueprint $table) {
$table->string('address_line1');
$table->string('address_line2');
$table->string('city');
});
});
Did you know that not only can you parameterize your translation strings, but you can also auto-capitalize them? π
<?php
// in your en/messages.json
return [
'welcome' => 'Welcome, :NAME',
'goodbye' => 'Goodbye, :NAME',
];
// This will auto-capitalize it for you
echo __('messages.welcome', ['name' => 'dayle']); // Welcome, DAYLE
echo __('messages.goodbye', ['name' => 'dayle']); // Goodbye, Dayle
Ever needed to quickly connect to your database via the CLI? Thereβs an Artisan command to do exactly that! π
// This will connect you directly to the database
php artisan db
// Have multiple connections? No worries you can specify which one to use
php artisan db mysql
When working with accessors, objects are cached by default. However, if you have a computationally intensive accessor for a primitive value, like a string, you can enable caching for it using the "shouldCache()" method π
<?php
protected function hash(): Attribute
{
return Attribute::make(
get: fn (string $value) => bcrypt(gzuncompress($value)),
shouldCache: true
);
}
When creating migrations, we sometimes don't format the name in a way that Laravel can understand to fill in the table name. However, you can always specify it manually π
<?php
/**
* If you want Laravel to fill in the Schema::table() for you,
* use the --table argument when generating the migration.
*/
php artisan make:migration "tweak_users_table" --table=users
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
//
});
}
};
Sometimes, you may want to send notifications to "anonymous" users who are not yet stored in the database. Laravel's "route" method allows you to do exactly that π
<?php
use Illuminate\Broadcasting\Channel;
use Illuminate\Support\Facades\Notification;
Notification::route('mail', '[email protected]')
->route('twilio', '9419287384')
->route('slack', '#slack-channel')
->notify(new InvoicePaid($invoice));
Have you ever needed to check if your db connection is working as expected? Or wondered how many open connections you have? Maybe you want to know the total size of a db? Well, Artisan comes with some cool commands to do exactly that π
# This command allows you to inspect a table
php artisan db:table
# This command checks if your db connections are working
php artisan db:monitor
# This command displays the db version, total size, open connections, and more
php artisan db:show
Often, we need to process input through multiple steps, such as applying request filters to a query or cleaning data in a multi-stage chain. When you find yourself in such cases, use Laravel's Pipeline helper π
<?php
$user = Pipeline::send($user)
->through([
// Those are invokable classes
GenerateProfilePhoto::class,
ActivateSubscription::class,
SendWelcomeEmail::class,
])
->then(fn(User $user) => $user);
// Example of the GenerateProfilePhoto class
class GenerateProfilePhoto
{
public function __invoke(User $user, Closure $next)
{
// Execute the logic to generate the profile photo
return $next($user);
}
}
When working with batched jobs, it's best to check if the batch is canceled before running the job, and you don't have to do it manually because the "SkipIfBatchCancelled" middleware does exactly this π
<?php
use App\Jobs\ImportContacts;
use Illuminate\Support\Collection;
// A batched job
public function handle(): void
{
if ($this->batch()->cancelled()) {
return;
}
}
public function middleware(): array
{
return [new SkipIfBatchCancelled];
}
We often use "where like" statements in our apps. Did you know Laravel ships with a "whereLike" method, which takes it a step further by allowing the like statement to be case insensitive? π
<?php
// Instead of this
User::query()->where('name', 'like', 'Jo%')->get();
// You can do this
User::query()->whereLike('name', 'Jo%')->get();
// You can even specify whether it should be case sensitive
User::query()->whereLike('name', 'Jo%', caseSensitive: true)->get();
// Query: select * from `users` where `name` like binary 'Jo%'
How many times have you needed to return an object right after a basic action? Yes many times I know. The "tap" helper allows you to do just that π
<?php
public function markAsPaid(): self
{
// Not bad
$this->update(['status' => 'paid']);
return $this;
// Better
return tap($this)->update(['status' => 'paid']);
}
We often use "find()", but did you know you can pass an array of IDs and also select specific columns? π
<?php
// You can pass an array of ids, an select specific columns
User::find(id: [1, 2], columns: ['email']);
// select `email` from `users` where `users`.`id` in (1, 2)
Starting from Laravel v11.23, you can execute tasks concurrently. This can speed things up when you have independent tasks that can be run simultaneously π
<?php
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;
Concurrency::defer([
fn () => Metrics::report('users'),
fn () => Metrics::report('orders'),
]);
When working with models, you might need the count of a relationship. If you forget to eager load it, you can always use "loadCount" to load the count on the fly π
<?php
$user = User::first();
// Load the count of related posts on the fly
$user->loadCount('posts');
// You can even constraint it
$user->loadCount('posts', fn(Builder $query) => $query->whereNull('published'));
// Access the count via <relationship>_count
$user->posts_count
When validating forms, sometimes you want to conditionally require a field if another one was filled. Laravel ships with "required_if_accepted" to do exactly that π
<?php
// subscribe_to_newsletter must be true for the email field to be required
$validator = Validator::make($data, [
'subscribe_to_newsletter' => 'boolean',
'email' => 'required_if_accepted:subscribe_to_newsletter|email',
]);
Did you know that you can request confirmation for risky commands before executing them? You can do this using the "confirm" method π
<?php
// Displays the old Laravel prompt view
$this->confirm('Do you want to continue?');
// With "components", it displays the modern Laravel prompt view
$this->components->confirm('Do you want to continue?');
// In your terminal, you should see
/*
* Do you want to continue? (yes/no) [no]
*
*/
Ever needed to skip job execution? While you can do it manually, Laravel ships with a "Skip" middleware to do exactly that π
<?php
use Illuminate\Queue\Middleware\Skip;
public function middleware(): array
{
return [
Skip::when($someCondition),
// You can also use `unless` and pass a closure
Skip::unless(fn () => $this->shouldNotSkip()),
];
}
When debugging an Eloquent query, we often use "dd()" to check the result. Did you know you can just chain it directly? π
<?php
// This is okay π
$user = User::all();
dd($users);
// This is better π₯
User::all()->dd();
Sometimes you may want to get today's date. While you can do this in multiple ways, Laravel ships with a readable "today()" helper to do exactly that. You can pass timezones and chain other helpful methods as well π
<?php
// Returns today's date
$today = today();
// But you can do more π
today()->isWeekend(); // true
today()->isWeekday(); // false
today()->isSaturday(); // true, and you can check for all days
today()->isMidday(); // false
// You can also pass timezones π₯
today($timezeone);
When a model grows, it can be hard to check all the relationships, including those from third-party packages, the registered events, and the observers. When that's the case, you can use the "model:show" command instead π
php artisan model:show User
# This will list: The table, attributes, relationships, events and observers.
Ever needed to send all mails to a single address? Whether you're working in a specific environment or managing a small project with just one contact email, you can use the "alwaysTo" method to do exactly that π
<?php
use Illuminate\Support\Facades\Mail;
public function boot(): void
{
if (app()->environment('staging')) {
Mail::alwaysTo('[email protected]');
}
}
Did you know you can move a column to the first position in your table, even if it was added later on? Use the "first()" method to do that π
<?php
Schema::table('posts', function (Blueprint $table) {
$table->string('uuid')->first();
});
Sometimes you may want to override or pass a single attribute to your factory. While you can pass a whole array for that, you can use the "set" method, which does exactly that π
<?php
// Rather than passing an array for just a single attribute
User::factory()->create(['email' => '[email protected]']);
// You can use set() π₯ Plus, It's more readable π
User::factory()
->set('email', '[email protected]')
->create();
Starting with Laravel v11.23.0, you can use the new "flexible" method for caching. If you've struggled with revalidating your cache before it expires, make use of it π
<?php
// If a request is made between the 5 and 10s interval,
// the cache will be recalculated π₯ The cache will only expire after 10 seconds
// if no requests are made within the 5 to 10s window.
$value = Cache::flexible('users', [5, 10], fn () => User::all());
When working with Laravel collections, remember that they come with higher order messages, which are shortcuts for the most common actions π
<?php
use App\Models\User;
$users = User::where('votes', '>', 500)->get();
// While you can do this
$users->each(fn(User $user) => $user->markAsVip());
// You can simplify it to this using higher order messages π₯
$users->each->markAsVip();
We often need to get the IDs of some models. While you can use the "pluck()" method to do this, you can also use "modelKeys()", which reads better and won't break if you change the primary key at any point π
<?php
use App\Models\User;
// Instead of this
$ids = User::all()->pluck('id');
// You can do this π₯
$ids = User::all()->modelKeys();
A reminder that Laravel ships with signed routes. They are perfect for magic login links, temporary access, and one-time actions like unsubscribing users, all in a safe way by making sure the URL isn't tampered with π
<?php
use Illuminate\Support\Facades\URL;
// This will generate a signed route with an absolute path
URL::signedRoute('unsubscribe', ['user' => 1]);
// http://local.test/unsubscribe?user=1&signature=753c19...6d18baa59c3b5851
// You can choose to generate a relative path instead
URL::signedRoute('unsubscribe', ['user' => 1], absolute: false);
// unsubscribe?user=1&signature=45cb85167c31085...05641509acb600
// You can also make it temporary
URL::temporarySignedRoute(
'unsubscribe', now()->addMinutes(30), ['user' => 1], absolute: false
);
// unsubscribe?expires=1727640587&user=1&signature=f71fa139fe2...515fe111a8
Since Laravel uses Carbon under the hood, you can easily get the age of a parsed date π
<?php
use Illuminate\Support\Carbon;
// Get the age from a given date
$laravelsAge = Carbon::parse('01-06-2011')->age;
// This also works with date columns
$age = User::first()->birthday->age;
Have you ever needed to get the latest record from a one to many relationship? While you can use subqueries to do this, Laravel already ship with the "latestOfMany" method to do exactly that π
<?php
class User extends Model
{
public function latestOrder()
{
return $this->hasOne(Order::class)->latestOfMany('created_at');
}
}
// You can now get the latest order
$latestOrder = $user->latestOrder;
// It can also be eager loaded
$users = User::with('latestOrder')->get();
Sometimes, when need to eager load nested relationships, and for that we the use dot notation. But did you know you can also pass nested arrays? π
<?php
// Instead of this
$books = Book::with([
'author.contacts',
'author.publisher'
])->get();
// You can pass a nested array
$books = Book::with([
'author' => [
'contacts',
'publisher'
]
])->get();
Ever needed to get the closest or farthest of two dates compared to a given date? Since Laravel uses Carbon under the hood, you can do that with the "closest" and "farthest" methods π
<?php
use Illuminate\Support\Carbon;
$date = Carbon::parse('2024-05-15');
$date1 = Carbon::parse('2024-01-01');
$date2 = Carbon::parse('2024-05-16');
// You can now check the closest or farthest date
$date->closest($date1, $date2); // 2024-05-16
$date->farthest($date1, $date2); // 2024-01-01
We've all created custom Artisan commands for different purposes. While it's great to have an expressive signature, if you're using the command often, you can define an alias for it π
<?php
class BillReminder extends Command
{
protected $signature = 'reminder:bill';
protected $aliases = ['rb']; // Alias here
// ...
}
// You can run the command with:
// php artisan reminder:bill
// But now you can also use the alias
// php artisan rb
Ever needed to generate a checksum for a file to check if it has been tampered with or simply to track changes over time? Laravel ships with the "checksum" method to do exactly that! π
<?php
// You can choose any other algo (returned by hash_algos())
Storage::checksum('/path/to/file', ['checksum_algo' => 'sha1']);
Ever needed to validate the dimensions of an image, like an avatar? Laravel comes with built-in validation rules for this. You can use the "dimensions" rule to build your validation logic π
<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
Validator::make($data, [
'avatar' => [
'required',
Rule::dimensions()->maxWidth(1000)->maxHeight(500)->ratio(3 / 2),
],
]);
I'm sure you've used the "diffForHumans" method to get a human-readable date. But did you know you can get a shorter version using the "shortRelativeDiffForHumans" method? π
<?php
now()->subDays(5)->diffForHumans(); // 5 days ago
// Need a shorter version? No problem
now()->subDays(5)->shortRelativeDiffForHumans(); // 5d ago
Sometimes, you might need to ensure that a string ends with a specific character, like a slash or a dot. Laravel ships with the "finish" helper to do exactly that π
<?php
use Illuminate\Support\Str;
Str::finish('this/string', '/'); // this/string/
Str::finish('this/string/', '/'); // this/string/
Ever needed to count the days between 2 dates while filtering some based on a condition? Since Laravel uses Carbon under the hood, you can use "diffInDaysFiltered" to doexactly that π
<?php
$start = now();
$end = now()->addDays(10);
// This will count only weekdays between the two dates π₯
$weekdays = $start->diffInDaysFiltered(fn (Carbon $date) => !$date->isWeekend(), $end);
$weekdays; // 8
Sometimes, you may want to "prohibit" a field from having data based on a condition, such as the presence of another field. Laravel ships with the "prohibitedIf" rule to do exactly that π
<?php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
// You can pass a bool
Validator::make($request->all(), [
'role_id' => Rule::prohibitedIf($request->user()->is_admin),
]);
// Or a closure if the logic is more complex
Validator::make($request->all(), [
'role_id' => Rule::prohibitedIf(fn () => $request->user()->is_admin),
]);
Since Laravel uses Guzzle under the hood, you can use URI templates with Laravel's HTTP Client by calling the "withUrlParameters" method π
<?php
// This follows the URI Template specification (RFC 6570).
Http::withUrlParameters([
'endpoint' => 'https://laravel.com',
'page' => 'docs',
'version' => '11.x',
'topic' => 'validation',
])->get('{+endpoint}/{page}/{version}/{topic}');
Ever needed to check whether a date is someone's birthday? Since Laravel uses Carbon under the hood, you can use the "isBirthday" method to do exactly that π
<?php
use Illuminate\Support\Carbon;
$born = Carbon::createFromDate(1987, 4, 23);
$noCake = Carbon::createFromDate(2014, 9, 26);
$yesCake = Carbon::createFromDate(2014, 4, 23);
$born->isBirthday($noCake); // false
$born->isBirthday($yesCake); // true
When consuming APIs, you may want to apply a specific user agent to all your outgoing requests. This can make debugging easier later on. Laravel ships with global request middleware to do exactly that π
<?php
use Illuminate\Support\Facades\Http;
Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
'User-Agent', 'Example Application/1.0'
));
Http::globalResponseMiddleware(fn ($response) => $response->withHeader(
'X-Finished-At', now()->toDateTimeString()
));
Ever needed to convert a string to a title? Laravel ships with the "headline" method to do exactly that π
<?php
Str::headline('a_cool_title'); // A Cool Title
Str::headline('EmailNotificationSent'); // Email Notification Sent
// Also works with fluent strings π₯
str('a_cool_title')->headline();
Laravel v11.27.1 introduced a new service provider method called "optimizes". You can now define commands to run with the "optimize" and "optimize:clear" commands, such as "filament:optimize" or other custom commands π
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
$this->optimizes(
optimize: 'filament:optimize',
// Defaults to the service provider name without "ServiceProvider" suffix
key: 'filament'
);
// ...
}
}
Have you ever found yourself needing the ID of a newly inserted row? Laravel ships with the "insertGetId" method to do exactly that π
<?php
$id = DB::table('users')->insertGetId(
['email' => '[email protected]', 'votes' => 0]
);
dd($id); // The ID of the newly inserted row
When debugging queries, we often use "dd()" or "toSql()", but did you know you can use "ddRawSql" to get the raw SQL with all bindings substituted? π
<?php
DB::table('users')->where('votes', '>', 100)->ddRawSql();
// "select * from `users` where `votes` > 100"
If you have multiple clients and some of them only expect JSON, instead of manually checking the Accept header, you can use Laravel's built-in "expectsJson" method π
<?php
// True when the "Accept: application/json" header is present
if ($request->expectsJson()) {
// ...
}
Sometimes you may want to add additional input to the current request. While you can merge it manually, Laravel already ships with the "mergeIfMissing" method to do exactly that π
<?php
$request->mergeIfMissing(['votes' => 0]);
Did you know that Laravel ships with the "emailOutputOnFailure" method which automatically sends the output of a failed command to your email? π
<?php
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:generate')
->daily()
->emailOutputOnFailure(config('contact.support'));
We often eager load relationships manually using the "load" method. While this works, it can result in duplicate queries when the relationship is already loaded. This can be avoided by using the 'loadMissing' method π
<?php
// If the relationships are already loaded, this will result in duplicate queries
$users->load(['comments', 'posts']);
// This will not query the DB if the relationships are already there
$users->loadMissing(['comments', 'posts']);
As of Laravel v11.28, instead of overriding the "newCollection" method, you can now use the new "CollectedBy" attribute to specify a custom collection for your model π
<?php
use Illuminate\Database\Eloquent\Attributes\CollectedBy;
#[CollectedBy(PostCollection::class)]
class Post
{
public function newCollection(array $models = [])
{
return new PostCollection($models);
}
}
We often need to check if a user is authenticated, and for that, we use the "check" method. But did you know that when you need to check if a user is a guest, you can use the "guest" method instead? π
<?php
// This is good
if (! Auth::check()) {
// The user is a guest
}
// This is more readable
if (Auth::guest()) {
// The user is a guest
}
We often use "withCount" when working with relationships, but did you know other aggregate functions are available out of the box? For example, you can also use "sum", "min", and "max" functions π
<?php
Post::withSum('comments', 'votes')->first(); // $post->comments_sum_votes
Post::withMax('comments', 'votes')->first(); // $post->comments_max_votes
Post::withAvg('comments', 'votes')->first(); // $post->comments_avg_votes
Post::withMin('comments', 'votes')->first(); // $post->comments_min_votes
Post::withExists('comments')->first(); // $post->comments_exists_votes
Have you ever needed to validate a field only if it's present, but skip it when itβs not? Laravel ships with the "sometimes" validation rule to do exactly that π
<?php
$validator = Validator::make($data, [
// The email will only be validated if it is present in the $data array
'email' => ['sometimes', 'email'],
]);
Since Laravel uses FakerPHP to generate fake data, you can use the "creditCardDetails" method to generate fake credit cards for your tests π
<?php
fake()->creditCardDetails();
// [
// "type" => "Visa Retired"
// "number" => "4485338826091"
// "name" => "Ron Effertz"
// "expirationDate" => "07/25"
// ]
// If you want to generate potentially expired cards you can pass false
fake()->creditCardDetails(valid: false);
// [
// "type" => "MasterCard"
// "number" => "5125073907573220"
// "name" => "Dr. Thelma Koelpin PhD"
// "expirationDate" => "08/22"
// ]
Have you ever needed to sort an array recursively, including all its sub arrays? Laravel ships with the "sortRecursive" method to do exactly that π
<?php
use Illuminate\Support\Arr;
$array = [
['PHP', 'Ruby', 'JavaScript'],
['one' => 1, 'two' => 2, 'three' => 3],
];
Arr::sortRecursive($array);
/*
[
['one' => 1, 'three' => 3, 'two' => 2],
['JavaScript', 'PHP', 'Ruby'],
]
*/
// You can also sort in a desc order
Arr::sortRecursiveDesc($array);
Have you ever needed to unset data from nested arrays? It can get messy (and ugly) quickly. Laravel ships with the "data_forget" helper to do exactly that using the dot notation π
<?php
$data = ['products' => ['desk' => ['price' => 100]]];
data_forget($data, 'products.desk.price'); // ['products' => ['desk' => []]]
Have you ever needed to check for a header? While you can do it manually, Laravel ships with the "hasHeader" method to do exactly that π
<?php
if ($request->hasHeader('X-Header-Name')) {
// ...
}
Sometimes you might want to make parent props available to child components. While you could explicitly redefine the props for child component, Laravel ships with the "aware" directive to do exactly that π
<x-menu color="purple">
<x-menu.item>...</x-menu.item>
<x-menu.item>...</x-menu.item>
</x-menu>
<!-- /resources/views/components/menu/index.blade.php -->
@props(['color' => 'gray']) <!-- Color property is not accessible to child components -->
@aware(['color' => 'gray']) <!-- Color property is accessible to child components -->
<ul {{ $attributes->merge(['class' => 'bg-'.$color.'-200']) }}>
{{ $slot }}
</ul>
Often, we need to conditionally mark an input as readonly. While this can be done manually, Laravel provides a cool blade directive "readonly" to do exactly that π
<input
type="email"
name="email"
{{ $user->isNotAdmin() ? 'readonly' : '' }}
@readonly($user->isNotAdmin())
/>
Have you ever needed to conditionally include a Blade view? While you could use "if" and "include" together, Laravel ships with the "includeWhen" and "includeUnless" directives to do exactly that π
// Instead of this π₯±
@if ($isAdmin)
@include('components.impersonate')
@endif
// You can do this π₯
@includeWhen($isAdmin, 'components.impersonate')
// even this
@includeUnless(! $isAdmin, 'components.impersonate')
If you have a service provider that only registers some bindings, you can mark it as deferred by implementing the "DeferrableProvider" interface. This way, it will load only when one of its bindings is needed π
<?php
namespace App\Providers;
use App\Services\Riak\Connection;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
// This service provider will only load when the Connection is needed,
// improving your apps performance.
class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register(): void
{
$this->app->singleton(Connection::class, function (Application $app) {
return new Connection($app['config']['riak']);
});
}
}
Did you know that Laravel collections ship with a "pipe" method? It passes the collection to a given callback and returns the result. It can be useful when you want to wrap your collection or perform calculations π
<?php
use Illuminate\Support\Collection;
$collection = collect([1, 2, 3]);
$collection->pipe(fn (Collection $collection) => $collection->sum()); // 6
$collection->pipe(fn (Collection $collection) => ['numbers' => $collection->toArray()]);
// ['numbers' => [1, 2, 3]]
Did you know you can render Blade templates inline? This is great for compiling Blade to HTML, like adding help texts in Nova or Filament or generating emails outside Laravel projects since it can work as a standalone package π
<?php
use Illuminate\Support\Facades\Blade;
return Blade::render('Hello, {{ $name }}', ['name' => 'Laravel']); // Hello, Laravel
At some point, we all needed to toggle a value, for example, a like feature that switches between states. While you can do it manually, Laravel ships with a "toggle" method to do exactly that π
<?php
// Instead of this π
if ($user->likes()->where('tweet_id', $tweet->id)->exists()) {
$user->likes()->detach($tweet->id);
} else {
$user->likes()->attach($tweet->id);
}
// You can do this π₯
$user->likes()->toggle($tweet->id);
// It also works with multiple IDs
$user->products()->toggle([1, 2, 3]);
Sometimes, you might need to check if a string does not contain a given value. Previously, you could negate the "contains" helper, but things just got even better with the new "doesntContain" method π
<?php
use Illuminate\Support\Str;
if (!Str::contains('Larvel forever', 'Laravel')) {
// string does not contain Laravel, fix it
}
if (Str::doesntContain('Larvel forever', 'Laravel')) {
// string does not contain Laravel, fix it
}
There are some exceptions that you may not want to report to your monitoring tool. While you could manually register them in "app.php", you can just mark the exception with the "ShouldntReport" interface π
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Contracts\Debug\ShouldntReport;
class PodcastProcessingException extends Exception implements ShouldntReport
{
//
}
Ever needed to dump the full query log executed in a method? You can enable the query log at the very beginning and dump it at the end by using "enableQueryLog" and "getRawQueryLog" π
<?php
use Illuminate\Support\Facades\DB;
DB::enableQueryLog();
User::whereNotNull('email_verified_at')->get();
DB::getRawQueryLog();
// select * from `users` where `email_verified_at` is not null
// time: 0.33
When writing Pest tests, you will likely use "$this", which is not good for IDE autocompletion and may require adding a PHPDoc. To avoid this, use the "test()" helper instead, which returns the current Test Case instance π
<?php
test('the array has the specified key', function () {
$array = [
'name' => 'John Doe',
'email' => '[email protected]',
];
// No autocompletion π
$this->assertArrayHasKey('email', $array);
// autocompletion works perfectly fine, and the IDE is happy π₯
test()->assertArrayHasKey('email', $array);
});
Sometimes you may want to apply global headers to all outgoing requests. For instance, a global user agent can help you identify your app's requests in other services or third-party APIs. Laravel already supports request and response middleware to do exactly that π
<?php
use Illuminate\Support\Facades\Http;
Http::globalRequestMiddleware(fn ($request) => $request->withHeader(
'User-Agent', 'Example Application/1.0'
));
Http::globalResponseMiddleware(fn ($response) => $response->withHeader(
'X-Finished-At', now()->toDateTimeString()
));
Laravel v11.32 introduces a new "rawColumn" method. Now, instead of having to use a DB statement when the grammar does not support updating or creating the column, you can use the "rawColumn" method π
<?php
new class extends Migration {
public function up()
{
// Instead of this
Schema::create('posts', function (Blueprint $table) {
$table->id();
});
DB::statement('alter table `posts` add `legacy_boolean` int(1) default 0 not null');
Schema::table('posts', function (Blueprint $table) {
$table->index(['id', 'legacy_boolean']);
});
// You can now do this
Schema::create('table', function (Blueprint $table) {
$table->id();
$table->rawColumn('legacy_boolean', 'int(1)')->default(0);
$table->index(['id', 'legacy_boolean']);
});
}
};
Have you ever needed to run an EXPLAIN on an Eloquent query to check if an index is being used? While you could manually extract the raw query and run EXPLAIN on it, you can just chain the "explain" method to do exactly that π
<?php
User::query()
->where('email', '[email protected]')
->explain()
->dd();
array:1 [
0 => {#3368
+"id": 1
+"select_type": "SIMPLE"
+"table": null
+"partitions": null
+"type": null
+"possible_keys": null
+"key": null
+"key_len": null
+"ref": null
+"rows": null
+"filtered": null
+"Extra": "no matching row in const table"
}
]
Since Laravel v11.31, you can enforce HTTPS for all generated URLs without needing the HTTPS schema specified in the request π
<?php
use Illuminate\Support\Facades\URL;
URL::forceHttps(app()->isProduction());
Most Laravel apps often have local-only or environment-dependent commands that shouldn't run elsewhere. To prevent accidents, use the "Prohibitable" trait and call the "prohibit" method π
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Console\Prohibitable;
class CleanLogs extends Command
{
use Prohibitable;
}
// Now in the boot method of the AppServiceProvider
CleanLogs::prohibit(app()->isProduction());
Running migrations or wiping the DB in production can be, well, disastrous. Since Laravel v11, you can prohibit all DB destructive commands by calling "prohibitDestructiveCommands" method π
<?php
namespace App\Providers;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Prevents 'migrate:fresh', 'migrate:refresh', 'migrate:reset', and 'db:wipe'
DB::prohibitDestructiveCommands(
$this->app->isProduction()
);
}
}
Sometimes, you may want to hide console commands, such as legacy commands, from being listed. While you can manually hide them usingΒ the "setHidden()", you can do this with theΒ "isHidden()"Β method π
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
class LegacyCommand extends Command
{
// ...
public function isHidden(): bool
{
// This could check a configuration flag
return true;
}
}
Laravel v11.28 introduced a new attribute RouteParameter
, which provides an elegant way to access route parameters. While you can use the route()
method on form requests, with the new attribute you also get proper type hints π
<?php
use App\Models\Post;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Container\Attributes\RouteParameter;
class UpdatePost extends FormRequest
{
public function authorize(#[RouteParameter('post')] Post $post): bool
{
return $this->user()->can('update', $post);
}
// ...
}
// Route definition
Route::put('/post/{post}', [PostsController::class, 'update']);
Have you ever needed to quickly create a disk, whether for tests or temporary files, but had to define it in the filesystem configuration? Well, Laravel ships with on-demand disks so that you can define disks at runtime instead π
<?php
use Illuminate\Support\Facades\Storage;
$disk = Storage::build([
'driver' => 'local',
'root' => '/path/to/root',
]);
$disk->put('image.jpg', $content);
Starting from Laravel v11.34, you can now spell ordinal numbers using the newly introduced "spellOrdinal" method π
<?php
use Illuminate\Support\Number;
$position = Number::ordinal(3); // 3rd
// Now you can spell it π₯
$position = Number::spellOrdinal(3); // third
Sometimes, you may have jobs where only one job can be queued at a time, but once it starts processing, you can queue more jobs. Laravel ships with the "ShouldBeUniqueUntilProcessing" contract to do exactly this π
<?php
use App\Models\Product;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Contracts\Queue\ShouldBeUniqueUntilProcessing;
class UpdateSearchIndex implements ShouldQueue, ShouldBeUniqueUntilProcessing
{
// ...
}
Sometimes you may want to check if a model exists, and if not, instantiate it without saving it to the database right away. Laravel ships with the "firstOrNew" to do exactly that π
<?php
use App\Models\User;
// Find the user by email, or save it to the database if it doesn't exist
User::firstOrCreate(['email' => '[email protected]']);
// Find the user by email, or instantiate a new User instance without saving it
User::firstOrNew(['email' => '[email protected]']);
// Do something with $user
// Save it to the database
$user->save();
Have you ever needed to key your eloquent collection by an attribute from its items? While you can hack your way with pluck, the "keyBy" method does exactly that π
<?php
$collection = collect([
['product_id' => 'prod-100', 'name' => 'Desk'],
['product_id' => 'prod-200', 'name' => 'Chair'],
]);
// Instead of doing this or any manual transformation
$keyed = $collection->pluck(null, 'product_id');
// You can simply do this π₯
$keyed = $collection->keyBy('product_id');
$keyed->all();
/*
[
'prod-100' => ['product_id' => 'prod-100', 'name' => 'Desk'],
'prod-200' => ['product_id' => 'prod-200', 'name' => 'Chair'],
]
*/