Support the ongoing development of Laravel.io →
Database Eloquent
Last updated 2 years ago.
0

Since I got no replies and couldn't quite find anything I have created a Trait called EloquentJSON (below) which allows you to define attributes on the entity which are json documents. By doing so when the model is read in it will parse the JSON document and set up getters and setters for each top level attribute making it easy to interact with the various attributes within the document. For example we could create a Photos model like this:

class Photo extends Eloquent
{
    use AMD\Traits\EloquentJSON;

    protected $jsonColumns = ['json_data'];
}

And then this:

$attr = json_decode($photo->json_data);
$attr->key = $value;
$photo->json_data = json_encode($attr);

becomes this:

$photo->key = value;

Also when calling the toArray() method the attributes are moved to the top level and the 'json_attributes' column is hidden. This essentially hides away the fact that you're using the json datatype and makes it look like we're working with attributes directly.

You can also establish relationships on a model like this:

public function user()
{
    return $this->hasOne( 'User', 'id', "json_data->>'user_id'" );
}

The one caveat is when you're setting an attribute not previously set already in the JSON document then no setter is created and so you would need to call:

$referral->setJsonAttribute();

This thing is far from complete and freshly minted code so something may/will break - hopefully this saves the next guy/gal some work, and if there's interest in this as a package let me know and I'll see about setting a more formal repo (packagist, etc...)

<?php

trait EloquentJSON
{
    /**
     * List of known PSQL JSON operators
     *
     * @var array
     */
    public static $jsonOperators = [
        '->',
        '->>',
        '#>',
        '#>>' ];

    /**
     * @var array
     */
    private $jsonAttributes = [];

    /**
     * Boot the EloquentJSON trait for a model.
     *
     * @return void
     */
    public static function bootEloquentJSON()
    {
        self::loading( function($obj)
        {
            $obj->inspectJsonColumns();
        } );
    }

    /**
     * Decodes each of the declared JSON attributes and records the attributes
     * on each
     *
     * @return void
     */
    public function inspectJsonColumns()
    {
        foreach ( $this->jsonColumns as $col )
        {
            $this->hidden[] = $col;
            $obj = json_decode( $this->$col );

            if( is_object($obj) )
            {
                foreach ( $obj as $key => $value )
                {
                    $this->flagJsonAttribute( $key, $col );
                    $this->appends[] = $key;
                }
            }
        }
    }

    /**
     * Record that a given JSON element is found on a particular column
     *
     * @param string $key
     * @param string $col
     *
     * @return void
     */
    public function flagJsonAttribute( $key, $col )
    {
        $this->jsonAttributes[$key] = $col;
    }

    /**
     * Include JSON column in the list of attributes that have a get mutator.
     *
     * @param  string  $key
     * @return bool
     */
    public function hasGetMutator( $key )
    {
        $jsonPattern  = '/' . implode( '|', self::$jsonOperators ) . '/' ;

        if ( array_key_exists($key, $this->jsonAttributes) !== false )
        {
            return true;
        }

        // In some cases the key specified may not be a simple key but rather a
        // JSON expression (e.g. "jsonField->'some_key'). A common case would
        // be when specifying a relation key. As such we test for JSON
        // operators and expect a mutator if this is a JSON expression
        elseif ( preg_match($jsonPattern, $key) != false )
        {
            return true;
        }

        return parent::hasGetMutator( $key );
    }

    /**
     * Include the JSON attributes in the list of mutated attributes for a
     * given instance.
     *
     * @return array
     */
    public function getMutatedAttributes()
    {
        $attributes = parent::getMutatedAttributes();
        $jsonAttributes = array_keys( $this->jsonAttributes );
        return array_merge( $attributes, $jsonAttributes );
    }

    /**
     * Check if the key is a known json attribute and return that value
     *
     * @param  string  $key
     * @param  mixed   $value
     * @return mixed
     */
    protected function mutateAttribute( $key, $value )
    {
        $jsonPattern  = '/' . implode( '|', self::$jsonOperators ) . '/' ;

        // Test for JSON operators and reduce to end element
        /* TODO: This only really works for 1-level deep. Should it be more? */
        $isJson = false;

        if ( preg_match($jsonPattern, $key) )
        {
            $elems = preg_split( $jsonPattern, $key );
            $key = end( $elems );
            $key = str_replace( [">","'"] , "", $key );

            $isJson = true;
        }

        if ( array_key_exists($key, $this->jsonAttributes) != false )
        {
            $obj = json_decode( $this->{$this->jsonAttributes[$key]} );
            return $obj->$key;
        }
        elseif ( $isJson )
        {
            return null;
        }


        return parent::mutateAttribute( $key, $value );
    }

    /**
     * Set a given attribute on the known JSON elements.
     *
     * @param  string  $key
     * @param  mixed   $value
     * @return void
     */
    public function setAttribute( $key, $value )
    {
        if ( array_key_exists($key, $this->jsonAttributes) !== false )
        {
            $this->setJsonAttribute( $this->jsonAttributes[$key], $key, $value );
            return;
        }

        parent::setAttribute( $key, $value );
    }

    /**
     * Set a given attribute on the known JSON elements.
     *
     * @param string $attribute
     * @param string $key
     * @param mixed $value
     * @return void
     */
    public function setJsonAttribute( $attribute, $key, $value )
    {
        $obj = json_decode( $this->{$attribute} );
        $obj->$key = $value;
        $this->{$attribute} = json_encode($obj);
        return;
    }
}
Last updated 9 years ago.
0

I am hugely interested as I found this looking for PostgreSQL jsonb support.

Its a good start for the model. I would also like migration support and query support for querying by json value.

I also need postgis geography data types and have adopted the lamb/postgis adaptor but it is pretty much abandoned and lacks some features. I would be open to a collaboration to add better PostgreSQL support to eloquent as I feel like what we have is very vanilla and I chose PostgreSQL specifically for its features that can't be found elsewhere like GIS capabilities and json fields.

0

I am also very interested in this for an upcoming project, specifically any advance on querying by the json. Please let me know if you have plans to release a formal repo/package.

0

I'm interesting too! +1

0

Wow - responses... guess I should check this more often... OK, I will set it up in github and packagist and whatnot in the next day or two... I'll post back here with info once it's set up.

0

So since there was some interest I have created a new package on packagist:

https://packagist.org/packages/darrylkuhn/dialect

Please feel free to contribute/comment/etc...

0

I keep coming across this thread when googling for JSONB, Postgres, and Eloquent.

Darryl, thanks for your library, I've found it helpful!

For anyone following: an easy way to get JSON and JSONB fields to automatically cast to an array or an object element in Eloquent is to use this feature: http://laravel.com/docs/master/eloquent-mutators#attribute-casting

class Photo extends Model
{
    protected $casts = [
         'json_data' => 'array',
         'other_json_data' => 'object',
    ];
    ....
}
...
$photo = Photo::find(100);
$photo->json_data['some_field'] = 'some_value';
$photo->other_json_data->some_other_field = 'some_other_value';
$photo->save();
Last updated 9 years ago.
0

Sign in to participate in this thread!

Eventy

Your banner here too?

Moderators

We'd like to thank these amazing companies for supporting us

Your logo here?

Laravel.io

The Laravel portal for problem solving, knowledge sharing and community building.

© 2024 Laravel.io - All rights reserved.