[solved] Best way to extend model inside a module or extension?

Hi Guys,

To me it seems this should be a common task, but maybe I’m just too dumb,

have not enough skills or my brain is malfunctioning at the moment…

I just can’t figure this out.

Lets say you write a module.

In this module you need specific stuff inside the User Model,

which is only used by this module.

So you want to extend a Model from main application to keep all module-specific stuff "inside" your module.

Currently I’m doing it like this:




namespace myModule\models;


use path\to\main\application\User as AppUser;


class User extends AppUser

{

    /**

     * Extend this class to module specific queries & functions

     */

}



This works - but the problem is:

If somebody else wants to use your module,

he has to "dig inside" your code and has to change the path according to his app-configuration.

Which is not good, since he always has to change this again when there is an update.

(You could compare the issue with changing code in "vendor" and there is an update of the composer package - it would overwrite all changes)

So it would be nice, if there is a way to do something like this:




'custom-module' => [

    'class' => 'mynamespace\path\to\Module',

    'userClass' => 'app\models\User'

],



And inside the module extend from given "userClass".

I hope the issue is clear described.

If not - let me know.

Every help / hint regarding this issue is very welcome.

Best Regards

Could you tell us what is the purpose of extending it?

If the parent model needs to implement IdentifyInterface you can always access it with Yii::$app->user->identity.

In other case you don’t know what is inside this model anyway (from the point of view of the anonymous user that installed it).

Hey, thanks for your answer!

But it seems like I’m missing something here… :(

Another Example:

Lets say you want to build a blog extension.

So you have Models inside your extension like:

Post, Comment etc.

Now you want to know the author of a Post.

So you need something like this in your Post Model:




use path\to\model\User;

// ... ... ..

public function getAuthor() {

    return $this->hasOne(User::className(), ['id' => 'author_id']);

}



Now you could do it like these guys and write this use statement in every extension-model where "User" is needed:

Or you could create a User model inside your extension (like I did above).

Use this extension-user-model in every other extenion-model.

Benefit is:

When I want to reuse the extension I only have to adjust the use statement in the extensions user-model instead of changing it everywhere.

What am I missing / not understanding here?

How would you handle this?

Best Regards

That’s not the best idea to include outside model in vendor module like in this yii2-blog. What if IdentityInterface is implemented in other model?

Anyway - for simple relation you can always set up your extension to get "User" model namespace in a configuration parameter so you can use it in your class.

I’ve had the same problem with forum module I’m making now. User can use my identity class for signing in but I’ve also added option to use already existing model. Namespace of it is passed in configuration.

Hi Bizley!

Thanks again for your answer and sorry for my late reply…

That was the reason I asked this question in the first place… :)

Since my way of doing it so far has basically the same "wrong" way.

To keep the blog example…

I would then do something like this:

In config:




'blog' => [

    'class' => 'mynamespace\blog\Module', 

    'userClass' => 'app\models\User', 

],



In any extension model (Blog Post for example):




public function getAuthor()

{

    $module = Module::getInstance();

    $userClass = new $module->userModel;

        

    return $this->hasOne($userClass::className(), ['id' => 'user_id']);

}



Basically Correct?

Or would you suggest some better way?

If yes - would you mind sharing an example?

And for clarification - one last question regarding this issue:

How do you proceed if you want to have specific code inside a main-app model?

Lets say - for example to keep it simple:

You want a relation in user model to access the posts of a specific user like this:




$user = new \path\to\model\User::findOne(['id' => 1])

$user->posts();



So you need something like:




public function getPosts()

{

    return $this->hasMany(\mynamespace\blog\models\Post::className(), ['user_id' => 'id']);

}



Would you simply define an interface and leave the implementation up to the user of your extension?

Or would you code behaviors/traits to make implementation "easier" for the user?

Or would you avoid extending the main-app class in any case?

Or any other solution. :)

Thanks again.

Really appreciate your thoughts & support!

Regards

No need to create new instance.




public function getAuthor()

{

    return $this->hasOne(Module::getInstance()->userModel, ['id' => 'user_id']);

}



There is of course the possible problem with ‘id’ being the ID column of User’s table. To make sure we can use something like




public function getAuthor()

{

    $class = Module::getInstance()->userModel;

    return $this->hasOne($class, [$class::primaryKey()[0] => 'user_id']);

}



primaryKey() returns array even if it isn’t composite (I’m not sure how to deal with composite PKs in this case).

Ok, now the second part.

I assume you ask about the relation user-post to provide app author with some instruction how to establish it using your module data. I think that preparing the behavior with the relation is good option. App author can choose to use it in his model or not and implementation instruction is simple and always the same.

Now I think everything is clear to me.

Thank you very much!

… and have a nice weekend! :)