Поддержка postgres массива в laravel5

Сейчас доминирующая база данных - 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, чтобы им могли пользоваться другие.

Posted in Laravel, PHP on Sep 04, 2015

comments powered by Disqus