Пустая строка вместо нулевых значений Eloquent

Я пытаюсь создать объекты, используя функцию Mass-assign 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' => ...
    ...
));

Какие-нибудь советы о том, как это сделать или исправить?

Обновление. Теперь я решил это сделать array_filter в 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, база данных будет вызывать ошибку, поскольку внешний ключ "" не может быть найден.

Работает отлично. Любой комментарий?

Ответ 1

Используйте событие 'save' для поиска пустых моделей и явно установите для них значение null. "Проект" - это название моей модели в этом примере. Поместите это в вспомогательную функцию и подключите ее ко всем вашим моделям.

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

    return true;
});

ОБНОВЛЕНИЕ ЗА ЛАВРОВ 5 (11 апреля 2016 года)

Я также закончил создание промежуточного ПО 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);
    }
}

Ответ 2

Я сам искал ответ на этот вопрос, и самое близкое, что я могу придумать, - это использовать 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 прежде чем я смогу назначить его для моей модели.

Ответ 3

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

Ответ 4

Возможно, более общее решение:

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'];
}

И все, вы не должны повторять один и тот же код в мутаторе.

Ответ 5

Для ввода формы это нормально и логичнее иметь пустые значения, а не нулевые значения.

Если вы действительно считаете, что лучший способ сделать это, это прямое внесение ввода в вашу базу данных, чем решение сделать пустые значения null будет примерно таким.

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

Лично я не согласен с такими решениями, но это работает для некоторых людей.

Ответ 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);
    }
}

Затем просто заполните обязательные поля в $ forceNullFields для каждой модели, если это необходимо.

Ответ 7

В модели Eloquent убедитесь, что $guarded пуст. Вам не нужно устанавливать $fillable.

Массовое присвоение обычно не является хорошей идеей, но в некоторых сценариях это нормально, вы должны определить, когда.

Смотрите: http://laravel.com/docs/eloquent#mass-assignment