【问题标题】:Empty string instead of null values Eloquent空字符串而不是空值 Eloquent
【发布时间】:2013-07-01 10:25:15
【问题描述】:

我正在尝试使用批量分配 Eloquent 功能创建实体...

$new = new Contact(Input::all());
$new->save();

问题在于,这样一来,每个字段都填充了一个空字符串,而不是我预期的 null 值。

我目前正在开发系统,但仍有一些表列没有定义,这就是为什么使用这种方法,以避免将每个新字段添加到 $fillable 数组和 new Contact(array(...));...

而且我在这个表中有大约 20 个字段,所以有一个这样的数组有点难看

$new = new Contact(array(
    'salutation' => Input::get('salutation'),
    'first_name' => Input::get('first_name'),
    'last_name'  => Input::get('last_name'),
    'company_id' => Input::get('company_id'),
    'city' => ...
    ...
));

关于如何做到这一点或修复的任何提示?

更新到目前为止,我已经在App::before() 过滤器中解决了这个问题。

更新过滤器有点乱。我最终会这样做:

public static function allEmptyIdsToNull()
{
    $input = Input::all();

    $result = preg_grep_keys ( '/_id$/' , $input );

    $nulledResults = array_map(function($item) {
        if (empty($item))
            return null;

        return $item;
    }, $result);

    return array_merge($input, $nulledResults);
}

在我的functions.php中。

if ( ! function_exists('preg_grep_keys'))
{
    /**
    * This function gets does the same as preg_grep but applies the regex
    * to the array keys instead to the array values as this last does.
    * Returns an array containing only the keys that match the exp.
    * 
    * @author Daniel Klein
    * 
    * @param  string  $pattern
    * @param  array  $input
    * @param  integer $flags
    * @return array
    */
    function preg_grep_keys($pattern, array $input, $flags = 0) {
        return array_intersect_key($input, array_flip(preg_grep($pattern, array_keys($input), $flags)));
    }
}

现在只使用以“_id”结尾的字段。这是我最大的问题,好像关系不是NULL,数据库会因为找不到外键“”而抛出错误。

完美运行。有什么意见吗?

【问题讨论】:

  • 把这个放在一行中...Contact::create(Input::all())ftw.
  • 我不是用这种方式得到空值吗?我认为它的作用与$new = new Contact(Input::all()); 相同

标签: php laravel laravel-4


【解决方案1】:

我自己也在寻找这个问题的答案,我能想到的最接近的方法是使用 Mutators (http://laravel.com/docs/eloquent#accessors-and-mutators)。

通过为模型中的外键字段添加(神奇!)Mutator 方法解决了同样的问题:

public function setHeaderImageIdAttribute($value)
{
    $this->attributes['header_image_id'] = $value ?: null;
}

对于一个有很多外键的表,这可能会有点笨重,但这是我找到的处理这个问题的最“内置”的方法。好处是它很神奇,所以您只需创建方法就可以了。

更新 -- Laravel 5.4 及更高版本

从 Laravel 5.4 开始,\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class 中间件会在收到请求时处理此问题。在上面的示例中,如果请求包含“header_image_id”的空字符串值,则此中间件会在我将其分配给我的模型之前自动将其转换为null

【讨论】:

  • 这是正确的 Laravel 解决方案。这就是 Eloquent 中存在 Accessors 和 Mutators 的原因。它还可以更好地控制设置为 null 的确切内容,而不是像接受的答案那样笼统地“将所有内容设置为空”。
  • 这个答案+1。这绝对是分配空键的正确方法。使您可以更好地控制各个列。如果您必须更改列默认值或类似的东西,这也将是有益的。
  • 对我花了很长时间才弄清楚的事情的好答案。一个可能有帮助的注意事项:记住用 ID 命名 mutator —— 即它不是改变关系函数,而是改变 db 字段。根据上面的示例,我犯了在函数之后调用函数“setHeaderImageAttribute”而不是“setHeaderImageIdAttribute”的错误。然后 Laravel 错过了捕获 mutator。事后看来很明显..但花了很长时间才找到。
  • +1 我正要发布另一个答案,但后来我看到了这个。这比 IMO 接受的答案要好得多。
【解决方案2】:

Laravel 4

如果有必要,您可以通过过滤删除数组中的任何空字符串。

$input = array_filter(Input::all(), 'strlen');

如果你有类似array('a' => 'a', 'b' => '') 的东西,你会得到:array('a' => 'a')

据我所知,如果数组中没有指定一个字段进行批量赋值,那么 Laravel Eloquent ORM 会将其视为NULL


Laravel 5

$input = array_filter(Request::all(), 'strlen');

// If you inject the request.
$input = array_filter($request->all(), 'strlen');

【讨论】:

  • 好主意...我考虑过迭代输入并想要一个更简洁的解决方案。
  • array_filter 本身就足够了。无需提供strlen 作为回调函数使用。
  • 你是对的 @JasonLewis 但是......使用 array_filter 本身将过滤掉所有 falsy 值。如果您有一个0 作为字符串值,它将被过滤掉,也许这不是预期的结果。
  • 如果您不想更新任何列,这很好。如果你说一个日期并且你希望它用 NULL 更新它。你怎么能这样做?
  • 请注意,这不适用于更新操作,因为用户重置的字段将被忽略并保留以前的值。在这种情况下,可以使用类似的方法:$input = array_map(function($e) { return $e ?: null; }, Input::all());
【解决方案3】:

可能更通用的解决方案:

class EloquentBaseModel extends Eloquent {

    public static function boot()
    {
        parent::boot();

        static::saving(function($model)
        {
            if (count($model->forcedNullFields) > 0) {
                foreach ($model->toArray() as $fieldName => $fieldValue) {
                    if ( empty($fieldValue) && in_array($fieldName,$model->forcedNullFields)) {
                        $model->attributes[$fieldName] = null;
                    }
                }
            }

            return true;
        });

    }

}

你需要清理空表单字段的模型应该扩展这个类,然后你应该用字段名称填充 $forcedNullFields 数组,在空表单字段的情况下需要为 NULL:

class SomeModel extends EloquentBaseModel {
    protected $forcedNullFields = ['BirthDate','AnotherNullableField'];
}

仅此而已,您不应该在 mutators 中重复相同的代码。

【讨论】:

  • 在我看来,这是最好的答案。它提供了 mutators 所做的完美精细控制,而无需一遍又一遍地重复代码。而且它更像 Laravel,这很好。
  • 不过,如果您将零作为有效值,则不会
  • 在您的示例模型中,数组值应该是snake_case 对吗?
【解决方案4】:

使用模型“保存”事件查找空模型并将其显式设置为 null。在此示例中,“项目”是我的模型的名称。把它放在一个辅助函数中并将它连接到你的所有模型。

Project::saving(function($model) {
    foreach ($model->toArray() as $name => $value) {
        if (empty($value)) {
            $model->{$name} = null;
        }
    }

    return true;
});

LARAVEL 5 更新(2016 年 4 月 11 日)

我最后还创建了一个 Http 中间件来清理来自 http 请求的输入数据,除了在数据发送到数据库之前清理数据。

class InputCleanup
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $input = $request->input();

        array_walk_recursive($input, function(&$value) {

            if (is_string($value)) {
                $value = StringHelper::trimNull($value);
            }
        });

        $request->replace($input);

        return $next($request);
    }
}

