1

By every is know that SoftDeleteable is a Doctrine Extension:

That allows behavior to "soft delete" objects, filtering them at SELECT time by marking them with a timestamp as, but not Explicitly removing them from the database.

Now considering this, which would be consistent logic when inserting new rows in the table and taking the above marked as deleted but physically being there?

The point is that I have recently been forced to make use of this behavior in an application but when I am inserting new records, and logically, when one exists then I got a error like this:

An exception occurred while executing "INSERT INTO fos_user (username, username_canonical, email, email_canonical, enabled, salt, password, last_login, locked, expired, expires_at, confirmation_token, password_requested_at, roles, credentials_expired, credentials_expire_at, deletedAt, createdAt, updatedAt) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) 'with params ["admin1" "admin1" "admin", "admin", 1, "8ycal2x0eewwg0gw0o0gcw884ooossg", "886mLvLTi1yPdSBTR9Cfi + + a3lvideQ4pw89ZHDOWVz86kJqXjx7C1 1ZIwTvzET7N1Fk \ / yHsw10z3Cjm9k + m \ / g ==", null, 0, 0, null, null, null, 'a: 2: {i: 0; s: 16: \ "ROLE_PROFILE_ONE \" i: 1, s: 16: \ "ROLE_PROFILE_TWO \";} ", 0, null, null," 09/12/2014 18:16:01 ""9/12/2014 18:16:01"]:

SQLSTATE [23000]: Integrity constraint Violation: 1062 Duplicate entry 'admin1' for key 'UNIQ_957A647992FC23A8'

My question is, how you handle SoftdDeleteable to enter new records? An example of what yours do or fewer ideas would come to me well and would help.

ReynierPM
  • 17,594
  • 53
  • 193
  • 363
  • Are you wanting to keep the original "softdeleted" rows and not allow the repeated username or remove the original and replace it completely? – qooplmao Sep 17 '14 at 09:06
  • @Qoop well my idea is to keep the original, this is the idea behind SoftDeleteable or not? – ReynierPM Sep 17 '14 at 10:49
  • I don't think there's a specific idea behind it, it can either allow you to softdelete and then hard delete or softdelete and keep for records. I've added an answer. – qooplmao Sep 17 '14 at 11:23

1 Answers1

2

If you are wanting to keep the original then you would need to find someway to make sure the unique field hadn't been used before. I think the easiest way t do that would be to use a custom repository for your user field and disable the softdeleteable filter before the search.

By default the UniqueEntity uses findBy and the repository set for the class but it would make sense to create your own method with the filter disabled by default so as to avoid having to mess around with the constraint while leaving the regular method intact.

As you are using FOSUserBundle (or so it seems from the table name fos_user) you can set the repositoryClass in your mapping (the XML is a big load of mess but you can see it here)...

// Annotation
@ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository")

//YAML
Acme\UserBundle\Entity\User:
    type: entity
    repositoryClass: Acme\UserBundle\Entity\UserRepository

And then in your UserRepository just add you findIncludingSoftdeletedBy method disabling the softdeleteable filter like...

namespace Acme\UserBundle\Entity;

use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
    /**
     * Finds users by a set of criteria including sofdeleted.
     *
     * @param array      $criteria
     * @param array|null $orderBy
     * @param int|null   $limit
     * @param int|null   $offset
     *
     * @return array The objects.
     */
    public function findIncludingSoftdeletedBy(
        array $criteria, array $orderBy = null, $limit = null, $offset = null
    )
    {
        // Get array of enabled filters
        $enabledFilters = $this->em->getFilters()->getEnabledFilters();

        // If softdeleteable (or soft-deleteable depending on config) 
        // is in array of enabled filters disable it
        if (array_key_exists('softdeleteable', $endabledFilters)) {
            // disabled softdeleteable filter ($this->em being entity manager)
            $this->_em->getFilters()->disable('softdeleteable');
        }

        // return regular "findBy" including softdeleted users
        return $this->findBy($criteria, $orderBy, $limit, $offset);
    }
}

Update

I forgot this bit.

You would then need to create your own validation file that would reference this new validation constraint. (For FOSUserBundle and in YAML (I prefer YAML, XML looks like a physics book has been sick on my screen)).

Acme\UserBundle\Entity\User:
    constraints:
        - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
            fields: usernameCanonical
            errorPath: username
            message: fos_user.username.already_used
            // Your method instead of the default "findBy"
            method: findIncludingSoftdeletedBy
            groups: [ Registration, Profile ]
        - Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity:
            fields: emailCanonical
            errorPath: email
            message: fos_user.email.already_used
            // Your method instead of the default "findBy"
            method: findIncludingSoftdeletedBy
            groups: [ Registration, Profile ]

For more information on the UniqueEntity constraint see the docs, specifically..

fields
type: array | string [default option]

This required option is the field (or list of fields) on which this entity should be unique. For example, if you specified both the email and name field in a single UniqueEntity constraint, then it would enforce that the combination value where unique (e.g. two users could have the same email, as long as they don't have the same name also).

If you need to require two fields to be individually unique (e.g. a unique email and a unique username), you use two UniqueEntity entries, each with a single field.

qooplmao
  • 17,622
  • 2
  • 44
  • 69
  • this approach is nice for just one column but what happen if others are UNIQUE too? I'll need to repeat the same code several times right? For example in FOSUserBundle, which I'm using as you said before, `username` and `email` tough are uniques, but I have also others fields since my `User` class extends from `BaseUser` then how to work with the in this case? – ReynierPM Sep 17 '14 at 11:49
  • The `UniqueEntity` constraint build and array of `$criteria` from the fields you set and their values in the object and then passes that to your `method`, all you are doing is piggy backing what is already there and adding the ability to disable to softdeleteable filter. So just use the `UniqueEntity` constraint in the usual way but with the method set. – qooplmao Sep 17 '14 at 12:18
  • it's not working! Since in your code I don't see where `$em` is defined then I changed your line of code to this: `$this->getEntityManager()->getFilters()->disable('soft-deleteable');` which return this `Filter 'soft-deleteable' is not enabled.` so something isn't working. In the other side if I comment that line then the same error with duplicates is showed, any advice? – ReynierPM Sep 17 '14 at 21:25
  • Sorry, may bad. That was just a copy and paste from the docs. I'm not sure why it's not working. It may be because you are disabling the filter twice (once for each call, but it's already disabled). Checking whether the filter is enabled may do the job for you. I will update. – qooplmao Sep 18 '14 at 09:56
  • Also it may be because you are using the `StofDoctrineExtensionsBundle` which registers the filter as `softdeleteable` rather than `soft-deleteable` like the original `Gedmo` docs. Please let me know which and I will update my answer accordingly. – qooplmao Sep 18 '14 at 10:02