31

I'm trying to create entities using mass-assignment Eloquent feature...

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

The problem's that this way, every field's filled out with an empty string instead of null values as I expected.

I'm currently developing the system and still some table columns're not defined, that's why using this method, to avoid adding every new field to $fillable array and to a new Contact(array(...));...

Also I've around 20 fields in this table, so It'd be a bit ugly to have an array such as

$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' => ...
    ...
));

Any tips of how to do this or fix?

Update By now I've sorted out this doing the array_filter in the App::before() filter.

Update In filter was a bit mess. I end up doing:

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

And in my 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)));
    }
}

By now only working with fields that ends with "_id". This is my biggest problem as if a relationship is not NULL, the database will throw an error as the foreign key "" cannot be found.

Works perfect. Any comment?

2
  • Put this in one line... Contact::create(Input::all()) ftw. Commented Jul 3, 2013 at 16:21
  • Aren't I getting the empty values using this way? I think it does the same stuff than doing $new = new Contact(Input::all()); Commented Jul 3, 2013 at 16:30

7 Answers 7

64

I've looked for the answer to this myself, and the closest I can come up with is using Mutators (http://laravel.com/docs/eloquent#accessors-and-mutators).

The same problem was solved by adding a (magic!) Mutator method for the foreign key field in the model:

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

For a table with a lot of foreign keys, this can get kind of bulky, but it's the most "built-in" method I've found for handling this. The upside is that it's magic, so all you have to do is create the method and you're good to go.

UPDATE -- Laravel 5.4 and above

As of Laravel 5.4, the \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class middleware handles this when the request is received. In my example above, if the request contains an empty string value for 'header_image_id', this middleware automatically converts it to null before I can assign it to my model.

Sign up to request clarification or add additional context in comments.

6 Comments

This is the proper Laravel solution. This is the reason Accessors and Mutators exist in Eloquent. It also gives more control over exactly what is set to null rather than a blanket "set everything to null" as the accepted answer does.
+1 for this answer. This is definitely the proper way to assign null keys. Gives you much more control over individual columns. This would also be beneficial if you had to change a columns default value or something similar down the line.
Great answer on something that took me forever to figure out. One note that may help: Remember to name the mutator with the ID -- ie it is not mutating the relationship function, but rather the db field. I was making the mistake of calling the function 'setHeaderImageAttribute' after the function instead of 'setHeaderImageIdAttribute' per the example above. Laravel then missed catching the mutator. Obvious in hindsight.. but took forever to find.
+1 I was about to post an alternative answer, but then I saw this one. This is much better than the accepted answer IMO.
|
19

Laravel 4

If it is necessary you could remove any empty string in an array by filtering.

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

Then if you have something like array('a' => 'a', 'b' => '') you will get: array('a' => 'a').

As far as I know, if a field is not specified in the array for mass-assignment, then Laravel Eloquent ORM will treat it like NULL.


Laravel 5

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

or

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

8 Comments

Good idea... I've thought iterating the input and wanted a cleaner solution.
array_filter by itself should be good enough. No need to supply strlen as the callback function to use.
You are right @JasonLewis but... using array_filter by itself will filter out all falsy values. If you have a 0 as a string value it will be filtered out, maybe this is not the expected result.
This is great if you don't want to update any columns. What if you have say a date and you want it to update it with NULL. How can you do this?
Note that this won't work for update operations, as fields reset by the user will be ignored and the previous values will be kept. In this case a similar approach can be used: $input = array_map(function($e) { return $e ?: null; }, Input::all());
|
12

Probably more generic solution:

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

    }

}

The model where you needs to sanitize empty form fields should extends this class, then you should fill $forcedNullFields array with field names that required to be NULL in case of empty form fields:

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

And thats all, you should not repeat same code in mutators.

3 Comments

This is the best answer, in my opinion. It gives the perfectly granular control that mutators do without repeating code over and over, tediously. And it's even more Laravel-like, which is nice.
not in the case you've got zero as a valid value, though
In your example Model, the array values should be in snake_case right?
10

Use the model 'saving' event to look for empty models and explicitly set them to null. 'Project' is the name of my model in this example. Put this in a helper function and wire it up to all your models.

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

    return true;
});

UPDATE FOR LARAVEL 5 (April 11 2016)

I also ended up creating an Http Middleware to cleanup the input data from the http request, in addition to cleaning up the data before it is sent to the database.

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

3 Comments

After using this code for a while.. be careful with it. It has an interesting side effect. It will set the values to null on every update. This might have unintended consequences. It would probably be good to check if the value has changed from its original state.
I expanded on your method here laravel-tricks.com/tricks/…
@craigtadlock now that we have laravel 5.1, I didn't see the side effect you mentioned. adding this comment to let others know this isn't a issue anymore.
1

For a form input, it's normal, and more logical to have empty values, rather then null values.

If you really think the best way to do this, is to directly put the input into your database, than the solution to make empty values null would be something like this.

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

Personally I don't approve with these kind of solutions, but it does work for some people.

6 Comments

Just store empty string directly to your database. After all, I don't think you will render null values...
I also do not accept empty values as I'm working on ImnoDB, relationships and constraints so, if a FK is not NULL, it tris to find the foreign ID, even if it's "", which obviously does not exist in the foreign table and throws a MySQL error such as Cannot add or update a child row: a foreign key constraint fails
@RubensMariuzzo There are some reasons why you might want to use Null values in your database. For example; I store a string with a serialized data object in my DB, this value is null if the data is yet to be generated, and it's an empty string if the data has already been generated, but there was no data to be stored. Israel Ortuño: I would actually write out all the fields, simply because I think it's best to "white-list" all input you get, so you would never process data you don't want to.
I've tried to do it that way... But still getting an empty field as Input::get() returns the field value (empty) instead of null.
try using Input::get('salutation', null) I think that should fix it. That way you can also set other default values without having to do if and else statements.
|
1

Another simple solution is to create base model class and extend models from it:

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

Then just fill required fields in $forcedNullFields for each model if needed.

Comments

-2

In your Eloquent model, make sure $guarded is empty. You don't need to set $fillable.

Mass assignment isn't usually a good idea, but it's OK to do in some scenarios - you must determine when.

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

7 Comments

I do not have problems doing mass-assignment, just having problems as every empty form field sets its value as "" instead of NULL
Do those fields exist when you POST? If so, are they empty? What's your migration and/or default values look like for the table columns?
The fields exist when I POST, that's why I'm getting empty values instead of NULL. The main problem is mentioned below
@RobW, the OP just want to get rid of those POSTed empty values; setting them to null.
If it's an empty row you want, why not just call Model::create(array())?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.