【讨论】:

  • 使用这段代码一段时间后..小心它。它有一个有趣的副作用。它将在每次更新时将值设置为 null。这可能会产生意想不到的后果。检查该值是否已从其原始状态更改可能会很好。
  • 我在这里扩展了你的方法laravel-tricks.com/tricks/…
  • @craigtadlock 现在我们有了 laravel 5.1,我没有看到你提到的副作用。添加此评论以让其他人知道这不再是问题。
【解决方案5】:

对于表单输入,使用空值而不是空值是正常的,也更合乎逻辑。

如果您真的认为最好的方法是直接将输入放入数据库,那么将空值设为 null 的解决方案将是这样的。

$input = Input::all();
foreach ($input as &$value) {
    if (empty($value) { $value = null; }
}
$new = new Contact(Input::all());
$new->save();

我个人不赞成这种解决方案,但它确实适用于某些人。

【讨论】:

  • 只需将空字符串直接存储到您的数据库中。毕竟,我不认为你会渲染空值......
  • 我也不接受空值,因为我正在处理 ImnoDB、关系和约束,因此,如果 FK 不是 NULL,它会查找外部 ID,即使它是 @987654323 @,在外表中显然不存在,并抛出Cannot add or update a child row: a foreign key constraint fails等MySQL错误
  • @RubensMariuzzo 出于某些原因,您可能希望在数据库中使用 Null 值。例如;我在我的数据库中存储了一个带有序列化数据对象的字符串,如果尚未生成数据,则此值为 null,如果已生成数据但没有要存储的数据,则此值为空字符串。 Israel Ortuño:我实际上会写出所有字段,只是因为我认为最好将你获得的所有输入“列入白名单”,这样你就永远不会处理你不想处理的数据。
  • 我尝试过这样做...但是仍然得到一个空字段,因为Input::get() 返回字段值(空)而不是null
  • 尝试使用 Input::get('salutation', null) 我认为应该可以解决它。这样,您还可以设置其他默认值,而无需执行 if 和 else 语句。
【解决方案6】:

另一个简单的解决方案是创建基础模型类并从中扩展模型:

class Model extends \Illuminate\Database\Eloquent\Model
{
    /**
     * Set field to null if empty string
     *
     * @var array
     */
    protected $forcedNullFields = [];

    /**
     * Set a given attribute on the model.
     *
     * @param  string  $key
     * @param  mixed  $value
     * @return \Illuminate\Database\Eloquent\Model
     */
    public function setAttribute($key, $value)
    {
        if (in_array($key, $this->forcedNullFields) && $value === '') {
            $value = null;
        }

        return parent::setAttribute($key, $value);
    }
}

然后,如果需要,只需在 $forcedNullFields 中为每个模型填写必填字段。

【讨论】:

    【解决方案7】:

    在您的 Eloquent 模型中,确保 $guarded 为空。你不需要设置$fillable

    批量分配通常不是一个好主意,但在某些情况下可以这样做 - 您必须确定何时。

    见:http://laravel.com/docs/eloquent#mass-assignment

    【讨论】:

    • 我在进行批量分配时没有问题,只是遇到问题,因为每个空表单字段都将其值设置为 "" 而不是 NULL
    • POST 时这些字段是否存在?如果有,它们是空的吗?表列的迁移和/或默认值是什么样的?
    • 这些字段在我发布时存在,这就是为什么我得到空值而不是 NULL。下面提到主要问题
    • @RobW,OP 只想摆脱那些发布的空值;将它们设置为null
    • 如果你想要一个空行,为什么不直接打电话给Model::create(array())
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-02
    • 2015-09-16
    • 1970-01-01
    • 1970-01-01
    • 2012-06-07
    • 2013-03-07
    相关资源
    最近更新 更多