Сейчас доминирующая база данных - mysql, в тоже время другие базы данных имеют свои интересные особенности. Одна из таких особенность в postgres - это массив из значений в поле таблицы. Например можно в поле таблицы иметь массив из номеров телефонов или массив email-ов.
Однако поддержка таких полей не встроена в laravel5 из коробки: поэтому работать с такими полями на уровне модели было мягко сказать неудобно. Так как я в своей работе использую массивы, то я решил дописать поддержку массивов и заодно разобраться как создавать обработчики специальных полей (да-да в postgres можно создавать собственные типы полей).
Немного поискав в интернете, я нашел прекрасный проект, который делает обработку json, и взял его за основу. Для расширения модели самым удобным является использование trait-ов. Однако мне хотелось, чтобы работа с массивом была сделана единообразно с json, поэтому я стал смотреть в сторону переопределения кастинга, для типа массив.
При работе с любым атрибутом базой являются действия: задать атрибут и прочесть значения атрибута - нужно переопределить каждое из них.
Начнем с получения значения:
* Get a plain attribute (not a relationship).
*
* @param string $key
* @return mixed
*/
public function getAttributeValue($key)
{
$value = $this->getAttributeFromArray($key);
// If the attribute has a get mutator, we will call that then return what
// it returns as the value, which is useful for transforming values on
// retrieval from the model to a form that is more useful for usage.
if ($this->hasGetMutator($key)) {
return $this->mutateAttribute($key, $value);
}
// If the attribute exists within the cast array, we will convert it to
// an appropriate native PHP type dependant upon the associated value
// given with the key in the pair. Dayle made this comment line up.
if ($this->hasCast($key)) {
$value = $this->castAttribute($key, $value);
}
// If the attribute is listed as a date, we will convert it to a DateTime
// instance on retrieval, which makes it quite convenient to work with
// date fields without having to create a mutator for each property.
elseif (in_array($key, $this->getDates())) {
if (! is_null($value)) {
return $this->asDateTime($value);
}
}
return $value;
То есть если для атрибута необходимо кастование, то вызывается метод castAttribute
, который и преобразует значение указанное в базе данных. Перепишем этот метод добавив поддержку postgres массивов:
/**
* Cast an attribute to a native PHP type.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
protected function castAttribute($key, $value)
{
if (is_null($value)) {
return $value;
}
switch ($this->getCastType($key)) {
case 'int':
case 'integer':
return (int) $value;
case 'real':
case 'float':
case 'double':
return (float) $value;
case 'string':
return (string) $value;
case 'bool':
case 'boolean':
return (bool) $value;
case 'object':
return json_decode($value);
case 'array': {
$return = [];
return self::arrayParse($value, $return);
}
case 'json':
return json_decode($value, true);
case 'collection':
return new BaseCollection(json_decode($value, true));
default:
return $value;
}
}
Для типа массив напишем специальный обработчик. Его код можно посмотреть на странице расширения. Это все, что необходимо для корректного получения массива.
Теперь сделаем задание аттрибута. В метода setAttribute
допишем специальный обработчик для масива:
/**
* Set a given attribute on the model.
*
* @param string $key
* @param mixed $value
* @return void
*/
public function setAttribute($key, $value)
{
// First we will check for the presence of a mutator for the set operation
// which simply lets the developers tweak the attribute as it is set on
// the model, such as "json_encoding" an listing of data for storage.
if ($this->hasSetMutator($key)) {
$method = 'set'.Str::studly($key).'Attribute';
return $this->{$method}($value);
}
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
elseif (in_array($key, $this->getDates()) && $value) {
$value = $this->fromDateTime($value);
}
if ($this->isArrayastable($key) && ! is_null($value)) {
$value = self::arrayStringify($value);
}
if ($this->isJsonCastable($key) && ! is_null($value)) {
$value = json_encode($value);
}
$this->attributes[$key] = $value;
}
В коде проверяется, что если атрибут массив, то его необходимо преобразовать из php массива в postgres массив.
На этом программирование окончено: остается только выложить получившийся плагин на packagist, чтобы им могли пользоваться другие.