Group multiple boolean attributes in Laravel Nova

Published on by

Group multiple boolean attributes in Laravel Nova image

Laravel Nova comes jam-packed with an amazing list of fields. These fields are pretty smart by default, and they are suited for almost every situation. But what if you have a situation in which the UI matches your needs, but the fields handles data differently. Are you forced to create a custom field? Well, maybe not.

Customize your field

Laravel Nova's fields have a bunch of methods that lets you customize their behavior. As you can read in the docs, you can customize the way the corresponding model is hydrated by attaching a fillUsing() callback. And you can customize the way the field is resolved by attaching a resolveUsing() callback. So let's use these functions, and create our own "custom" field.

Grouping multiple booleans into a BooleanGroup

Imagine you have a Message model that represents a message that can be shown in multiple scopes, like: website, app and rss. You want to query those messages easily by their scope, so you've added 3 boolean fields: scope_website, scope_app and scope_rss.

Yes, you could have made categories and created a pivot table; but you only had these scopes, and then you remembered this was an example..

<?php
 
namespace App\Nova\Resources;
 
use App\Nova\Resource;
use Illuminate\Http\Request;
use Laravel\Nova\Fields\BooleanGroup;
 
class MessageResource extends Resource
{
// ...
 
public function fields(Request $request): array
{
return [
BooleanGroup::make('Scopes')->options($options = [
'scope_website' => 'Website',
'scope_app' => 'Application',
'scope_rss' => 'RSS Feed',
])
];
}
}

Hydrating the model attributes

Instead of adding 3 different fields to the Nova Resource we want to add a BooleanGroup of these scopes. By default, the BooleanGroup field will store its options as a JSON blob on a single attribute. To change this behavior we are going to customize the hydration of our field by calling the fillUsing method with this callback function:

use App\Model\Message;
use Laravel\Nova\Http\Requests\NovaRequest;
 
// this function goes inside ->fillUsing(...)
function (NovaRequest $request, Message $model, string $attribute, string $requestAttribute) {
// Make sure the `scopes` value exists on the request.
if (!$request->exists($requestAttribute)) {
return;
}
// Decode the values because it is send as a JSON blob.
$values = json_decode($request[$requestAttribute], true);
// Hydrate the model.
foreach ($values as $key => $value) {
$model->{$key} = $value;
}
}

The callback function for fillUsing() receives 4 parameters:

  • NovaReqeust $request The request object that has the POST values
  • Message $model The model we are going to hydrate
  • string $attribute The name of the attribute on the model (we are not using this in our example)
  • string $requestAttribute The name of the attribute inside the $request object that has our POST value

After making sure we posted the values, we can map al those options to their model attribute. Effectively calling something like:

$model->scope_website = 1; // or 0

Resolving the field

Now that we actually store the booleans on the correct attributes, it's time to take a look at the resolving of the field in Nova. While the model may have scopes set, all checkboxes will be turned "off". This is because the field still tries to retrieve their values from $model->scopes; which doesn't exist. So lets fix that by adding a resolveUsing callback.

You might have noticed we defined our field options inside a $options variable. This was intentional, because we need the keys for those options. As of this time, the resolveUsing callback doesn't have access to the field itself to retrieve those options. This is our workaround.

// this function goes inside ->resolveUsing(...)
function ($value, Message $model, string $attribute) use ($options) {
$keys = array_keys($options);
$values = array_map(function($value, $key) use ($model) {
return $model->{$key};
}, $options, $keys);
 
return array_combine($keys, $values);
}

The callback function for resolveUsing() receives 3 parameters:

  • mixed $value The value that Laravel Nova tried to retrieve
  • Message $model The model that provided the value
  • string $attribute The name of the attribute on the model (again, we won't be needing this)

The only thing our callback needs to do is retrieve the values for every boolean from the model, and return those values as an array. This code is a bit of a mess. It's not easy to see what is going on, and we need to use values within multiple function scopes. Let's clean this up a bit by using shorthand functions and some laravel collect magic:

fn($value, Message $model) => collect($options)->map(fn($value, $key) => $model->{$key})

There you go, a nice one-liner that resolves the field from the correct model attributes. When you refresh your Nova page, the checkboxes should correctly indicate their status.

Bonus: make this field required

Just for fun, let's assume you need to select at least one scope. Your first instinct might be to just set ->required() on the field, but that doesn't actually work, although it will give a nice asterisk * on the form. Luckily we can also add a custom validation rule by calling the rules() method on the field.

$field->rules('required', function (string $attribute, $value, callable $fail) {
if (!array_filter(json_decode($value, true) ?? [])) {
return $fail(sprintf('The "%s" field must have at least one option selected.', $attribute));
}
})

Setting the required rule will also add the asterisk * to the form. The callback does a quick check to see if any value was returned as true. If not; we call the $fail callable and provide the reason for failing the validation.

And that's it; a custom field, without actually building a custom field.

Doeke Norg photo

PHP developer from Groningen, the Netherlands. Works mostly with Symfony, Laravel and a sprinkle of WordPress plugins. Testing enthusiast. Recently started blogger.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

Laravel Forge logo

Laravel Forge

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Forge
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $7500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
Bacancy logo

Bacancy

Supercharge your project with a seasoned Laravel developer with 4-6 years of experience for just $2500/month. Get 160 hours of dedicated expertise & a risk-free 15-day trial. Schedule a call now!

Bacancy
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Lunar: Laravel E-Commerce logo

Lunar: Laravel E-Commerce

E-Commerce for Laravel. An open-source package that brings the power of modern headless e-commerce functionality to Laravel.

Lunar: Laravel E-Commerce
LaraJobs logo

LaraJobs

The official Laravel job board

LaraJobs
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate logo

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate

Build your SaaS application in hours. Out-of-the-box multi-tenancy and seamless Stripe integration. Supports subscriptions and one-time purchases, allowing you to focus on building and creating without repetitive setup tasks.

Supercharge Your SaaS Development with FilamentFlow: The Ultimate Laravel Filament Boilerplate
Rector logo

Rector

Your partner for seamless Laravel upgrades, cutting costs, and accelerating innovation for successful companies

Rector
MongoDB logo

MongoDB

Enhance your PHP applications with the powerful integration of MongoDB and Laravel, empowering developers to build applications with ease and efficiency. Support transactional, search, analytics and mobile use cases while using the familiar Eloquent APIs. Discover how MongoDB's flexible, modern database can transform your Laravel applications.

MongoDB

The latest

View all →
Asymmetric Property Visibility in PHP 8.4 image

Asymmetric Property Visibility in PHP 8.4

Read article
Access Laravel Pulse Data as a JSON API image

Access Laravel Pulse Data as a JSON API

Read article
Laravel Forge adds Statamic Integration image

Laravel Forge adds Statamic Integration

Read article
Transform Data into Type-safe DTOs with this PHP Package image

Transform Data into Type-safe DTOs with this PHP Package

Read article
PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell image

PHPxWorld - The resurgence of PHP meet-ups with Chris Morrell

Read article
Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3 image

Herd Executable Support and Pest 3 Mutation Testing in PhpStorm 2024.3

Read article