Skip to content
Advertisement

Laravel package base relationship models

in our web application we want to have a simple package base cms, for having that we have users, packages, features and user_package table on database

  1. each users can be have one package which we created user_package for that
  2. each user_package belongs to many users
  3. each user_package has on packages
  4. each packages can be have many features which we created features table
  5. each features belongs to many packages

when i try to get user package it, i think it should be:

user->user_package->package->[feature]

my models:

class User extends Authenticatable
{
    //...

    public function user_package()
    {
        return $this->hasOne(UserPackage::class);
    }
}

class UserPackage extends Model
{
    public function package()
    {
        return $this->belongsTo(Package::class);
    }
}

class Package extends Model
{
    public function feature(): HasMany
    {
        return $this->hasMany(Features::class);
    }
}

class Features extends Model
{
    public function package(): BelongsToMany
    {
        return $this->belongsToMany(Package::class);
    }
}

migrations:

Schema::create('packages', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('description')->nullable()->default('');
    $table->timestamp('created_at')->useCurrent();
    $table->timestamp('updated_at')->useCurrent();
});

Schema::create('features', function (Blueprint $table) {
    $table->id();
    $table->foreignId('packages_id')->nullable()
          ->constrained()->cascadeOnUpdate()
          ->cascadeOnDelete();
    $table->string('title');
    $table->text('description')->nullable()->default('');
    $table->timestamp('created_at')->useCurrent();
    $table->timestamp('updated_at')->useCurrent();
});


Schema::create('user_packages', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()
        ->cascadeOnUpdate()
        ->cascadeOnDelete();
    $table->foreignId('packages_id')->nullable()->constrained()
        ->cascadeOnUpdate()
        ->cascadeOnDelete();
    $table->longText('features')->nullable()->default('');
    $table->integer('price');
    $table->dateTime('start_date');
    $table->dateTime('end_date');
    $table->timestamp('created_at')->useCurrent();
    $table->timestamp('update_at')->useCurrent();
});

now when i try to get data i get null in package relation ship

Route::get('/test',function(){
   dd(auth()->user()->with(['user_package'=>function($query){
       $query->with(['package'=>function($package){
           $package->with('feature')->get();
       }])->first();
   }])->first());
});

output:

AppModelsUser {#1319 ▼
  #hidden: array:2 [▶]
  #casts: array:1 [▶]
  #connection: "mysql"
  #table: "users"
  ...
  #relations: array:1 [▼
    "user_package" => AppModelsUserPackage {#1570 ▼
      #connection: "mysql"
      #table: "user_packages"
      ...
      #dispatchesEvents: []
      #observables: []
      #relations: array:1 [▼
        "package" => null
      ]
      ...
    }

Advertisement

Answer

I think the crux of your question comes down to fetching nested relationships, which is as simple as auth()->user()->with('user_package.package.feature') but there is a problem with your relationships.

Your relationship between Package and Feature is broken; since the features table has a package_id column, a feature by definition cannot “belong to many” packages.

class User extends Authenticatable
{
    public function user_package(): HasOne
    {
        return $this->hasOne(UserPackage::class);
    }
}

class UserPackage extends Model
{
    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function package(): BelongsTo
    {
        return $this->belongsTo(Package::class);
    }
}

class Package extends Model
{
    public function features(): HasMany
    {
        return $this->hasMany(Feature::class);
    }

    /**
     * It never hurts to define both ends of the relationship
     * even if they aren't being used
     */
    public function user_packages(): HasMany
    {
        return $this->hasMany(UserPackage::class);
    }
}

class Feature extends Model
{
    public function package(): BelongsTo
    {
        return $this->belongsTo(Package::class);
    }
}

Now, using the relationship, you can get the package features:

dump(Auth::user()->load('user_package.package.features'))

You’ve got some naming problems that I corrected in my examples above – relationship methods that return a single model should be singular, others should be plural. Class names should never be plural words (i.e. Feature not Features.)

Generally speaking, there’s no need to create a pivot class if all it’s doing is connecting two models. I’m not going to get into that since it would make for a much longer answer, but it’s something to keep in mind.

And it’s a matter of personal taste, but your class names should be one word only (i.e. Subscription instead of UserPackage) as it makes figuring out things like relationship names more intuitive.

User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement