1

Could you help me understand the right way to extend existing models? I'm developing a package and want to do as much as possible separated from the main application.

I want to have all existing functionality of the User model, but only add a relation to another model. In my package User I can have several (hasMany) Article. Instead of adding a method to \App\User I created a new model in my package class User extends \App\User:

namespace Package\Sample;

use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;

/**
 * Class User
 * @package Package\Sample
 */
class User extends \App\User
{
    use HasApiTokens, Notifiable;

    public function articles()
    {
        return $this->hasMany(Article::class);
    }
}

To make it work I add the configuration for my package auth.php:

'providers' => [
        'users' => [
        'driver' => 'eloquent',
        'model'  => \Package\Sample\User::class,
    ],
],
'guards' => [
        'api' => [
        'driver' => 'passport',
        'provider' => 'users',
    ],
],

My question: Is it a "best practice" to extend functionality of existing models?

Karl Hill
  • 12,937
  • 5
  • 58
  • 95
Olga Zhe
  • 163
  • 7
  • Look how spatie does it: they do not touch User model they require users to use traits. https://github.com/spatie/laravel-permission/tree/master/src/Traits – Kyslik Oct 12 '18 at 08:43
  • @Kyslik nice! A good example with traits. Thank you! In [the documentation|https://github.com/spatie/laravel-permission#usage] one suppose to add a trait `Spatie\Permission\Traits\HasRoles` to `User` model and this is what I want to avoid. I want to do it programmatically and not require it in installation for my package. I don't know if it is possible, that's why I'm asking for a best practice. Maybe to require to add a trait during installation is a best practice :) – Olga Zhe Oct 15 '18 at 11:42

4 Answers4

2

I solved it by using the class_alias() php function.

In my package service provider I setup an alias of the model class defined for Authentication in /config/auth.php like this:

public function boot(){   

       class_alias(config("auth.providers.users.model"), 'ParentModel');

}

then I use ParentModel class where needed:

use ParentModel;

class Agent extends ParentModel {
...

}

Hope It makes sense for someone

PaianoA
  • 21
  • 2
1

that will prevent edits on the \Package\Sample\User::class. any new method needed will result in a package update.

why not declare a trait in your package containing your methods and use them in the App\User::class like what laravel is using.

namespace App;

use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;

class User extends Authenticatable
{
    use Notifiable;
N69S
  • 16,110
  • 3
  • 22
  • 36
  • That is true, I'll have to use `\Package\Sample\User` model everywhere and extend it in other packages. What I don't understand is - How to make my package be as much independent as possible and not to change main application code. – Olga Zhe Oct 12 '18 at 08:34
  • as mentioned in the answer, use `Traits`. take a look at `Illuminate\Notifications\Notifiable` for an example – N69S Oct 12 '18 at 08:50
0

I tried using the class_alias and although it works for basic usage, when things got more complicated my user class couldn't cut it. For example, my notifications where using the local package user type and not showing in app.

After doing more research I found using a trait IS the proper way like others have mentioned.

I found a comprehensive guide here: https://laravelpackage.com/08-models-and-migrations.html#approach-2-using-a-polymorphic-relationship

the gist:

Create a trait in your package:

Important here you can setup whatever the relationship you need depending on you db modeling.

 // 'src/Traits/HasPosts.php'
<?php

namespace JohnDoe\BlogPackage\Traits;

use JohnDoe\BlogPackage\Models\Post;

trait HasPosts
{
  public function posts()
  {
    return $this->morphMany(Post::class, 'author');
  }
}

Add the use in whatever user classes it applies to in your app

 // 'App\Models\User.php'
<?php

namespace App\Models;

use JohnDoe\BlogPackage\Traits\HasPosts;

class User extends Authenticatable
{
   use HasPosts;
   ...

Lastly, you'll need to add an extra field in the db for the user_type where you are using the user_id.

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class AddUserTypeToPostTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->tinyText('user_type')
                ->comment('User class type')
                ->nullable();
        });
    }

    /**
    * Reverse the migrations.
    *
    * @return void
    */
    public function down()
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropColumn('user_type');
        });
    }
}
Mr Seche
  • 43
  • 9
-1

You need to Add config(['auth.providers.users.model' => Myname\Myproject\App\Models\User::class]); to the boot-method inside my package-service-provider.

And Create new Class in your package.

namespace Myname\Myproject\App\Models;

class User extends \App\User
{

    public function roles(){
        return $this->belongsToMany('Myname\Myproject\App\Models\Role', 'user_role', 'user_id', 'role_id');
    }
}