在Laravel中,如何对复合模型字段的唯一性与表单输入进行验证?

3
我正在使用Laravel 4编写用户注册表单,让用户在first_namelast_name字段中输入他们的姓名。在表单输入验证期间,我想根据将要保存的表中的值,检查两个字段是否唯一,组合名称为first_name + " " + last_name
我知道您可以使用unique规则检查单个字段的唯一性,并且甚至可以通过指定unique:tableName,fieldName来覆盖该字段。
理想情况下,我会像这样做:unique:tableName,first_name + " " + last_name,或者在模型本身中指定一些内容,但是我还没有找到有关复合/虚拟字段的任何信息。
3个回答

2

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

'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')
);

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


我会试一下。不过这个验证器扩展的代码应该放在哪里呢? - Derek
实际上,只要包含规则的文件被包含在内,就没有任何关于此的约定。例如,您可以将其放置在 app/validators.php 中,并使用 require app_path().'/validators.php';app/start/global.php 中包含它。 - Bogdan
您提供的每个查询变量都会给我返回以下SQL错误(postgresql):未定义列:7 ERROR:列“composite_value”不存在 - Derek
因为你指导了我正确的方向,所以我给你点赞,但请看我的答案,了解我是如何实际解决这个问题的。 - Derek
1
我发布的代码没有经过测试,所以会出现错误。为了以后的参考,我已经更新了我的答案,并提供了一个经过测试的可行版本。 - Bogdan

1
我最终扩展了 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);
    }
}

1
当您设置$modelName时,您会删除字符串末尾的s。这并不总是获取单数形式的正确方法(考虑'box' => 'boxes')。您可以通过使用 Laravel 的 str_plural() 来改进它(查看文档)。对于使用非英语模型/表名称的人,应该注意这种复数形式(或简单地将表名传递给验证)。 - Luís Cruz
谢谢你的建议。我会将它加入到这个项目的热修复队列中。 - Derek

0

不需要创建自定义验证器,您也可以指定更多的条件作为“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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接