NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages
Attributes for Laravel

Definitions & validation

Register known attributes with a type and validation rules. Registered attributes are always validated on write and stored in their defined type:

use RoundlyConsulting\Attributes\Facades\Attributes;
use RoundlyConsulting\Attributes\DataTransferObjects\AttributeDefinitionData;
use RoundlyConsulting\Attributes\Enums\AttributeType;

Attributes::define(new AttributeDefinitionData(
    name: 'rating',
    type: AttributeType::Integer,
    rules: ['min:1', 'max:5'],
));

Attributes::for($product)->set('rating', 9); // throws InvalidAttributeValueException

Registering definitions

Register definitions in a service provider’s boot() method, or seed them through the definitions config key (see Configuration):

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rule;
use RoundlyConsulting\Attributes\DataTransferObjects\AttributeDefinitionData;
use RoundlyConsulting\Attributes\Enums\AttributeType;
use RoundlyConsulting\Attributes\Facades\Attributes;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Attributes::defineMany(
            new AttributeDefinitionData('rating', AttributeType::Integer, rules: ['min:1', 'max:5']),
            new AttributeDefinitionData('size', AttributeType::String_, rules: [Rule::in(['S', 'M', 'L'])]),
            new AttributeDefinitionData('launched_at', AttributeType::DateTime),
            new AttributeDefinitionData('tags', AttributeType::Array_, rules: ['max:10']),
        );
    }
}

AttributeDefinitionData takes name, type, rules, default, required, unique and encrypted — everything after type is optional. The AttributeType cases that clash with PHP keywords carry a trailing underscore: String_, Float_ and Array_.

How validation works

  • The type’s own rule comes first — string, integer, numeric (float), boolean, array or date (datetime) — followed by your rules.
  • Rules may be strings or rule objects, exactly as in a Laravel validator; the attribute name is used as the field label in messages.
  • null values are never validated.
  • Undefined names are not validated at all — unless strict mode rejects them.

Strict mode

Set ATTRIBUTES_STRICT=true to reject any attribute name that is not registered — writing a value or its meta throws UnknownAttributeException. A name counts as registered when it has a global definition (config or Attributes::define()) or one in the owner model’s own schema:

// .env: ATTRIBUTES_STRICT=true

Attributes::define(new AttributeDefinitionData('color', AttributeType::String_));

Attributes::for($product)->set('color', 'white');   // registered: fine
Attributes::for($product)->set('nickname', 'Blue'); // throws UnknownAttributeException

The definitions API

The facade’s definition methods delegate to RoundlyConsulting\Attributes\Registry\AttributeRegistry, the container singleton that stores definitions:

MethodPurpose
define($definition) / defineMany(...$definitions)Register (or replace) definitions.
has($name) / get($name) / all()Inspect the global definitions.
forget($name) / flush()Unregister one definition, or all of them.
resolveFor(?$owner, $name)The definition that applies to an owner — its model schema first, then global.
definitionsFor($owner)Global definitions overlaid by the owner’s model schema.
default($name, ?$owner = null)A definition’s default value.
requiredNames()Names of the required global definitions.
validate($name, $value) / validateFor(?$owner, $name, $value)Validate a value; throws InvalidAttributeValueException.
assertUnique($owner, $name, $value)Uniqueness check; throws DuplicateAttributeValueException.
isStrict() / assertKnown($name, ?$owner = null)Strict-mode state and check — given the owner, its model schema counts too; throws UnknownAttributeException.
use RoundlyConsulting\Attributes\Facades\Attributes;

Attributes::has('rating');                   // bool
Attributes::get('rating')?->rules;           // ['min:1', 'max:5']
Attributes::all();                           // array<string, AttributeDefinitionData>
Attributes::definitionsFor($product);        // global + the model's own schema
Attributes::resolveFor($product, 'rating');  // the definition that applies to this owner
Attributes::default('retries', $product);    // a definition's default value
Attributes::requiredNames();                 // required names among global definitions
Attributes::validate('rating', 4);           // throws InvalidAttributeValueException if invalid
Attributes::forget('rating');                // unregister one
Attributes::flush();                         // unregister all

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.