Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Nullable Fields Laravel Package

dyrynda/laravel-nullable-fields

Keeps nullable database columns genuinely null. Add the NullableFields trait to an Eloquent model and list your nullable fields; empty strings are converted to NULL on save, keeping your Laravel data consistent across queries, filters, and relationships.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require dyrynda/laravel-nullable-fields

There is nothing to publish and no service provider to register — the package is a single trait. Read vendor/dyrynda/laravel-nullable-fields/src/NullableFields.php first (it's ~40 lines); understanding exactly which values it considers "empty" is the whole game.

Wire it up

use Dyrynda\Database\Support\NullableFields;
use Illuminate\Database\Eloquent\Model;

class User extends Model
{
    use NullableFields;

    protected $nullable = ['bio', 'twitter_handle', 'phone'];
}

Make the columns actually nullable — this is a PHP-side helper only:

$table->string('twitter_handle')->nullable();

First use case: an optional profile form. Without the trait, an empty text input lands in the database as '', which breaks whereNotNull('phone'), makes COALESCE/fallback logic lie, and produces inconsistent rows depending on whether the client omitted the key or sent an empty string. With the trait, '' (and null) collapse to a single canonical NULL regardless of whether the data came from a form request, an import CSV, a console command, or a Nova/Filament form — none of which share the ConvertEmptyStringsToNull web middleware.

Implementation Patterns

Declare once on a base model, configure per model. Most projects have an App\Models\Model or a BaseModel; put the trait there and only the $nullable array in each concrete model, so you never think about it again.

Dynamic columns via the extension point. The trait exposes getNullableColumns(), which by default returns $this->nullable. Override it when nullability depends on context (tenancy, feature flags, a $fillable-style computed list):

protected function getNullableColumns(): array
{
    return array_merge($this->nullable, ['company_name']);
}

Keep it aligned with validation. If a field is in $nullable, the FormRequest rule should be ['nullable', 'string'], not ['sometimes'] — otherwise the two layers disagree about what "no value" means and you get flapping behaviour between create and update.

Use it for the paths middleware can't reach. ConvertEmptyStringsToNull only runs on HTTP requests in the web group. The trait earns its keep in:

  • Artisan commands and scheduled imports writing arrays straight into Model::create()
  • Queue jobs hydrating payloads from external APIs ('', ' '-adjacent junk, 'null' strings)
  • Seeders and factories where you deliberately want a null baseline
  • API routes registered without the web middleware group
  • Admin panel resources (Nova/Filament) that don't always route through your HTTP kernel

Lean on it in upserts. updateOrCreate, firstOrCreate, fill, and forceFill all funnel through setAttribute, so the normalisation applies uniformly:

UserProfile::updateOrCreate(
    ['user_id' => $user->id],
    $request->validated(), // empty strings become NULL here
);

Trim before it matters. The trait treats '' as empty but not ' '. Either keep Laravel's TrimStrings middleware in front of it, or normalise in a mutator before calling the parent.

Test at the attribute level, not just the DB level. Both are worth asserting, because they can diverge if you later add a cast:

$user = User::create(['name' => 'Ada', 'bio' => '']);
$this->assertNull($user->bio);
$this->assertDatabaseHas('users', ['id' => $user->id, 'bio' => null]);

Gotchas and Tips

The trait overrides setAttribute(). That single fact drives most surprises:

  • It fires at assignment time, not save time. $user->bio = ''; already reads back as null, and isDirty('bio') reflects null. Anything comparing against the raw input (observers, audit logs, model events) sees the normalised value, not what the caller passed.

  • If another trait in the same class also overrides setAttribute, PHP will fatal on a collision. Resolve with insteadof/as or compose carefully:

    use NullableFields, OtherSetterTrait {
        NullableFields::setAttribute insteadof OtherSetterTrait;
        OtherSetterTrait::setAttribute as setAttributeFromOther;
    }
    
  • Because it calls parent::setAttribute(), use it on a class whose ancestry actually provides that method — i.e. a model (or base model) extending Illuminate\Database\Eloquent\Model. Putting it on a random DTO will not do what you want. If you place it on a base model and a child model also uses it, you'll get the trait applied twice through the chain; don't.

empty() semantics are broader than "empty string". Verify against the installed source, but treat 0, '0', false, and [] as at-risk. Never put these in $nullable:

  • Booleans ('active' => 'boolean' cast + false → null is a silent data-loss bug)
  • Integer columns where 0 is meaningful (counts, sort_order, balance)
  • Foreign keys — a 0 becoming NULL can violate an FK constraint or, worse, trigger nullOnDelete
  • Primary keys, obviously

The database column still has to be nullable. Forgetting ->nullable() in the migration produces a NOT NULL constraint violation (or a silent '' under non-strict MySQL) — the trait cannot help you there.

Query builder writes bypass it entirely.

User::where('id', $id)->update(['bio' => '']); // '' hits the database
DB::table('users')->insert(['bio' => '']);     // ditto

Same for raw inserts, insertOrIgnore, and anything using DB:: rather than the model.

Casts run after the fact. With 'meta' => 'array' or a date cast, an empty input becomes null before the cast; that's usually right, but if a cast's set mutator expects a string you'll now hand it null. Test the combination.

Unique indexes become friendlier. MySQL and Postgres treat NULL as distinct in unique indexes, so multiple rows can have a "blank" handle. This is often the real motivation for the package — but it only works if those rows are genuinely NULL, so don't let a stray ->update() path smuggle '' back in.

Debugging checklist when a field isn't nulled:

  1. Is the key in $nullable / returned by getNullableColumns()? (Spelling, and is it a column name, not a form field name.)
  2. Did the write go through the model's setAttribute, or through the query builder?
  3. Is the value actually empty by the trait's definition? ' ', "\t", 'null', and '0' are not.
  4. Did a TrimStrings-less path leave whitespace behind?
  5. Is a cast or mutator re-injecting a value after normalisation?

Extension points worth knowing: override hasNullableColumns() to short-circuit when $nullable is absent (useful on a shared base model to skip the in_array cost), and isNullableColumn($key) if you need prefix- or suffix-based rules instead of an explicit list.

Keep the trait's scope honest. It's ~40 lines and does one thing: normalise falsy-ish input to NULL on Eloquent attribute assignment. If you find yourself wanting it to handle whitespace-only strings, enum coercion, or per-context rules, write that in a dedicated mutator or Attribute accessor instead — fighting the trait's empty() definition is how people end up accidentally nulling 0 in production.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo