Sajjadist's avatar

Validating ENUM Column in Laravel

Hey devs,

I have a table with an ENUM column that defines user roles beyond just 0 (user) and 1 (admin). The issue is that, even though I've set a NOT NULL constraint, if an invalid value is provided, the column ends up empty. I don’t want this to happen.

How can I properly validate incoming data while still using the usual create and factory methods? I’d prefer not to introduce an intermediate method.

P.S.: Would it be better to store roles as strings or as integers (and map them to strings in the model)? Which approach do you think is best?

1 like
7 replies
LaryAI's avatar
Level 58

To properly validate an ENUM column in Laravel, you can leverage Laravel's built-in validation system to ensure that only valid values are accepted before they are inserted into the database. Here's how you can achieve this:

Step 1: Define the ENUM Values

First, define the valid ENUM values in your model or a separate configuration file. This will help you maintain consistency and make it easier to update the values if needed.

// In your model or a config file
public const USER_ROLES = [
    'user',
    'admin',
    'editor',
    'moderator',
];

Step 2: Use Validation in Your Controller

When handling incoming requests, use Laravel's validation to ensure that the provided role is one of the valid ENUM values.

use Illuminate\Http\Request;
use App\Models\User;

public function store(Request $request)
{
    $validatedData = $request->validate([
        'role' => ['required', Rule::in(User::USER_ROLES)],
        // other fields...
    ]);

    User::create($validatedData);

    return response()->json(['message' => 'User created successfully']);
}

Step 3: Update Your Factory

If you're using Laravel factories to generate test data, make sure to use the valid ENUM values.

use App\Models\User;
use Illuminate\Database\Eloquent\Factories\Factory;

class UserFactory extends Factory
{
    protected $model = User::class;

    public function definition()
    {
        return [
            'role' => $this->faker->randomElement(User::USER_ROLES),
            // other fields...
        ];
    }
}

Storing Roles as Strings vs. Integers

Storing as Strings:

  • Pros: More readable and self-explanatory. Easier to understand and manage.
  • Cons: Slightly more storage space required compared to integers.

Storing as Integers:

  • Pros: More efficient in terms of storage and performance.
  • Cons: Less readable. Requires mapping integers to strings in your application logic.

Recommendation: If the number of roles is small and unlikely to change frequently, storing them as strings is generally more convenient and easier to manage. However, if you have a large number of roles or expect frequent changes, consider using integers with a mapping in your application logic.

By following these steps, you can ensure that only valid ENUM values are stored in your database, and you can maintain the integrity of your data.

vincent15000's avatar
Level 63

Here is what you can do.

enum RoleEnum: string
{
    case SUPERADMIN = 'superadmin';
    case ADMIN = 'admin';
    case ASSISTANT = 'assistant';

    public function label(): string
    {
        return match ($this) {
            self::SUPERADMIN => 'Super Administrator',
            self::ADMIN => 'Administrator',
            self::ASSISTANT => 'Assistant',
        };
    }
}
// Form request for the model, for example UserRequest
public function rules(): array
{
    return [
        'name' => ['required', 'string', 'max:255'],
        'email' => ['required', 'string', 'email', 'max:255', Rule::unique('users')->ignore($this->user)],
        'role' => ['required', 'in:admin,assistant'],
    ];
}
public function store(UserRequest $request)
{
	$user = new User;

	$user->fill($request->validated());

	$user->save();
}

Why do you need a factory to store a new model ?

Sajjadist's avatar

@vincent15000

Thanks for your response! You're right, using a factory isn't really relevant here. Just to add, you can integrate your enum with the model like this:

protected function casts(): array
{
    return [
        'role' => RoleEnum::class,
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
    ];
}
1 like
Snapey's avatar

@vincent15000 ideally you use the enum in your validation rule as this prevents you entering a typo in the list, and also means you dont need to alter the validation rule when adding new roles.

2 likes
vincent15000's avatar

@Snapey Yes it's a good idea, I use the enum in my validation, but only if the fields to validate can accept all the enum values.

For cases where the fields can accept only a part of the enum values, I prefer putting the values manually in the validation rule.

Snapey's avatar

@vincent15000 You could create a function in the enum class that returns a set of enums that are relevant to the type of request.

alternatively just list the enums in the validation IN rule, and not the bare strings.

1 like

Please or to participate in this conversation.