Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added (optional) morphable user #420

Closed
wants to merge 14 commits into from
Closed

Added (optional) morphable user #420

wants to merge 14 commits into from

Conversation

crashkonijn
Copy link

Changes

  • Added config option for morphable mode. Everything defaults to 'single mode', to maintain backwards compatibility.
  • Added config option for guard order.
  • Changed current UserSolver to UserIdSolver.
  • Added UserIdSolver and UserClassSolver.
  • Added commented value to migration file, which needs to be uncommented for morphable to work.
  • Added tests to include new classes.
  • Adjusted tests to test both modes.

@quetzyg
Copy link
Contributor

quetzyg commented May 9, 2018

Hi @crashkonijn,

Care to explain what exactly is the purpose of this PR? What does it solve, and so on?

Cheers

@crashkonijn
Copy link
Author

crashkonijn commented May 9, 2018

Hello @quetzyg,

We really wanted to use this package in our project, but in almost all project we have different models for different roles within the project. However this project very clearly only supported a single user type, where feedback in previous requests was to change the projects to match this package by using just a single user model.

After some discussing within our team we decided to extend this package and implement this feature. Because we really like this package and love to give something back to the community we tried to implement it with as little impact on existing code as possible. The morphable user has been implemented in such a way that it should be backwards compatible, and is completely optional.

We really hope this PR will be accepted, because we feel that would benefit more people than hosting a separate version.

If there's any feedback or suggestions to improve this I'm very willing to make those changes.

Cheers!

Edit: Typo

@quetzyg
Copy link
Contributor

quetzyg commented May 9, 2018

However this project very clearly only supported a single user type, where feedback in previous requests was to change the projects to match this package by using just a single user model.

While I never actually tested this package with a polymorphic User model, I'm pretty sure you could use it as is, right now. I mean, you just have to resolve the User id and store it.

Then you should be able to get the user with: $audit->user->userable

Or am I missing something?

@crashkonijn
Copy link
Author

That would indeed be an option yes, but unfortunately our users have different models and nothing in common in that regard. Just like a normal polymorphic relation we need to store the user type as well, which is what this PR does.

The user just remains $audit->user, and the other way around can stay model->audits this way, which keeps things simple.

@remkobrenters
Copy link

@quetzyg,

The use case is for example you have multiple userTypes in a platform. By separating them in different models and Laravel guards not only can you keep the data separate (to prevent endless empty columns for users with different datasets) but it also allows you to have multiple user accounts with the same email address and, even cooler, allows you to be logged in as various users at the same time (for example a admin that is acting as a front-end user for support purposes).

This requires a morphable solution as the userId is not 'unique' (as each entity has it's own table and unique Id's) so you need the userType to retreive the unique id + type identity.

@quetzyg
Copy link
Contributor

quetzyg commented May 9, 2018

@remkobrenters, yeah I'm aware, but given each morph user type does relate to a base User, that's the id I was referring to in my earlier comment.

