Skip to content

Conversation

@dereuromark
Copy link
Member

This is currently a hidden and not so easy to trace issue

When you have a nullable foreign key like company_id, the existsIn rule will fail for null values by default (when checking composite keys where only some are null), which is counterintuitive since:

  1. NULL values are semantically valid for optional relationships
  2. The rule is pointless for NULL anyway (always fails)
  3. It forces you to manually add ['allowNullableNulls' => true] everywhere

So:

Changing the default to true would be more ergonomic because:

  • Single field case: Already handled by _fieldsAreNull() check (lines 126-128), which passes when ALL fields are null
  • Composite key case: Currently requires manual allowNullableNulls => true to allow partial nulls
  • Database constraint: If a field is truly required, it should be NOT NULL at the database level anyway

And you can still set it manually, if needed.

This seems to me to be a saner default now.
We can add this to the migration changelog for 5.next.

@othercorey
Copy link
Member

I guess this depends on what bake currently generates if it's a safe change for 5.next.

@dereuromark
Copy link
Member Author

How come? I pointed out above that this should be a safe default either way.

@othercorey
Copy link
Member

@markstory Can you take a look at this one too?

public function __construct(array|string $fields, Table|Association|string $repository, array $options = [])
{
$options += ['allowNullableNulls' => false];
$options += ['allowNullableNulls' => true];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be on purpose

My concern with doing this in 5.x is breaking an existing application. Like the current failures, these will be silent. I personally don't want to have to deal with regression issues. Perhaps we can add a deprecation, and change behavior in 6.x?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow, that comment was lost for 3 weeks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also fine going into 6.x directly.
Just not sure what the regressions would be in this case. Like what scenario would cause it? Since this just properly excludes the null ones right now, which before it didnt. And for the ones not nullable it would error on DB level, so either way it is caught from what I understand.

Copy link
Member Author

@dereuromark dereuromark Nov 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example scenario:

 // DB: category_id INT NULL (nullable but app always requires it)
 $rules->add($rules->existsIn(['category_id', 'site_id'], 'SiteCategories'));
 // Old behavior: rejects null category_id
 // New behavior: accepts null category_id

However: This is already fragile code because:
- If validation is bypassed (patch entities, direct DB queries), null gets saved anyway
- The validation rule's intent is to check foreign key existence, not nullability
- Proper code would have explicit validation: ->notBlank('category_id')

If we go with 5.next:
This could be clarified in the migration guide.

If we go with 6.x:
We could also forward shim this with a app.php config value (default false for full BC) if we wanted to globally allow the upgrade switch towards 6.x then.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Full BC possible with e.g. Configure::read('ORM.nullableNull') could be used with current default false to make apps future proof in 5.3.
If people like it extra cautious. This will give us experience over time about possible edge cases.

@dereuromark
Copy link
Member Author

@ADmad whats your take?

@ADmad
Copy link
Member

ADmad commented Nov 5, 2025

While allowing null by default for nullable columns seems intuitive, this change could potentially cause issue.

For legacy apps, at times the db schema doesn't always reflect the business requirement, which are enforced in code. So for a multi column primary key, a column could be nullable in the db even though it is required. Switching the default would remove the in-code safe guard.

To get better defaults for a new apps, we can update bake instead to generate the rule with 'allowNullableNulls' => true in case of multi columns.

The default value for the option can be changed in 6.x. Or maybe it would be better to have a separate rule altogether ExistsInForNullableColumnInCompositeKey 😄

@dereuromark
Copy link
Member Author

Wouldnt ExistsInNullable suffice? ;)

Anyway, what do you think about my upwards shim config value? That would also solve it cleanly as opt-in.
I do have to say, a separate rule might be easier indeed, though.

@ADmad
Copy link
Member

ADmad commented Nov 5, 2025

Anyway, what do you think about my upwards shim config value?

You mean adding a config like 'ORM.nullableNull'?

Not a fan of that. I would add such config only when absolutely necessary. For features that are not common, such configs are not easily discoverable by users and also get lost/forgotten, as we saw with the Error.convertStatementToDatabaseException config.

@dereuromark
Copy link
Member Author

It depends on if we want to keep both ExistsIn and ExistsInNullable around in 6.x
If we know we only need and want ExistsIn, we would add quite some baggage until then - in that case the flag would be the easier way, and documentable.

I can whip up the alternative and we can see what is a better fit.

@dereuromark dereuromark mentioned this pull request Nov 5, 2025
2 tasks
@dereuromark dereuromark marked this pull request as draft November 5, 2025 05:58
@dereuromark
Copy link
Member Author

dereuromark commented Nov 5, 2025

I prefer the updated PR here as we probably would not need 2 separate methods forward.
The main reason:

  • The ExistsIn with false itself seems pointless once we switched the default in 6.x, the upgrading of this can be avoided then.
  • No bake adjustment needed, only "docs". "app" skeleton can ship with the correct default.

But I made an alternative in case people want a speaking API here.

@dereuromark dereuromark marked this pull request as ready for review November 5, 2025 06:10
@dereuromark dereuromark changed the title Fix up allowNullableNull default. 5.next: Fix up allowNullableNull default. Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants