mirror of
https://github.com/barryvdh/laravel-ide-helper.git
synced 2026-08-18 01:57:13 +00:00
Feature: Add Config Option to Enforce Nullable Relationships (#1580)
* feat: add config option for nullable relationships Introduce enforce_nullable_relationships configuration to control nullable Eloquent relationships. * refactor: update logic for nullable relationships Update isRelationNullable method to respect new config option. * test: add tests for nullable relationship config - Verify behavior of enforce_nullable_relationships configuration option. - Create snapshot to reflect enforce_nullable_relationships set to false. * docs: improve documented context and usage * docs: update CHANGELOG with enforce_nullable_relationships option
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
- Drop support for Laravel versions earlier than 11.15.
|
- Drop support for Laravel versions earlier than 11.15.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- Introduce `enforce_nullable_relationships` configuration option to control how nullable Eloquent relationships are enforced during static analysis. This provides flexibility for scenarios where application logic ensures data integrity without relying on database constraints. [#1580 / jeramyhing](https://github.com/barryvdh/laravel-ide-helper/pull/1580)
|
||||||
|
|
||||||
- Add support for AsCollection::using and AsEnumCollection::of casts [#1577 / uno-sw](https://github.com/barryvdh/laravel-ide-helper/pull/1577)
|
- Add support for AsCollection::using and AsEnumCollection::of casts [#1577 / uno-sw](https://github.com/barryvdh/laravel-ide-helper/pull/1577)
|
||||||
|
|
||||||
|
|||||||
@@ -299,6 +299,29 @@ return [
|
|||||||
*/
|
*/
|
||||||
'additional_relation_return_types' => [],
|
'additional_relation_return_types' => [],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Enforce nullable Eloquent relationships on not null columns
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| When set to true (default), this option enforces nullable Eloquent relationships.
|
||||||
|
| However, in cases where the application logic ensures the presence of related
|
||||||
|
| records it may be desirable to set this option to false to avoid unwanted null warnings.
|
||||||
|
|
|
||||||
|
| Default: true
|
||||||
|
| A not null column with no foreign key constraint will have a "nullable" relationship.
|
||||||
|
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
| * @property-read BelongsToVariation|null $notNullColumnWithNoForeignKeyConstraint
|
||||||
|
|
|
||||||
|
| Option: false
|
||||||
|
| A not null column with no foreign key constraint will have a "not nullable" relationship.
|
||||||
|
| * @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
| * @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
'enforce_nullable_relationships' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Run artisan commands after migrations to generate model helpers
|
| Run artisan commands after migrations to generate model helpers
|
||||||
|
|||||||
@@ -836,13 +836,15 @@ class ModelsCommand extends Command
|
|||||||
$fkProp = $reflectionObj->getProperty('foreignKey');
|
$fkProp = $reflectionObj->getProperty('foreignKey');
|
||||||
$fkProp->setAccessible(true);
|
$fkProp->setAccessible(true);
|
||||||
|
|
||||||
|
$enforceNullableRelation = $this->laravel['config']->get('ide-helper.enforce_nullable_relationships', true);
|
||||||
|
|
||||||
foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) {
|
foreach (Arr::wrap($fkProp->getValue($relationObj)) as $foreignKey) {
|
||||||
if (isset($this->nullableColumns[$foreignKey])) {
|
if (isset($this->nullableColumns[$foreignKey])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!in_array($foreignKey, $this->foreignKeyConstraintsColumns, true)) {
|
if (!in_array($foreignKey, $this->foreignKeyConstraintsColumns, true)) {
|
||||||
return true;
|
return $enforceNullableRelation;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,4 +46,23 @@ class Test extends AbstractModelsCommand
|
|||||||
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
|
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
|
||||||
$this->assertMatchesMockedSnapshot();
|
$this->assertMatchesMockedSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRelationNotNullable(): void
|
||||||
|
{
|
||||||
|
// Disable enforcing nullable relationships
|
||||||
|
Config::set('ide-helper.enforce_nullable_relationships', false);
|
||||||
|
|
||||||
|
$command = $this->app->make(ModelsCommand::class);
|
||||||
|
|
||||||
|
$tester = $this->runCommand($command, [
|
||||||
|
'--write' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertSame(0, $tester->getStatusCode());
|
||||||
|
$this->assertStringContainsString('Written new phpDocBlock to', $tester->getDisplay());
|
||||||
|
$this->assertMatchesMockedSnapshot();
|
||||||
|
|
||||||
|
// Re-enable default enforcing nullable relationships
|
||||||
|
Config::set('ide-helper.enforce_nullable_relationships', true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+266
@@ -0,0 +1,266 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $not_null_column_with_foreign_key_constraint
|
||||||
|
* @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
* @property int|null $nullable_column_with_foreign_key_constraint
|
||||||
|
* @property int|null $nullable_column_with_no_foreign_key_constraint
|
||||||
|
* @property-read BelongsToVariation $notNullColumnWithForeignKeyConstraint
|
||||||
|
* @property-read BelongsToVariation $notNullColumnWithNoForeignKeyConstraint
|
||||||
|
* @property-read BelongsToVariation|null $nullableColumnWithForeignKeyConstraint
|
||||||
|
* @property-read BelongsToVariation|null $nullableColumnWithNoForeignKeyConstraint
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNotNullColumnWithForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNotNullColumnWithNoForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNullableColumnWithForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|BelongsToVariation whereNullableColumnWithNoForeignKeyConstraint($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class BelongsToVariation extends Model
|
||||||
|
{
|
||||||
|
public function notNullColumnWithForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(self::class, 'not_null_column_with_foreign_key_constraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function notNullColumnWithNoForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(self::class, 'not_null_column_with_no_foreign_key_constraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nullableColumnWithForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(self::class, 'nullable_column_with_foreign_key_constraint');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nullableColumnWithNoForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(self::class, 'nullable_column_with_no_foreign_key_constraint');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property int $not_null_column_with_foreign_key_constraint
|
||||||
|
* @property int $not_null_column_with_no_foreign_key_constraint
|
||||||
|
* @property int|null $nullable_column_with_foreign_key_constraint
|
||||||
|
* @property int|null $nullable_column_with_no_foreign_key_constraint
|
||||||
|
* @property-read CompositeBelongsToVariation $bothNonNullableWithForeignKeyConstraint
|
||||||
|
* @property-read CompositeBelongsToVariation $nonNullableMixedWithoutForeignKeyConstraint
|
||||||
|
* @property-read CompositeBelongsToVariation|null $nullableMixedWithForeignKeyConstraint
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation whereId($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation whereNotNullColumnWithForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation whereNotNullColumnWithNoForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation whereNullableColumnWithForeignKeyConstraint($value)
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|CompositeBelongsToVariation whereNullableColumnWithNoForeignKeyConstraint($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class CompositeBelongsToVariation extends Model
|
||||||
|
{
|
||||||
|
public $table = 'belongs_to_variations';
|
||||||
|
|
||||||
|
public function bothNonNullableWithForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
// Note, duplicating the keys here for simplicity.
|
||||||
|
return $this->belongsTo(
|
||||||
|
self::class,
|
||||||
|
['not_null_column_with_foreign_key_constraint', 'not_null_column_with_foreign_key_constraint'],
|
||||||
|
['not_null_column_with_foreign_key_constraint', 'not_null_column_with_foreign_key_constraint'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nonNullableMixedWithoutForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(
|
||||||
|
self::class,
|
||||||
|
['not_null_column_with_foreign_key_constraint', 'not_null_column_with_no_foreign_key_constraint'],
|
||||||
|
['not_null_column_with_foreign_key_constraint', 'not_null_column_with_no_foreign_key_constraint'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function nullableMixedWithForeignKeyConstraint(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(
|
||||||
|
self::class,
|
||||||
|
['nullable_column_with_no_foreign_key_constraint', 'not_null_column_with_foreign_key_constraint'],
|
||||||
|
['nullable_column_with_no_foreign_key_constraint', 'not_null_column_with_foreign_key_constraint'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Models;
|
||||||
|
|
||||||
|
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\ModelsOtherNamespace\AnotherModel;
|
||||||
|
use Barryvdh\LaravelIdeHelper\Tests\Console\ModelsCommand\Relations\Traits\HasTestRelations;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphOne;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphToMany;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @property int $id
|
||||||
|
* @property-read Simple $relationBelongsTo
|
||||||
|
* @property-read AnotherModel $relationBelongsToInAnotherNamespace
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationBelongsToMany
|
||||||
|
* @property-read int|null $relation_belongs_to_many_count
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationBelongsToManyWithSub
|
||||||
|
* @property-read int|null $relation_belongs_to_many_with_sub_count
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationBelongsToManyWithSubAnother
|
||||||
|
* @property-read int|null $relation_belongs_to_many_with_sub_another_count
|
||||||
|
* @property-read AnotherModel $relationBelongsToSameNameAsColumn
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationHasMany
|
||||||
|
* @property-read int|null $relation_has_many_count
|
||||||
|
* @property-read Simple|null $relationHasOne
|
||||||
|
* @property-read Simple $relationHasOneWithDefault
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationMorphMany
|
||||||
|
* @property-read int|null $relation_morph_many_count
|
||||||
|
* @property-read Simple|null $relationMorphOne
|
||||||
|
* @property-read Model|\Eloquent $relationMorphTo
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationMorphedByMany
|
||||||
|
* @property-read int|null $relation_morphed_by_many_count
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationSampleRelationType
|
||||||
|
* @property-read int|null $relation_sample_relation_type_count
|
||||||
|
* @property-read Model|\Eloquent $relationSampleToAnyMorphedRelationType
|
||||||
|
* @property-read \Illuminate\Database\Eloquent\Collection<int, Simple> $relationSampleToAnyRelationType
|
||||||
|
* @property-read int|null $relation_sample_to_any_relation_type_count
|
||||||
|
* @property-read Simple $relationSampleToBadlyNamedNotManyRelation
|
||||||
|
* @property-read Simple $relationSampleToManyRelationType
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|Simple newModelQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|Simple newQuery()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|Simple query()
|
||||||
|
* @method static \Illuminate\Database\Eloquent\Builder|Simple whereId($value)
|
||||||
|
* @mixin \Eloquent
|
||||||
|
*/
|
||||||
|
class Simple extends Model
|
||||||
|
{
|
||||||
|
use HasTestRelations;
|
||||||
|
|
||||||
|
// Regular relations
|
||||||
|
public function relationHasMany(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationHasOne(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationHasOneWithDefault(): HasOne
|
||||||
|
{
|
||||||
|
return $this->hasOne(Simple::class)->withDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationBelongsTo(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationBelongsToMany(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationBelongsToManyWithSub(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->belongsToMany(Simple::class)->where('foo', 'bar');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationBelongsToManyWithSubAnother(): BelongsToMany
|
||||||
|
{
|
||||||
|
return $this->relationBelongsToManyWithSub()->where('foo', 'bar');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationMorphTo(): MorphTo
|
||||||
|
{
|
||||||
|
return $this->morphTo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationMorphOne(): MorphOne
|
||||||
|
{
|
||||||
|
return $this->morphOne(Simple::class, 'relationMorphTo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationMorphMany(): MorphMany
|
||||||
|
{
|
||||||
|
return $this->morphMany(Simple::class, 'relationMorphTo');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationMorphedByMany(): MorphToMany
|
||||||
|
{
|
||||||
|
return $this->morphedByMany(Simple::class, 'foo');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom relations
|
||||||
|
|
||||||
|
public function relationBelongsToInAnotherNamespace(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(AnotherModel::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationBelongsToSameNameAsColumn(): BelongsTo
|
||||||
|
{
|
||||||
|
return $this->belongsTo(AnotherModel::class, __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSampleToManyRelationType()
|
||||||
|
{
|
||||||
|
return $this->testToOneRelation(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSampleRelationType()
|
||||||
|
{
|
||||||
|
return $this->testToManyRelation(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSampleToAnyRelationType()
|
||||||
|
{
|
||||||
|
return $this->testToAnyRelation(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSampleToAnyMorphedRelationType()
|
||||||
|
{
|
||||||
|
return $this->testToAnyMorphedRelation(Simple::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function relationSampleToBadlyNamedNotManyRelation()
|
||||||
|
{
|
||||||
|
return $this->testToBadlyNamedNotManyRelation(Simple::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user