Closed damon18 closed 10 years ago
deleting users is not supported so IMO this is not a bug... it should break.... shouldn't it?
The user module has a process for deleting users.
So no, shouldn't break. I would suggest that in the case of a post or topic where the username attached to the post UID is gone that the post/topic should remain and display "unknown" or "removed" as username.
You might just need to move the doctrine calls to the controller so they are not being called in the template because otherwise there is no way to trap the not found exception. Unless there is a way to put magic in the entity methods, I would simply move the logic into the controller and use try/catch.
It's not that simple.
PRs are welcome to show POC. I do not see anyway to solve this other than
I'm leaning toward option 2 now (rather than my own core PR) but of course that is a core issue.
Here is an example that you can use exceptions. Obviously it can be done differently, but this is a POC.
views/lastPostBy.tpl
:
<span>
{if $last_post}
{php}
try {
$this->_tpl_vars['last_post']['poster']['user']['uid'];
} catch (Exception $e) {
$this->_tpl_vars['not_found'] = true;
}
{/php}
{if isset($not_found)}
{gt text="Last post by UNKNOWN USER"}<br />
{else}
{gt text="Last post by %s" tag1=$last_post.poster.user.uid|profilelinkbyuid}<br />
{/if}
{$last_post.post_time|dateformat:'datetimebrief'}
<a class="tooltips" title="{gt text="View latest post"}" href="{lastTopicUrl topic=$last_post.topic}"><i class='icon-hand-right'></i> {$last_post.topic.title|strip_tags|truncate:25:'...'}</a>
{/if}
</span>
It would seem to be that it would be more robust if Diskus actually stored the username in the "shadow" table. This way it can always get it. The uname does not change, just like the uid so it would be a much more robust way and you get better than "unknown user" you get the real user.
You could even go a step further - this was discussed with @rmburkhead a few years back - you can use the delete event to copy over the relevant data, like uname etc, to the "shadow" users table.
Either way, this reduces coupling. In reality, direct coupling to another module's entity is not fantastic, but rather using APIs which can provide an abstract interface and thus you couple to an external API of the users module rather than it's internals. That's ideal of course, but I dont know if such APIs exist. But it's something we can think about moving forward.
you still don't understand. did you try your POC? did it work?
yes. it's a copy paste from a working example using func=index page.
well that's good news. I'm going to look into solutions at the Entity level.
I'll also try to ask @guilhermeblanco by email if he has time to give advice. If there is a quick solution, I imagine he'll know immediately.
my thinking is to add some checks to the getUser()
method in the Entity. If the user is not set, then construct a "fake" and return that instead.
if @guilhermeblanco actually looks here, the entity I'm talking about is this one: https://github.com/zikula-modules/DizkusModule/blob/master/Zikula/Module/DizkusModule/Entity/ForumUserEntity.php
Here is his answer
It's very easy to add this support.
It's all around you entity mapping. We can natively rely on RDBMS drivers to achieve this.
Here is a sample on how to achieve this inside of your ForumUserEntity:
/**
* @ORM\ManyToOne(targetEntity="UserEntity", nullable=true)
* @ORM\JoinColumn(name="user_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $user;
That should do the work.
Once querying, don't forget to use LEFT JOIN, otherwise it'd strip all ForumUserEntity where UserEntity is NULL. =)
@drak that would not perfectly work in this file because it's also marked as an @ORM\Id field. You have to create an "id" field which will be your primary key, otherwise you'll end up having NULL in the user field, causing DB constraint issues.
Be careful about substituting uname for uid as the id field. As I mentioned elsewhere, it is possible that unames can be reused if deleted. That renders them not unique over time.
@guilhermeblanco - if I do $forumUserEntity->getUser()
what does Doctrine return if the associated entity is deleted but the column still contains the id value? I'm guessing either null
or an exception?
@craigh it should not happen, since you're the responsible to keep your Object Graph into a consistent state. This means you are responsible to trigger an update query if your user is deleted.
Using the excerpt I provided to @drak, we would rely on RDBMS to fix this for us. By adding an onDelete="SET NULL", this means that if the PK of User is removed, all ForumUserEntity will be updated setting the user_id to NULL when this user_id is the PK that you have removed, so you don't need to bother about it.
But supposing by any mean you forgot it and you have an exceptional situation where user does not exist, it does exist in a separate table, but your foreign key constraint got removed somehow. In this case, Doctrine will return you a Proxy of that User and once you attempt to initialize it, it'll throw a NoResultFoundException for you. =)
@guilhermeblanco the problem here is the relation to UserEntity is one outside of this module's control and jurisdiction. If a UserEntity is deleted, in this case, @craigh does not want to cascade the delete because he wants to keep the forum posts in-tact and just mark then as "unknown user". I've provided a different way to do it here but some entity level thing would be great. catching exceptions in the template is not as straightforward.
What I was hoping to do is something like this in the Entity:
public function getUser()
{
try {
$user = $this->user;
} catch (EntityNotFoundException $e) {
$user = 2; // actually I would create a "fake" user entity here
}
return $user;
}
but I just tested something like this and it doesn't work as I hoped
@guilhermeblanco - you're saying I cannot use the $user
as the PK for any of these items to work... yes?
It wont because the proxy is wrapping it. Unless it's possible to override the proxy @guilhermeblanco ? I searched the docs and did see references for that.
right. I'm just seeing that myself now. $this->user
is an uninitialized instance of the proxy class. What I'm wondering about is just testing for initialization. If not, then construct a fake.... possible?
I think this is sort of doing what I had in mind:
public function getUser()
{
$props = $this->getReflection()->getDefaultProperties();
if (isset($props['__isInitialized__']) && $props['__isInitialized__']) {
$user = $this->user;
} else {
$user = new ZikulaUser();
$user->setUid(1);
$user->setUname('user deleted');
}
return $user;
}
i dont think so because the proxy is wrapping the class. You would need to be able to customise the proxy class.
I think this is sort of doing what I had in mind:
well it is sort of working...
Excellent if it works. Not sure how it does but excellent.
hmmm. not as well as I had hoped. the __isInitialized__
property seems to always be false
:unamused:
ok, this is sort of working:
public function getUser()
{
try {
$uname = $this->user->getUname();
} catch (\Exception $e) {
$this->user = new ZikulaUser();
$this->user->setUid(self::FAKE_USER_ID);
$this->user->setUname('user deleted');
}
return $this->user;
}
Comments welcome on the PR.
In practice, how would the admin change the text "user deleted", just using translations or could there be an admin setting.
Also, this would set the same user deleted text for any missing user regardless of why they are missing.
Since you are using getUname to check if a user exists instead of the UID how would this affect the case where a users username has been changed? On a busy site I get requests to change members usernames about once a week.
In practice, how would the admin change the text "user deleted", just using translations or could there be an admin setting.
is there? no. could there be? yes, probably.
Also, this would set the same user deleted text for any missing user regardless of why they are missing.
Any deleted user - yes. same text.
Since you are using getUname to check if a user exists instead of the UID how would this affect the case where a users username has been changed?
It would not. The only time it will trigger this code is if the user is deleted entirely from the Core's UserEntity table.
fixed in #216
This may have only been working because the cache was not cleared. as soon as I (was forced to) cleared my cache there are a bunch of errors again. :disappointed:
I just tried to test this again with a fresh Dizkus install, but when I tried to delete the test user the user module threw out a page of errors. Starting with
Runtime Notice: call_user_func() expects parameter 1 to be a valid callback, non-static method Zikula\Module\DizkusModule\EventHandlers\SystemListeners::deleteUser() should not be called statically in C:\xampp\htdocs\core\src\vendor\symfony\symfony\src\Symfony\Component\EventDispatcher\EventDispatcher.php line 164
500 Internal Server Error - ContextErrorException
Should I open an issue in Core?
update and try again.
I've still not come up with any solutions to this other than reassigning all posts to another user. I'm thinking assigning to "anonymous" would work (uid=1)
Let me throw some more fuel onto the fire.
I see a lot of problems in the future for any modules that couple themselves to anther module's internals. Making a direct relation to another module's entities is a recipe for disaster and difficulty. Now you would be expecting the module to maintain BC on it's entities - but they are not a pubic API and it's not a reasonable assumption to make. Unlike class inheritance, there is no way to make an entity invisible, but in any case, a module that makes this kind of coupling is always going to have to play catch up. There surely must be a better way.
I've refrained from posting this before, but I'm looking into the future and trying to minimise difficulties in the future.
@damon18 - please test again. I think I've got it the way it should be.
@damon18 - please test again. I think I've got it the way it should be.
Sorry, I broke Dizkus.
Thinking about issue zikula/core#1322 I wanted to find out what currently happens when a user is deleted (by admin). So I created a user and made some posts in Dizkus, then deleted the user and visited Dizkus.