Multiple mutators for one attribute in Laravel

By
Chris Haan

On a recent project, I found the need to set the value of an attribute in an Eloquent model using input formatted in two different ways. One way was basically 'raw' input—copying a date field from a table in one database into a corresponding date field in my new table. As you can imagine, there's not much work to do in a case like this. A simple $myModel->date = $original_date where $myModel is an instance of my Eloquent model will do the trick. However, I also needed to store the value from a datepicker widget in a particular format to the same field at times, so it seemed convenient to create a mutator in my model that would automatically translate the datepicker format (which incidentally was "<full month name> <day of month>, <four digit year>, or as we like to say in PHP: 'F j, Y') into the standard MySQL format. So, I added the mutator to my model, tested the user interface, and all was good...

...until the next time I ran my import. Now, the date attribute expected input in the above format so the "raw" value just didn't look right to the model. So, it very kindly informed me that it had no idea what the heck I was talking about when I handed it a value in the standard MySQL format of 'Y-m-d'. It didn't take long for me to realize what I had done. So now I had a bit of a problem: I needed to set this date field using input formatted in two very different ways.

Several possible solutions occurred to me. Maybe I could create a duplicate Eloquent model that was used only for import but without the mutator for the date field, or maybe I could toss a conditional or two into the mutator to try to set the value appropriately based on the input, or heck, maybe I could just use a raw query for the import and avoid the issue altogether. Now, if you google "multiple mutators for one attribute" you will find a lot of fantastic resources that discuss mutators and accessors and you will probably learn a thing or two—I know I did. However, you will probably not find anything that addresses this specific problem.

At first glance, it would seem to be impossible to have more than one mutator for a single attribute. After all, the method name is constructed using the name of the database column in camel case prefixed with "set" and suffixed with "Attribute" (e.g. setDateAttribute), and PHP won't like it very much if you try to have two methods with the same name. In the past, I have used an accessor for an attribute to retrieve a value in a different format such as retrieving a human-friendly boolean as a 'Y' or 'N'. So I thought maybe, just maybe, I could do the same for a mutator. Of course, it's still not possible to have two methods with the same name, so I had to change the name of the function used for importing the value. Not being the creative type, I decided to go with setImportDateAttribute. The trick is that inside the mutator, you still use the original attribute key and since this was just copying a value, the method looked like this:


public function setImportDateAttribute($value)
{
	$this->attributes['date'] = $value;
}

So, basically I was telling it to take the original value and set the 'date' attribute of the model to that value. To utilize this new mutator, I simply had to change the line in my import from "$myModel->date = $original_date" to "$myModel->import_date = $original_date" and the rest is Laravel magic.