简体   繁体   中英

yii2 unique validator only when field not empty

In Yii2 I have two fields in my Database: email and shopId

  • Email and shopId should be unique together
  • Email could also be empty ( NULL ) while shopId is always an integer

These are my rules in the Model:

[['email'],'default','value' => NULL],
[['shopId'], 'integer'],
[['email','shopId'], 'unique', 'targetAttribute' => ['email', 'shopId'], 'message' => 'Already taken!'],

This is not working when I have two entries with eg email="NULL" and shopId="1" .

How can I solve this?

While your solution is working it isn't technically correct. Validation rule

[['email','shopId'], 'unique', 'targetAttribute' => ['email', 'shopId']]

will validate email to be unique with given shopId if email is not empty (desired functionality), but it will also validate shopId to be unique with given email if shopId is not empty (unwanted). You are validating two fields with two queries to DB.

Validation rule that fits your needs is

[['email'], 'unique', 'targetAttribute' => ['email', 'shopId']]

saying "If email is not empty, check if combination of email and shopId is unique and bind result to email attribute".

skipOnEmpty属性设置为false http://www.yiiframework.com/doc-2.0/yii-validators-validator.html# $skipOnEmpty-detail

I used the when condition inside the rule

[
    ['email', 'shopId'],
    'unique',
    'targetAttribute' => ['email', 'shopId'],
    'message' => 'Diese E-Mail Adresse ist bereits registriert!',
    'when' => function ($model) {
        return !empty($model->email);
    }
],

I have created a solution to enforce uniqueness across a set of fields, including nullable fields, which utilises the yii\\validators\\UniqueValidator::$filter callback in a generic PHP Trait that can be simply dropped into any Yii2 model. Full code examples below:

Generic Trait [common/models/traits/UniqueValidatorFilterTrait.php]

<?php

namespace common\models\traits;

use yii\db\ActiveQuery;

/**
 * Trait UniqueValidatorFilterTrait provides custom anonymous function callback methods.
 *
 * @package common\models\traits
 * @author Alec Pritchard <ajmedway@gmail.com>
 */
trait UniqueValidatorFilterTrait
{
    /**
     * Custom anonymous function for [[yii\validators\UniqueValidator::$filter]], used to modify the passed-in
     * [[\yii\db\ActiveRecord]] query to be applied to the DB query used to check the uniqueness of the input value.
     *
     * @param ActiveQuery $query
     * @param array $nullableUniqueFields the field names to ensure are considered and validated for the uniqueness
     * of a set of model attributes, even if some have empty/null values.
     * @see \yii\validators\UniqueValidator::$filter
     */
    public function filterFunctionUniqueWithNull(ActiveQuery $query, array $nullableUniqueFields = [])
    {
        // check if at least one of the $nullableUniqueFields currently has an empty value loaded
        $hasEmptyUniqueField = false;
        foreach ($nullableUniqueFields as $field) {
            if (empty($this->{$field})) {
                $hasEmptyUniqueField = true;
                break;
            }
        }
        // proceed to modify AR query
        if ($hasEmptyUniqueField) {
            // change query item for the $nullableUniqueFields, which only checks against empty string by design
            // @link https://github.com/yiisoft/yii2/issues/4333#issuecomment-57739619
            // ensure the composite unique constraint is applied to include NULL values for all $nullableUniqueFields
            foreach ($query->where as $whereItemKey => $whereItem) {
                if (!is_array($whereItem)) continue;
                foreach ($whereItem as $columnName => $value) {
                    // check if this column is one of the unique fields and if it currently has an empty string
                    if (str_replace($nullableUniqueFields, '', $columnName) != $columnName
                        && $value === '') {
                        // change from '' to NULL
                        $query->where[$whereItemKey][$columnName] = null;
                    }
                }
            }
        }
    }
}

Example Model Usage

<?php

namespace common\models;

use Yii;
use common\models\traits\UniqueValidatorFilterTrait;

/**
 * This is the model class for table "my_table".
 *
 * @property int $id
 * @property null|string $email
 * @property int $shop_id
 */
class MyTable extends \yii\db\ActiveRecord
{
    use UniqueValidatorFilterTrait;

    /**
     * {@inheritdoc}
     */
    public static function tableName()
    {
        return 'my_table';
    }

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return [
            [
                ['email', 'shop_id'],
                'unique',
                'targetAttribute' => ['email', 'shop_id'],
                // ensure the composite unique constraint is applied to include NULL values for specified nullable fields
                'filter' => function (MyTableQuery $query) {
                    /* @see \common\models\traits\UniqueValidatorFilterTrait::filterFunctionUniqueWithNull() */
                    $this->filterFunctionUniqueWithNull($query, ['email', 'shop_id']);
                },
                // ensure that we don't skip on empty values, namely the `email` and/or `shop_id`
                'skipOnEmpty' => false,
            ],
        ];
    }

See the full discussion about this shortcoming of the Yii2 core unique validator, and reasons given by core dev team here: https://github.com/yiisoft/yii2/issues/4333

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM