【问题标题】:Validate uniqueness of composite model fields against form input in Laravel?根据 Laravel 中的表单输入验证复合模型字段的唯一性?
【发布时间】:2014-12-28 13:30:53
【问题描述】:

我正在使用 Laravel 4 编写用户注册表单,让用户在 first_namelast_name 字段中输入他们的姓名。在我的表单输入验证期间,我想检查这两个字段与组合名称 first_name + " " + last_name 的唯一性,该名称来自表中要保存的值。

我知道您可以使用unique 规则检查单个字段的唯一性,甚至可以通过指定unique:tableName,fieldName 覆盖该字段。

理想情况下,我会执行unique:tableName,first_name + " " + last_name 之类的操作,或者在模型本身中指定某些内容,但我无法在复合/虚拟字段上找到任何内容。

【问题讨论】:

    标签: php validation laravel laravel-4 model


    【解决方案1】:

    编写你自己的规则。它可能看起来像这样:

    'unique_composite:table,field1,field2,field3,...,fieldN'
    

    表名之后枚举的字段将被连接起来并根据复合值进行检查。验证规则如下所示:

    Validator::extend('unique_composite', function ($attribute, $value, $parameters)
    {
        // Get table name from first parameter
        $table  = array_shift($parameters);
        $fields = implode(',', $parameters);
    
        // Build the query that searches the database for matches
        $matches = DB::table($table)
                    ->where(DB::raw('CONCAT_WS(" ", ' . $fields . ')'), $value)
                    ->count();
    
        // Validation result will be false if any rows match the combination
        return ($matches == 0);
    });
    

    在你的验证器中你可以有这样的东西:

    $validator = Validator::make(
        array('full_name' => $firstName + ' ' + $lastName),
        array('full_name' => 'unique_composite:users,first_name,last_name')
    );
    

    使用此规则,您可以使用任意数量的字段,而不仅仅是两个。

    【讨论】:

    • 我会试试这个。不过,我应该把这个验证器扩展的代码放在哪里?
    • 只要包含包含规则的文件,实际上并没有任何约定。例如,您可以将其放在 app/validators.php 中,并将其包含在 app/start/global.phprequire app_path().'/validators.php'; 中。
    • 您提供的查询的每个变体都会给我以下 SQL 错误 (postgresql):Undefined column: 7 ERROR: column "composite_value" does not exist
    • 给你+1,因为你把我引向了正确的方向,但看看我的回答,我实际上是如何解决这个问题的。
    • 我发布的代码没有经过测试,这就是它抛出错误的原因。我已使用经过测试的有效版本更新了我的答案,以供将来参考。
    【解决方案2】:

    我最终扩展了 Validator 类来定义自定义验证器规则。我在我的应用程序的first_name 字段中检查它,主要是因为我不想在生成全名时做额外的工作。除了没有将其设为复合值(考虑到问题后没有必要这样做),我只是将其设置为检查指定字段的所有值的AND。您可以指定任意数量的字段,如果其中一个不存在于验证器数据中,它将引发异常。我什至不确定这是否可以仅使用现有的 unique 规则来完成,但无论如何这是一个很好的练习。

    'first_name' => 'unique_multiple_fields:members,first_name,last_name'
    

    我的验证器子类代码:

    use Illuminate\Validation\Validator as IlluminateValidator;
    
    class CustomValidatorRules extends IlluminateValidator
    {
        /**
         * Validate that there are no records in the specified table which match all of the 
         * data values in the specified fields. Returns true iff the number of matching 
         * records is zero.
         */
        protected function validateUniqueMultipleFields( $attribute, $value, $parameters )
        {
            if (is_null($parameters) || empty($parameters)) {
                throw new \InvalidArgumentException('Expected $parameters to be a non-empty array.');
            }
            if (count($parameters) < 3) {
                throw new \InvalidArgumentException('The $parameters option should have at least 3 items: table, field1, field2, [...], fieldN.');
            }
    
            // Get table name from first parameter, now left solely with field names.
            $table = array_shift($parameters);
    
            // Uppercase the table name, remove the 's' at the end if it exists
            // to get the class name of the model (by Laravel convention).
            $modelName = preg_replace("/^(.*)([s])$/", "$1", ucfirst($table));
    
            // Create the SQL, start by getting only the fields specified in parameters
            $select = $modelName::select($parameters);
    
            // Generate the WHERE clauses of the SQL query.
            foreach ($parameters as $fieldName) {
                $curFieldVal = ($fieldName === $attribute) ? $value : $this->data[$fieldName];
                if (is_null($curFieldVal)) {
                    // There is no data for the field specified, so fail.
                    throw new \Exception("Expected `{$fieldName}` data to be set in the validator.");
                }
    
                // Add the current field name and value
                $select->where($fieldName, '=', $curFieldVal);
            }
    
            // Get the number of fields found
            $numFound = $select->count();
    
            return ($numFound === 0);
        }
    }
    

    如果您好奇,我确实使用我最初正在查看的复合方法使其工作。代码如下。原来“分隔符”完全没有意义,因此我最终将它重构为使用上面指定的多字段方法。

    use Illuminate\Validation\Validator as IlluminateValidator;
    
    class CustomValidatorRules extends IlluminateValidator
    {
        /**
         * Validate that the final value of a set of fields - joined by an optional separator -
         * doesn't match any records in the specified table. Returns true iff the number of
         * matching records is zero.
         */
        protected function validateUniqueComposite( $attribute, $value, $parameters )
        {
            if (is_null($parameters) || empty($parameters)) {
                throw new \InvalidArgumentException('Expected $parameters to be a non-empty array.');
            }
            if (count($parameters) < 3) {
                throw new \InvalidArgumentException('The $parameters option should have at least 3 items: table, field1, field2, [...], fieldN.');//, [separator].');
            }
    
            // Get table name from first parameter
            $table = array_shift($parameters);
    
            // Determine the separator
            $separator = '';
            $lastParam = array_pop($parameters);
            if (! isset($this->data[$lastParam])) {
                $separator = $lastParam;
            }
    
            // Get the names of the rest of the fields.
            $fields = array();
            foreach ($parameters as $fieldName) {
                array_push($fields, $table . "." . $fieldName);
            }
            $fields = implode(', ', $fields);
    
            $dataFieldValues = array();
            foreach ($parameters as $fieldName) {
                $curFieldVal = ($fieldName === $attribute) ? $value : $this->data[$fieldName];
                if (is_null($curFieldVal)) {
                    throw new \Exception("Expected `{$fieldName}` data.");
                }
                array_push($dataFieldValues, $curFieldVal);
            }
            $compositeValue = implode($separator, $dataFieldValues);
    
            // Uppercase the table name, remove the 's' at the end if it exists
            // to get the class name of the model (by Laravel convention).
            $modelName = preg_replace("/^(.*)([s])$/", "$1", ucfirst($table));
            $raw = \DB::raw("concat_ws('" . $separator . "', " . $fields . ")");
            $model = new $modelName;
    
            // Generate the SQL query
            $select = $modelName::where($raw, '=', $compositeValue);
            $numFound = $select->count();
    
            return ($numFound === 0);
        }
    }
    

    【讨论】:

    • 当您设置$modelName 时,您将删除字符串末尾的s。这并不总是获得单数形式的正确方法(想想'box' => 'boxes')。你可以使用 Laravel 的 str_plural() (check the docs) 来改进它。对于那些使用非英语模型/表名的人,您应该注意这种复数形式(或者只是将表名传递给验证)。
    • 感谢您的提示。我会将其添加到此项目的修补程序队列中。
    【解决方案3】:

    在不创建自定义验证器的情况下,您还可以指定更多条件作为“where”子句添加到查询中并执行以下操作:

    'first_name' => 'required|unique:table_name,first_name,null,id,last_name,'.$data['last_name'],
    'last_name' => 'required|unique:table_name,last_name,null,id,first_name,'.$data['first_name'],
    

    这样,first_name 的唯一性将仅在last_name 等于输入的last_name(在我们的示例中为$data['last_name'])的行上强制执行。

    last_name 的唯一性反过来也是如此。

    如果您想强制唯一规则忽略给定 ID,只需将 null 替换为该特定 ID。

    参考:http://laravel.com/docs/4.2/validation#rule-unique

    【讨论】:

      猜你喜欢
      • 2018-10-15
      • 2015-09-06
      • 2020-07-07
      • 1970-01-01
      • 1970-01-01
      • 2013-09-20
      • 2011-05-01
      • 1970-01-01
      • 2019-03-24
      相关资源
      最近更新 更多