So, you would resolve the base User id, which would be stored, and then you would get model via:
$audit->user->userable (or whatever method name you have defined for the morphTo() and the inverse would be like $user->user->audits.

But I get your point.

Copy link
Contributor

@quetzyg quetzyg left a comment

Choose a reason for hiding this comment

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

Left a few comments.

config/audit.php Outdated
|
*/

'morphable' => false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Move this to the user config down bellow, a line after 'model' => App\User::class,

config/audit.php Outdated
|--------------------------------------------------------------------------
|
| Define the User guards, in the order they should be checked.
| This is used when in 'morphable mode'.
Copy link
Contributor

Choose a reason for hiding this comment

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

We could actually use this even when not in morphable mode.

@@ -28,6 +28,7 @@ class CreateAuditsTable extends Migration
Schema::create('audits', function (Blueprint $table) {
$table->increments('id');
$table->unsignedInteger('user_id')->nullable();
//$table->string('user_type')->nullable();
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not be commented, since we want the column.

Copy link
Author

Choose a reason for hiding this comment

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

The reason that I left it commented out is because you don't need it for the 'normal' mode, just when you need it morphable. Still want me to uncomment this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yup

config/audit.php Outdated
@@ -49,6 +76,8 @@
|
*/
'resolver' => [
'user_id' => OwenIt\Auditing\Resolvers\UserIdResolver::class,
Copy link
Contributor

Choose a reason for hiding this comment

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

Just one resolver, since both are doing pretty much the same thing, just calling two different methods (->getMorphClass() and ->getAuthIdentifier()).

@@ -282,18 +283,24 @@ public function toAudit(): array

$tags = implode(',', $this->generateTags());

return $this->transformAudit([
Copy link
Contributor

Choose a reason for hiding this comment

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

With just one User resolver, we would get the user at this point, like:

$user = $this->resolveUser();

@@ -43,9 +42,9 @@ public function auditable(): MorphTo;
/**
* User responsible for the changes.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
* @return \Illuminate\Database\Eloquent\Relations\MorphTo | \Illuminate\Database\Eloquent\Relations\BelongsTo
Copy link
Contributor

Choose a reason for hiding this comment

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

No space before/after |, please

*/
protected static function resolveUser()
{
$userResolver = \Config::get('audit.resolver.user');
Copy link
Contributor

Choose a reason for hiding this comment

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

Import Config and remove \

*/
protected static function resolveUser()
{
$userResolver = \Config::get('audit.resolver.user');
Copy link
Contributor

Choose a reason for hiding this comment

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

Import Config and remove \

@@ -23,6 +23,36 @@ class UserResolver implements \OwenIt\Auditing\Contracts\UserResolver
*/
public static function resolve()
{
return Auth::check() ? Auth::user()->getAuthIdentifier() : null;
return \Config::get('audit.morphable', false) ? static::resolveMorphable() : static::resolveSingle();
Copy link
Contributor

Choose a reason for hiding this comment

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

Import Config and remove \

*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
private static function resolveMorphable()
Copy link
Contributor

Choose a reason for hiding this comment

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

Like I stated earlier, we can default to this behaviour, even when not using a morph user.

@remkobrenters
Copy link

@quetzyg,

I'm afraid you do not understand the whole case. When you have multiple user entities there is no main user table or id. They all live in separate tables without relations in between. Without the userType there is no way of telling in which table to look for the user who triggered the audit-event.

@quetzyg
Copy link
Contributor

quetzyg commented May 9, 2018

I'm afraid you do not understand the whole case. When you have multiple user entities there is no main user table or id. They all live in separate tables without relations in between. Without the userType there is no way of telling in which table to look for the user who triggered the audit-event.

But then, are you actually using Laravel's polymorphic relationships, or do you have something else which you're also calling polymorphic?

Because the whole point of Laravel's polymorphic relationships is having a base User (in this case), and then you have the extended models (Admin, Manager or whatever) with their specific properties.

The idea is that they share basic functionality (that the User model has), like id, email, name, surname, etc.

PS: I'm aware that each other model (Admin, Manager or whatever) will have their own id too, but the User id is what I'm referring to in my example.

@crashkonijn
Copy link
Author

crashkonijn commented May 9, 2018

A polymorphic relation doesn't rely on that at all, the whole point is that it can be any model.

Even the example in the docs uses 2 totally unrelated models (posts and videos) and adds comments to them.

https://laravel.com/docs/5.6/eloquent-relationships#polymorphic-relations

This package even uses it for the object it's watching. The whole reason for these changes is to lift the limitation of a single object/model being the user.

@quetzyg
Copy link
Contributor

quetzyg commented May 9, 2018

A polymorphic relation doesn't rely on that at all, the whole point is that it can be any model.

Yes, I'm fully aware.

Even the example in the docs uses 2 totally unrelated models (posts and videos) and adds comments to them.

You would think they're unrelated, but the fact is that you can comment on both models, in the same way a generic User can have different properties/roles through user types.

This package even uses it for the object it's watching. The whole reason for these changes is to lift the limitation of a single object/model being the user.

My point with this conversation is to understand how you're using such relation in your user models, since the whole base User model and extended (polymorphic) models, doesn't seem to apply.

@remkobrenters
Copy link

@quetzyg,

I think we're not on the same page. Since L5.3 (correct me if i'm wrong on the exact version) Laravel allows you to create separate authenticable entities with their own class and table. For example if you have a huge web-shop with customers which contain a lot of customer-related data (addresses, orders, invoices, wish-lists) and you have a admin environment that has content managers, admins etc. You can create two different user tables, customers & managers for example. They do not share a central users table or Id and do not extend each other in any way.

Laravel allows you to have a guard for each authenticable entity with it's own configuration. Allowing you for example to load the managers from another database, using another session 'container', authentication method (maybe two-factor for your managers) or whatever you like. Separating them completely from the other user accounts.

In this case both customers and managers can perform auditable actions and without the morphable user_type (which contains the entity class) there is no way of telling what entity belongs to the identity that performed the action.

I think this tutorial shows a really simple but clear explanation of this 'new' feature and it's implementation: https://scotch.io/@sukelali/how-to-create-multi-table-authentication-in-laravel

@quetzyg
Copy link
Contributor

quetzyg commented May 10, 2018

Ahh OK, I finally understood what you have going there. The Authenticatable interface is just a contract, that doesn't have anything to do with polymorphic relations.

But since you kept referring to it as such (morphable), that's what threw me off.

@remkobrenters
Copy link

@quetzyg,

Cool. Sorry for the confusion. We think the suggested PR could be a very welcome addition for parties that, like us, use a lot of separate authenticable entities to keep various datasets separate.

@quetzyg
Copy link
Contributor

quetzyg commented May 10, 2018

Actually, if you don't mind, I'm gonna simplify this a little bit in another branch.

@crashkonijn
Copy link
Author

Alright, I'm very curious to see what you changed!

@quetzyg quetzyg mentioned this pull request May 10, 2018
6 tasks
@quetzyg
Copy link
Contributor

quetzyg commented May 10, 2018

So basically, I got rid of unnecessary configurations. We're always gonna store the User as a morph, which avoids the programmer from choosing between normal and morphable modes.

This simplifies tests, overall logic and migrations (no need to comment user_type column, since it's always used).

Great job @crashkonijn / @remkobrenters !

@quetzyg quetzyg closed this May 10, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants