Giter Club home page Giter Club logo

eloquent-json-relations's Introduction

Eloquent JSON Relations

CI Code Coverage Scrutinizer Code Quality Latest Stable Version Total Downloads License

This Laravel Eloquent extension adds support for JSON foreign keys to BelongsTo, HasOne, HasMany, HasOneThrough , HasManyThrough, MorphTo, MorphOne and MorphMany relationships.

It also provides many-to-many and has-many-through relationships with JSON arrays.

Compatibility

  • MySQL 5.7+
  • MariaDB 10.2+
  • PostgreSQL 9.3+
  • SQLite 3.38+
  • SQL Server 2016+

Installation

composer require "staudenmeir/eloquent-json-relations:^1.1"

Use this command if you are in PowerShell on Windows (e.g. in VS Code):

composer require "staudenmeir/eloquent-json-relations:^^^^1.1"

Versions

Laravel Package
11.x 1.11
10.x 1.8
9.x 1.7
8.x 1.6
7.x 1.5
6.x 1.4
5.8 1.3
5.5–5.7 1.2

Usage

One-To-Many Relationships

In this example, User has a BelongsTo relationship with Locale. There is no dedicated column, but the foreign key (locale_id) is stored as a property in a JSON field (users.options):

class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
        'options' => 'json',
    ];

    public function locale()
    {
        return $this->belongsTo(Locale::class, 'options->locale_id');
    }
}

class Locale extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users()
    {
        return $this->hasMany(User::class, 'options->locale_id');
    }
}

Remember to use the HasJsonRelationships trait in both the parent and the related model.

Referential Integrity

On MySQL , MariaDB and SQL Server you can still ensure referential integrity with foreign keys on generated/computed columns.

Laravel migrations support this feature on MySQL/MariaDB:

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->json('options');
    $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id');
    $table->unsignedBigInteger('locale_id')->storedAs($locale_id);
    $table->foreign('locale_id')->references('id')->on('locales');
});

Laravel migrations (5.7.25+) also support this feature on SQL Server:

Schema::create('users', function (Blueprint $table) {
    $table->bigIncrements('id');
    $table->json('options');
    $locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id');
    $locale_id = 'CAST('.$locale_id.' AS INT)';
    $table->computed('locale_id', $locale_id)->persisted();
    $table->foreign('locale_id')->references('id')->on('locales');
});

There is a workaround for older versions of Laravel.

Many-To-Many Relationships

The package also introduces two new relationship types: BelongsToJson and HasManyJson

Use them to implement many-to-many relationships with JSON arrays.

In this example, User has a BelongsToMany relationship with Role. There is no pivot table, but the foreign keys are stored as an array in a JSON field (users.options):

Array of IDs

By default, the relationship stores pivot records as an array of IDs:

class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
       'options' => 'json',
    ];
    
    public function roles()
    {
        return $this->belongsToJson(Role::class, 'options->role_ids');
    }
}

class Role extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users()
    {
       return $this->hasManyJson(User::class, 'options->role_ids');
    }
}

On the side of the BelongsToJson relationship, you can use attach(), detach(), sync() and toggle():

$user = new User;
$user->roles()->attach([1, 2])->save(); // Now: [1, 2]

$user->roles()->detach([2])->save();    // Now: [1]

$user->roles()->sync([1, 3])->save();   // Now: [1, 3]

$user->roles()->toggle([2, 3])->save(); // Now: [1, 2]

Array of Objects

You can also store pivot records as objects with additional attributes:

class User extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [
       'options' => 'json',
    ];
    
    public function roles()
    {
        return $this->belongsToJson(Role::class, 'options->roles[]->role_id');
    }
}

class Role extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function users()
    {
       return $this->hasManyJson(User::class, 'options->roles[]->role_id');
    }
}

Here, options->roles is the path to the JSON array. role_id is the name of the foreign key property inside the record object:

$user = new User;
$user->roles()->attach([1 => ['active' => true], 2 => ['active' => false]])->save();
// Now: [{"role_id":1,"active":true},{"role_id":2,"active":false}]

$user->roles()->detach([2])->save();
// Now: [{"role_id":1,"active":true}]

$user->roles()->sync([1 => ['active' => false], 3 => ['active' => true]])->save();
// Now: [{"role_id":1,"active":false},{"role_id":3,"active":true}]

$user->roles()->toggle([2 => ['active' => true], 3])->save();
// Now: [{"role_id":1,"active":false},{"role_id":2,"active":true}]

Limitations: On SQLite and SQL Server, these relationships only work partially.

Composite Keys

If multiple columns need to match, you can define a composite key.

Pass an array of keys that starts with JSON key:

class Employee extends Model
{
    public function tasks()
    {
        return $this->belongsToJson(
            Task::class,
            ['options->work_stream_ids', 'team_id'],
            ['work_stream_id', 'team_id']
        );
    }
}

class Task extends Model
{
    public function employees()
    {
        return $this->hasManyJson(
            Employee::class,
            ['options->work_stream_ids', 'team_id'],
            ['work_stream_id', 'team_id']
        );
    }
}

Query Performance

MySQL

On MySQL 8.0.17+, you can improve the query performance with multi-valued indexes.

Use this migration when the array is the column itself (e.g. users.role_ids):

Schema::create('users', function (Blueprint $table) {
    // ...
    
    // Array of IDs
    $table->rawIndex('(cast(`role_ids` as unsigned array))', 'users_role_ids_index');
    
    // Array of objects
    $table->rawIndex('(cast(`roles`->\'$[*]."role_id"\' as unsigned array))', 'users_roles_index');
});

Use this migration when the array is nested inside an object (e.g. users.options->role_ids):

Schema::create('users', function (Blueprint $table) {
    // ...
    
    // Array of IDs
    $table->rawIndex('(cast(`options`->\'$."role_ids"\' as unsigned array))', 'users_role_ids_index');
    
    // Array of objects
    $table->rawIndex('(cast(`options`->\'$."roles"[*]."role_id"\' as unsigned array))', 'users_roles_index');
});

MySQL is quite picky about the syntax so I recommend that you check once with EXPLAIN that the executed relationship queries actually use the index.

PostgreSQL

On PostgreSQL, you can improve the query performance with jsonb columns and GIN indexes.

Use this migration when the array of IDs/objects is the column itself (e.g. users.role_ids):

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->jsonb('role_ids');
    $table->index('role_ids')->algorithm('gin');
});

Use this migration when the array is nested inside an object (e.g. users.options->role_ids):

Schema::create('users', function (Blueprint $table) {
    $table->id();
    $table->jsonb('options');
    $table->rawIndex('("options"->\'role_ids\')', 'users_options_index')->algorithm('gin');
});

Has-Many-Through Relationships

Similar to Laravel's HasManyThrough, you can define HasManyThroughJson relationships when the JSON column is in the intermediate table (Laravel 9+). This requires staudenmeir/eloquent-has-many-deep.

Consider a relationship between Role and Project through User:

Role → has many JSON → User → has many Project

Install the additional package, add the HasRelationships trait to the parent (first) model and pass the JSON column as a JsonKey object:

class Role extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function projects()
    {
        return $this->hasManyThroughJson(
            Project::class,
            User::class,
            new \Staudenmeir\EloquentJsonRelations\JsonKey('options->role_ids')
        );
    }
}

The reverse relationship would look like this:

class Project extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;

    public function roles()
    {
        return $this->hasManyThroughJson(
            Role::class, User::class, 'id', 'id', 'user_id', new JsonKey('options->role_ids')
        );
    }
}

Deep Relationship Concatenation

You can include JSON relationships into deep relationships by concatenating them with other relationships using staudenmeir/eloquent-has-many-deep (Laravel 9+).

Consider a relationship between User and Permission through Role:

User → belongs to JSON → Role → has many → Permission

Install the additional package, add the HasRelationships trait to the parent (first) model and define a deep relationship:

class User extends Model
{
    use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function permissions()
    {
        return $this->hasManyDeepFromRelations(
            $this->roles(),
            (new Role)->permissions()
        );
    }
    
    public function roles()
    {
        return $this->belongsToJson(Role::class, 'options->role_ids');
    }
}

class Role extends Model
{
    public function permissions()
    {
        return $this->hasMany(Permission::class);
    }
}

$permissions = User::find($id)->permissions;

Contributing

Please see CONTRIBUTING and CODE OF CONDUCT for details.

eloquent-json-relations's People

Contributors

27pchrisl avatar dsazup avatar francoism90 avatar iksaku avatar mstaack avatar nikazooz avatar staudenmeir avatar szepeviktor avatar ziadoz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

eloquent-json-relations's Issues

Your requirements could not be resolved to an installable set of packages.

I am getting this error . Here is My composer.json:

{
    "name": "october/october",
    "description": "October CMS",
    "homepage": "https://octobercms.com",
    "type": "project",
    "keywords": ["october", "cms", "octobercms", "laravel"],
    "license": "MIT",
    "authors": [
        {
            "name": "Alexey Bobkov",
            "email": "[email protected]",
            "role": "Co-founder"
        },
        {
            "name": "Samuel Georges",
            "email": "[email protected]",
            "role": "Co-founder"
        },
        {
            "name": "Luke Towers",
            "email": "[email protected]",
            "homepage": "https://luketowers.ca",
            "role": "Maintainer"
        }
    ],
    "support": {
        "paid": "https://octobercms.com/premium-support",
        "issues": "https://github.com/octobercms/october/issues",
        "forum": "https://octobercms.com/forum/",
        "docs": "https://octobercms.com/docs/",
        "source": "https://github.com/octobercms/october"
    },
    "repositories": [
        {
            "type":"vcs",
            "url":"https://github.com/octoberrain/composer-merge-plugin"
        }
    ],
    "require": {
        "php": ">=7.2",
        "october/rain": "1.1.*",
        "october/system": "1.1.*",
        "october/backend": "1.1.*",
        "october/cms": "1.1.*",
        "laravel/framework": "~6.0",
        "wikimedia/composer-merge-plugin": "dev-feature/composer-v2 as 1.5.0"
    },
    "require-dev": {
        "phpunit/phpunit": "^8.4|^9.3.3",
        "mockery/mockery": "~1.3.3|^1.4.2",
        "fzaninotto/faker": "~1.9",
        "squizlabs/php_codesniffer": "3.*",
        "php-parallel-lint/php-parallel-lint": "^1.0",
        "meyfa/phpunit-assert-gd": "^2.0.0",
        "dms/phpunit-arraysubset-asserts": "^0.1.0"
    },
    "autoload-dev": {
        "classmap": [
            "tests/concerns/InteractsWithAuthentication.php",
            "tests/fixtures/backend/models/UserFixture.php",
            "tests/TestCase.php",
            "tests/PluginTestCase.php"
        ]
    },
    "scripts": {
        "post-create-project-cmd": [
            "php artisan key:generate",
            "php artisan package:discover"
        ],
        "post-update-cmd": [
            "php artisan october:version",
            "php artisan package:discover"
        ],
        "test": [
            "phpunit --stop-on-failure"
        ],
        "lint": [
            "parallel-lint --exclude vendor --exclude storage --exclude tests/fixtures/plugins/testvendor/goto/Plugin.php ."
        ],
        "sniff": [
            "phpcs --colors -nq --report=\"full\" --extensions=\"php\""
        ]
    },
    "config": {
        "preferred-install": "dist",
        "platform": {
            "php": "7.2"
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true,
    "extra": {
        "merge-plugin": {
            "include": [
                "plugins/*/*/composer.json"
            ],
            "recurse": true,
            "replace": false,
            "merge-dev": false
        }
    }
}

Has many with optional array items

Hey. Great package, I was really happy when I found this exists. I just tried to use it and run into an issue. I was not sure how to name it, but here it goes:

Let's say I have this relationship:

public function conditionGroups()
{
     return $this->belongsToJson(Group::class, 'data->conditions[]->group->id');
}

And the conditions array contains:

[
    {group: {id: 1}, anotherKey: true},
    {group: {id: 2}, anotherKey: true},
    {field: {id: 1}, anotherKey: true},
    {field: {id: 2}, anotherKey: true}
]

I would expect the query to be SELECT * FROM groups WHERE id IN(1,2), but what I actually see is SELECT * FROM groups WHERE id IN(0,1,2). As you can see there is an additional zero coming from somewhere. Would you agree this is a bug?

HasManyThroughJSON

Hi! First I really like this package! Good job! 👍

I wonder if it's possible to use a HasManyThroughJSON to query a parent relationship items.

Thanks

Laravel 6+ suppor?

I see the code was updated 21 days ago so I assume it's active but I am not sure if Laravel 6+ is supported. Can you please confirm? If it is I can perhaps describe an issue I am seeing.

use inside function?

In database I have only plain JSON array of ids, eg.:
[1,5,9]

and I need to get the relationship working.

I have a ProductVariant model and it hasMany ProductAttributeOptions (these are referenced by the ids in that array in ProductVariant; option_ids). How do I get it working? Also how do I use your library scoped to one specific function (variants and options) when defining a relationship? The 'use' directive in function doesn't work for me.

Here is native Laravel relationship represantation of relationships, I need these transfer to your library extension.

class ProductAttributeOption extends Model
{
	.
        .
        .

	public function variants() {
		return $this->belongsTo(ProductVariant::class, 'option_ids[]', 'id'); //Here I need reference the id in array
	}
}
class ProductVariant extends Model
{
	.
        .
        .

	public function options() {
		return $this->hasMany(ProductAttributeOption::class 'id', 'option_ids[]'); //This references the id in array (option_ids) in ProductAttributeOption model option_ids
	}
}

Yes, I know vanilla Larevel don't know the [], but this is just for you to better imagine what I want and what the option_ids is (an array). Thank you.

Invalid argument supplied for foreach()

Great package, but I am having an issue with the hasManyJson relation.

Code

public function recordings()
{
        return $this->hasManyJson(Recording::class, 'properties->device_id', 'device_id');
}

DB Structure
properties field (JSON): {"device_id": "EF9FDEC"}

Error Returned
"Invalid argument supplied for foreach()", exception: "ErrorException",…}

class: "Staudenmeir\EloquentJsonRelations\Relations\HasManyJson"
file: "/Users/greggushard/Valet/vss-portal/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Relations/HasOneOrMany.php"
function: "buildDictionary"
line: 128
type: "->"

Bad method call error many to many relationship

I just follow the whole document to setup, and I am using Laravel 8

SQLSTATE[HY000]: General error: 1366 Incorrect integer value: '[364, 365]' for column 'item_id' at row 1 (SQL: update `shipments` set `item_ids` = {"item_id":[364,365]}, `shipments`.`updated_at` = 2021-04-24 14:23:09 where `id` = 19)

I also try attach

my setup is like this

public function items()
    {
        return $this->belongsToJson('App\Models\Item', 'item_ids->item_id');
    }
    public function shipment()
    {
        return $this->hasManyJson('App\Models\Shipment', 'item_ids->item_id');
    }
$shipment->items()->sync($selectedItemId)->save();
  KEY `shipments_item_id_foreign` (`item_id`),
  CONSTRAINT `order_shipment` FOREIGN KEY (`order_id`) REFERENCES `orders` (`id`),
  CONSTRAINT `shipments_item_id_foreign` FOREIGN KEY (`item_id`) REFERENCES `items` (`id`)

Please advise

belongsToJson pivot ?

When there is a duplicate ID, the data is chaotic

//OrderModel
public function goods()
    {
        return $this->belongsToJson(GoodsModel::class, 'goods_data[]->id');
    }
//OrderModel goods_data
[
  {"id": 23, "amount": "13", "remark": "131313"}, 
  {"id": 23, "amount": "7", "remark": "777"},
  {"id": 23, "amount": "5", "remark": "555"}
]

Every goods's pivot is the first

$order = OrderModel::with('goods')->find($id);
$order->goods; // 3 items

$order = OrderModel::find($id);
$order->goods; //1 items

How to work with pivot data

I'm column json with price type and value

"prices": [{ "id": 1, "value": 5000 }, { "id": 2, "value": 30000 }],

It's realy required?

Since you change requirements from "illuminate/database": "~5.5.29|5.6.*|5.7.*", to "illuminate/database": "5.8.*" all systems that works with 5.7 and lower became unsupported. This is not like "added support 5.8" but "5.7 <= support removed".

Also

"require-dev": {
        "laravel/homestead": "^7.18",
        "phpunit/phpunit": "~6.5"
    },

there are not necessary for workability and may be moved to suggest section.

Use simple json column

I use below code not work.

class Notification extends Model
{
    protected $casts = [
        'user_ids' => 'json',
        'config' => 'json',
    ];

    public function users()
    {
        return $this->belongsToJson(User::class, 'user_ids');
    }
}

but this work.

class Notification extends Model
{
    protected $casts = [
        'config' => 'json',
    ];

    public function users()
    {
        return $this->belongsToJson(User::class, 'config->user_ids');
    }
}

Relationship doesn't working

What I trying to achieve is get the relationship between "Stock" and "Supply" model.
I tried to use these parameters but didn't work.

public function supplies()
{
  return $this->belongsToJson('App\Supply', 'supplies->[]->supply->id');
}

The column which stores the JSON is "supplies" and it has this format:

[
  {
    "supply": {
      "id": 1,
      ...
    },
    "price": "1",
    "quantity": "5"
  },
  {
    "supply": {
      "id": 2,
      ...
    },
    "price": "1",
    "quantity": "10"
  }
]

Array of Objects "hasManyJson", "belongsTo" Got Empty Results

Hi, I have created a repo to demonstrate my use case as follows :
ragavanrandy/staudenmeir-eloquent-json-relations-demo

In my case, I have two models: User and Order.
The Order Model has worklogs json field to hold array of object and each object contains the User model primary key.

The Order and User has relationship, To get the users of order and orders of user respectively.

In User Model:

public function orders(){
    return $this->hasManyJson(Order::class, 'worklogs->[]->user_id', 'user_id');
}

In Order Model:

public function user(){
    return $this->belongsToJson(User::class, 'worklogs->[]->user_id', 'user_id');
}

In controller, jsonDemo.php:

public function index(){
        $users = User::with('orders')->get();
        dd($users[0]);
    }

public function userOrders($id=null){
    if($id != null){
        $user = User::find($id);
        $user->load('orders');
        dd($user);
    }
}

I got empty results in for relational models as follows:

getjson

get_userorders

To reproduce this case,

Updating/Saving the one-to-many relationship

Looking at the example from Readme with User and 'locale_id' kept in `options` - actually how can we update the user's locale_id?

We can easily read $user->locale, but how to update it?

And how does updating work? I mean if `options` kept other attributes like for example 'currency', will setting just one of the attributes leave untouched the other attributes already present in the db?

Can BelongsToManyJson be supported?

Due to some special circumstances, I know that it can be obtained through BelongsToJson, but the relationship of BelongsToJson is one-to-one

BelongsTo Relation with array

Hello,

I'v got table named received_orders
and table named invoices with column invoices (invoices.invoices) as JSON
contains received_orders id array
for example

[45,67,154]

the received order with id=45 belongs to this invoice

I'v tried

return $this->BelongsTo('App\Invoice', 'invoices','id');

return $this->BelongsTo('App\Invoice', 'invoices[]');

hasManyJson(): array_key_exists() expects parameter 2 to be array, null given

platform: laravel v7.2+ postgresql v12 + eloquent-json-relations v1.4; Follow the guild, exactly.
problem: using json array relations public function users() { return $this->hasManyJson('App\User', 'roles[]->role_id'); }, the json column:
[{"role_id":1,"active":true},{"role_id":2,"active":false}]
when test User::find(1)->roles, return array_key_exists() expects parameter 2 to be array, null given

  • If trying to change the Integer 1 to String "1": [{"role_id":"1","active":true},{"role_id":"2","active":false}],
    there's no error, but return an empty array.
    -trying to use mysql v8.0 instead of postgresql, get the same resaults.

Support in laravel 7 and 8?

Hello, I am working on a project in laravel 7 and this package would be of great help to me. But it has no support for laravel 7 :(
I don't know if you could put the support for laravel 7 or I could help you that you would need me to check your package with the updates of laravel so I can fork and help the update-

UPDATE
I started to navigate in their tags and I realized that I must use version 1.4. * However, they should specify in their composer.json that they support database8 and 7 since if people who use laravel 8 make updates we will not have the updates

Save items order

I have JSON-field with this value:

[{"artist_id":"5"},{"artist_id":"34"},{"artist_id":"35"},{"artist_id":"4"}]

Add relation in model:

public function lineupItems() : BelongsToJson
{
    return $this->belongsToJson(Artist::class, 'lineup');
}

After that I call: $model->lineupItems and get items in order 4, 5, 34, 35, but the right order is 5, 34, 35, 4.

I solve it by 2 solutions. First:

public function lineupMembers() : BelongsToJson
{
    $ids = collect($this->lineup)->pluck('artist_id');
    return $this->belongsToJson(Artist::class, 'lineup')
        ->orderByRaw(DB::raw('field(id,' . implode($ids->toArray(), ',') . ')'));
}

And another:

public function getLineupMembersAttribute() : Collection
{
    return $this->lineupItems->sortBy(function ($product, $key) {
        return search_for_id('artist_id', $product->id, $this->lineup);
    });
}

// Helper function
function search_for_id($field, $id, $array)
{
    foreach ($array as $key => $val) {
        if(!isset($val[$field])) {
            return null;
        }

        if ($val[$field] == $id) {
            return $key;
        }
    }
    return null;
}

I'm not sure this is issue, but it's interesting usecase and documentation for others.

Relationship for hasMany with array

I have two tables:

people: id, name, team_ids
teams: id, name

Where people.team_ids is a JSON array.

I have a relationship on people:

public function teams() {
    return $this->hasMany(Team::class, 'id', 'team_ids[]');
}

It looks like HasJsonRelationships expects the field to be and object with a key-value of arrays ({ "teams": [ 1, 2, 3 ] }) whereas our system is [ 1, 2, 3 ] in the team_ids field.

From what I can tell, it looks like getAttributeValue is missing handling of of this situation. Would it be possible to add said handling? I've done some exploratory digging, and it looks like this is the place to add the code, but I'm not completely certain what would need to be added.

MariaDB json_contains parameter order

Here's the issue that I'm having, it produces the code to where the function has the wrong parameter order.

Here's what I'm running:
mysql.exe Ver 15.1 Distrib 10.4.11-MariaDB, for Win64 (AMD64), source revision 7c2c420b70b19cc02b5281127205e876f3919dad
Laravel Framework 7.8.1

Tables:

Schema::table('entities', function (Blueprint $table) {
    $table->id('grp_id');
    $table->json('leaders');
});
Schema::table('users', function(Blueprint $table) {
    $table->id();
    $table->text('name');
});

Example of Entities Table:

id - 1
leaders - [1, 3]

id - 2
leaders - [1]

Relationships:

class Entity extends Model
{
    protected $primaryKey = 'entities';
    protected $primaryKey = 'grp_id';

    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function leaders()
    {
        return $this->hasManyJson(User::class, 'id', 'leaders')->distinct();
    }
}
use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

class User extends Authenticatable
{
    use Notifiable, HasRoles, HasLocalDates, HasJsonRelationships;
    protected $casts = [
        'email_verified_at' => 'datetime',
        'leaders' => 'json',
    ];

    public function leaders()
    {
        return $this->belongsToJson(Entity::class, 'leaders');
    }
}

Called by:
$entity = Entity::with('leaders')->get();

Runs query:

select distinct * from `users` where (json_contains(`users`.`id`, '"[1, 3]"') or json_contains(`users`.`id`, '"[1]"'))

When I dd():

#relations: array:1 [▼
"leaders" => Illuminate\Database\Eloquent\Collection {#520 ▼
#items: []
}
]

When I run it raw in the database it works by swapping the parameters of json_contains.
https://mariadb.com/kb/en/json_contains/

HasManyThroughJson

I have three tables Organisation, Contact, Emails

my issues is im trying to get all the emails through client which maps like this client[id] -> contacts[client_id][email] -> contacts[to] but to is a json array of objects for example [{"name": "", "email": ""}]

when I try 'to[]->email' as my identifier it fails and is unable find anything yet to[]->email works elsewhere on has any son etc

I hope this makes sense I'm just wondering if there's a way to make this work

Add "using()" method, to can use json column in a pivot table

Hello. thanks for the great package
I have a problem, is that possible for you to help?

I have 3 models: Test, Step and Supervisor
Each test can have one or more steps, and steps can be in common between tests so relation between tests and steps are manyToMany via a pivot table. and each test on specific steps can have some supervisors. I want to put supervisors ids in a column in my pivot table. what i need to do this, is to be able to use a pivot model which is available for regular relationships. like below

public function supervisors()
    {
        return $this->hasManyJson('App\Models\Supervisor', 'supervisors')->using('App\Models\TestStep');
    }
    pivot tabel:
     ----------------------------------
     | test_id | step_id | supervisors |
     ----------------------------------
     |    1    |     2   | [10, 20, 30]|

Erro to get foreign keys, when has cast using Collection

image

image

Sample fix:
image

public function getForeignKeys(Model $model = null)
{
    $model = $model ?: $this->child;

    if ($model->{$this->foreignKey} instanceof \Illuminate\Support\Collection) {
        $keys = $model->{$this->foreignKey}->toArray();
    } else {
        $keys = (array) $model->{$this->foreignKey};
    }

    return array_filter($keys, function ($key) {
        return $key !== null;
    });
}

Loading via load() method doesn't work

Hi, great package. I get zero results when I use $model->load('relation') , which seems like a bug? EG:

// the relation on Booking model:
    public function notifications(){
        return $this->hasMany('App\Models\Notification', 'data->booking_id');
    }

dd($booking->notifications); // this provides results

// if I first load the relation, I get no results
$booking->load('notifications');
dd($booking->notifications); // this provides no results

Using v1.5, Laravel 8

Not installing via composer

not installing on composer install. I have added this package through composer require command, it has been installed successfully.But not installing on server via composer install.But downloading on composer update. Here is my composer.json. I only pushed composer.json
{ "name": "laravel/lumen", "description": "The Laravel Lumen Framework.", "keywords": ["framework", "laravel", "lumen"], "license": "MIT", "type": "project", "require": { "php": ">=7.1.3", "doctrine/dbal": "^2.9", "guzzlehttp/guzzle": "^6.3", "kreait/firebase-php": "^4.0", "laravel/lumen-framework": "5.7.*", "lcobucci/jwt": "^3.2", "league/flysystem-aws-s3-v3": "~1.0", "staudenmeir/eloquent-json-relations": "^1.1", "vlucas/phpdotenv": "~2.2" }, "require-dev": { "fzaninotto/faker": "~1.4", "phpunit/phpunit": "~7.0", "mockery/mockery": "~1.0" }, "autoload": { "classmap": [ "database/seeds", "database/factories" ], "psr-4": { "App\\": "app/" } }, "autoload-dev": { "classmap": [ "tests/" ] }, "scripts": { "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ] }, "config": { "preferred-install": "dist", "sort-packages": true, "optimize-autoloader": true }, "minimum-stability": "dev", "prefer-stable": true }

Laravel Translatable

Problem: We cannot use this package with Laravel Translatable
Error: Trait method getAttributeValue has not been applied, because there are collisions with other trait methods on App\\Product",

It would be nice to be able to use both in a model.

BelongsTo whereHas with fully qualified columns

Hello,

Before anything else, thank you for this package.
It definitely should be included in Laravel's core.

I'm facing a limitation now regarding the BelongsTo relationship and whereHas.

Consider the data attribute of the Notification model.
In my use case, it contains a custom field — activity_id — that references another model. Therefore the following is achieved: Notification -- BelongsTo --> Activity.

Using whereHas from the BelongsTo relationship as follows is causing a PDOException:

$user->notifications()->whereHas('activity')->get();

The error is:

Caused by
PDOException: SQLSTATE[42883]: Undefined function: 7 ERROR:  operator does not exist: text ->> unknown
LINE 1: ...t * from "activities" where "notifications"."data"->>'activi...
                                                             ^
HINT:  No operator matches the given name and argument types. You might need to add explicit type casts.

/home/vagrant/code/carsight/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOStatement.php:121
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Connection.php:335
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Connection.php:662
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Connection.php:629
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Connection.php:338
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2149
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2137
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2609
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2138
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2236
/home/vagrant/code/carsight/vendor/laravel/framework/src/Illuminate/Database/Query/Builder.php:2207

I suspect it is related to fully qualified columns.

I am using:

  • v1.3.1 of this package.
  • Laravel 6.18.8
  • PostgreSQL 10.12

with() not working for hasOne relation between json to json field!!

I have a relation as following:

class X {
public function eX()
    {
        return $this->hasOne(EX::class, 'file->>e', 'data->field');
    }
}
}

I have a field data (JSON: '{ "field": "some_value" }') in X and field file (JSON: '{ "e": "some_value" }') in EX.

I am calling the relation with the following query:

X::select('*')
   ->with('eX')
   ->get();

The output of the query I am getting is having a e_x key with value null but I have data according to the relation, so I was expecting an object with the child being joined. The output of getQueryLog() is following:

array:2 [
0 => array:3 [
"query" => "select * from x where x.deleted_at is null
"time" => 0.68
]
1 => array:3 [
"query" => "select * from e_x where e_x.file->>'$."e"' in (?, ?, ?) and e_x.deleted_at is null"
"bindings" => array:3 [
0 => "1568851023-7283a7f28d582bcb3ef1ba1f83010fd6"
1 => "1568851208-adbde7e3a2ca4613eb83682ffeab00cb"
2 => "1568851823-c04d0530718306aedc0080864c1c7599"
]
"time" => 0.44
]

I am getting the correct value in e_x if the bindings of index 1 is having only one element but when the number of elements is greater than 1 then the output of e_x is null

Please help me with a solution. I will be waiting for your reply.

hasManyJson not prepending prefix on withCount & whereHas?

The error I am getting is

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'skills.id' in 'where clause' (SQL: select id, name, (select count(*) from fc_x_details where json_contains(fc_x_details.skill, json_object(skill_id, skills.id)) and fc_x_details.deleted_at is null) as x_detail_count from fc_skills where status = 1 and fc_skills.deleted_at is null limit 92 offset 0)

The skills in bold should be fc_skills but the prefix is not taking. The code I am using is given below:

Skill::select('id', 'name')
            ->where('status', '1')
        ->withCount('xDetail')

The relation is:

public function xDetail()
    {
        return $this->hasManyJson(XDetail::class, 'skill');
    }

The field is fc_x_details.skill is of type json containing array of ids of table fc_skills like the following:
[1,2,3]

Please try to reply as soon as possible. I am in the middle of a project and is very much in need of a solution.

Cant get Relasi on belongsTo

I get an error like this

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'users.kepada' in 'where clause' (SQL: select * from users where json_contains(users.kepada->'$."user"', 17))

my Code is:

/**
* Get Model User
*/
public function selectedKepada() {
    return $this->hasManyJson('App\User', 'kepada->user');
}

and my DB record is:
[{"user": 1}]

HasManyThrough with JSON - does it work?

Hi there, great package!

3 models: Form, FormGroups, & Workshops. Workshops hasOne FormGroup (via form_group_id). form_groupshas aform_idscolumn that contains a JSON of Formids, like [1,2]`.

I'd like to get a Workshop's Forms, so a hasManyThrough (I think) but I can't quite get it to work.

Is this the same problem: #13 ?

Ideally I'd like $workshop->forms() and have it return a collection of Forms.

Trait 'Staudenmeir\EloquentJsonRelations\HasJsonRelationships' not found

Installed in my Laravel 5.8 project recently and was unable to get trait to load. I ran composer dump-autoload and php artisan cache:clear.

I was able to get it to work by adding "Staudenmeir\EloquentJsonRelations\": "vendor/staudenmeir/eloquent-json-relations/src/" to composer.json under autoload.ps4 and autoload-dev.ps4.

Is there another solution?

Does not work with string values

Problem

I currently have two models, Event and Account. Account has an id column of type string, and Event has a data column with a JSON value like {"userId": "12345", "a-bunch-more": "stuff"}. The query is sending the Account.id value as an integer, which doesn't match the string value in JSON.

Models

The model classes look like this (some stuff omitted for brevity):

class Account extends Model
{
    use HasJsonRelationships;

   protected $with = ['events'];

  public function events(): HasMany
  {
        return $this->hasMany(Event::class, "data->userId");
  }
}
class Event extends Model
{
    use HasJsonRelationships;

    public function account(): BelongsTo
    {
        return $this->belongsTo(Account::class, 'data->userId');
    }
}

Queries

The query being run (per DB::getQueryLog()) looks like this:

select * from "events" where "events"."data"->>'userId' in (?)

And the final query for Student->id = "12345" looks like:

select * from "events" where "events"."data"->>'userId' in (12345)

Which won't work because data->>userId is a string. It needs to be:

select * from "events" where "events"."data"->>'userId' in ('12345')

eager load use with method,show error “array_key_exists() expects parameter 2 to be array, null given”

My Code as follows:

class CourseCategory extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    public function teachers()
    {
        return $this->hasManyJson(Teacher::class, 'courses[]->category');
    }

}

class Teacher extends Model
{
    use \Staudenmeir\EloquentJsonRelations\HasJsonRelationships;

    protected $casts = [ 
        'courses' => 'json',  
    ];
 
    public function courseCategories()
    {
        return $this->belongsToJson(CourseCategory::class, 'courses[]->category');
    }

}

the json column data sample:

[{"main_category":21,"category":33,"course":[85]},{"main_category":20,"category":25,"course":[88]},{"main_category":37,"category":40}]

When I use CourseCategory::find(33)->teachers , is ok,
but I use CourseCategory::with('teachers')->find(33) , show error array_key_exists() expects parameter 2 to be array, null given

I check vendor/staudenmeir/eloquent-json-relations/src/Relations/HasManyJson.php:221

    protected function pivotAttributes(Model $model, Model $parent)
    {
        $key = str_replace('->', '.', $this->key);
     
        $record = collect($model->{$this->getPathName()})
            ->filter(function ($value) use ($key, $parent) {
                return Arr::get($value, $key) == $parent->{$this->localKey};
            })->first();
        
        return Arr::except($record, $key);
    }

I found $parent->{$this->localKey} is null

hasManyJson saved as array of strings

Project had been in use for a long time before I found the package, and all the many to many relations are stored as array of strings, something like {ids: ["2", "3", "4"]}. Relation "hasManyJson" in this case doesn't work and returns empty collection, it expects json field to be {ids: [2, 3, 4]}, but I must admit that "belongsToJson" method works perfectly. Also graphql schema requires ids to be a list of strings, so what solution can you advise to make 'hasManyJson' metod work regardless if he gets a list of strings or list of integers.
And also sync, attach and other methods ruin everything, becouse they rewrite strings to integers in ids array and graphql fails.
To sum up:

  1. "belongsToJson" doesn't care if he gets list of integers or list of strings
  2. "hasManyJson" requires only list of integers
    it would be perfect if "hasManyJson" behaved same as "belongsToJson";

SQLServer 2019 does not get the flatten json ids

I have the following relationship:

return $this->belongsToJson(Scheme::class, 'scheme_ids');

The scheme_ids is of type varchar but contains some JSON ids as follows:

[
    1,
    2,
    3
]

The relationship generates the following SQL:

SELECT
	[schemes].[name],
	[schemes].[id]
FROM
	[schemes]
WHERE
	[schemes].[id] in(0)

Why it returns in(0) instead of the scheme_id data?

Help me get the data.

Hi everyone, I'm trying to add relationships, but it doesn't work.

models

class JobVacancy extends Model implements Transformable
{
    use TransformableTrait;

    use HasJsonRelationships, Translatable {
        HasJsonRelationships::getAttribute as getAttributeJson;
        Translatable::getAttribute as getAttributeTranslations;
    }

    public function getAttribute($key)
    {
        if (! $this->isTranslationAttribute($key)) {
            return $this->getAttributeJson($key);
        }

        return $this->getAttributeTranslations($key);
    }

    protected $casts = [
        'parameters' => 'json',
    ];

    public $translatedAttributes = [
        'name',
        'description',
        'm_title',
        'm_description',
        'm_keywords',
    ];

    public function parameters()
    {
        return $this->belongsToJson('App\Entities\Parameter', 'parameters[]->id');
    }
}

class Parameter extends Model implements Transformable
{
    use TransformableTrait;
    use NodeTrait;

    use HasJsonRelationships, Translatable {
        HasJsonRelationships::getAttribute as getAttributeJson;
        Translatable::getAttribute as getAttributeTranslations;
    }

    public function getAttribute($key)
    {
        if (! $this->isTranslationAttribute($key)) {
            return $this->getAttributeJson($key);
        }

        return $this->getAttributeTranslations($key);
    }

    public $timestamps = false;

    public $translatedAttributes = [
        'name',
    ];

    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);

        $this->defaultLocale = app()->getLocale();
    }

    public function jobVacancies()
    {
        return $this->hasManyJson('App\Entities\JobVacancy', 'parameters[]->id');
    }
}

Tables
Аннотация 2020-05-16 183125

Migrations

        Schema::create(
            'job_vacancies',
            function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->json('parameters')->nullable();
                $table->boolean('status')->default(true);
                $table->timestamps();
            }
        );

       Schema::create(
            'parameters',
            function (Blueprint $table) {
                $table->bigIncrements('id');
                $table->integer('sorting_order')->default(0);
                $table->nestedSet();
            }
        );

Data in DB
Аннотация 2020-05-16 194617
Аннотация 2020-05-16 194643

Result

App\Entities\JobVacancy {#1656 ▼
  #fillable: array:8 [▶]
  #casts: array:8 [▶]
  +translatedAttributes: array:5 [▶]
  #with: array:1 [▶]
  #connection: "mysql"
  #table: "job_vacancies"
  #primaryKey: "id"
  #keyType: "int"
  +incrementing: true
  #withCount: []
  #perPage: 15
  +exists: true
  +wasRecentlyCreated: false
  #attributes: array:12 [▶]
  #original: array:12 [▶]
  #changes: []
  #dates: []
  #dateFormat: null
  #appends: []
  #dispatchesEvents: []
  #observables: []
  #relations: array:2 [▼
    "images" => Illuminate\Database\Eloquent\Collection {#1658 ▶}
    "parameters" => Kalnoy\Nestedset\Collection {#1815 ▼
      #items: []
    }
  ]
  #touches: []
  +timestamps: true
  #hidden: []
  #visible: []
  #guarded: array:1 [▶]
  #defaultLocale: null
}

Please help us understand what the problem is

belongsTo not working with array

Here's my json, the field name is "payload":
{"icon": "img/categorias/automotivo.png", "route": "website.listaLocais", "routeParams": {"slug": "automotivo"}}

My relationship, slug is the field name on the parent table:
return $this->belongsToJson(Category::class, 'slug', 'payload->routeParams[]->slug');

I've also tried:
return $this->belongsToJson(Category::class, 'slug', 'payload[]->routeParams[]->slug');
return $this->belongsToJson(Category::class, 'slug', 'payload->routeParams->slug');

Is what I'm trying to achieve not supported at the moment or I'm just not doing it right?

hasManyJson, multiple foreign keys

I was wondering if its possible to add in many foreign keys for an example ['email[]->to', email[]->from]
?
if so is it something that would be easy to implement

Strange bug when calling relationship in accessor

Strange but was trying to call a relationship in an accessor like so

public function getOptionsAttribute($value) {
        
    //wanted to loop through the actual relation because it carries the additional data needed
    // just a test...to see what is returned
    error_log(print_r( $this->roles->get()->toArray(), true ));

    return json_decode($value, true);
}

then if I call the relationship or try to retrieve the options attribute like so... (I used tinker to test)

\App\User::find(7)->roles
or
\App\User::find(7)->options

...I get the following

PHP Notice: Undefined property: App/User::$roles[]->role_id in /path_to_project/vendor/staudenmeir/eloquent-json-relations/src/Relations/BelongsToJson.php on line 97

line 97 refers to this line below in the addConstraints() method of BelongsToJson.php
$this->query->whereIn($table.'.'.$this->ownerKey, (array) $this->child->{$this->foreignKey});

Calling other relationships this way, within the accessor, seem to work except BelongsToJson

Can't install on version 6

I have tried to use the command on the readme but I keep getting an error from composer.

Your requirements could not be resolved to an installable set of packages.

Problem 1
- Installation request for staudenmeir/eloquent-json-relations v1.1 -> satisfiable by staudenmeir/eloquent-json-relations[v1.1].
- Conclusion: remove laravel/framework v6.0.0
- Conclusion: don't install laravel/framework v6.0.0

Polymorphic Relations

Hi,

love the package, but I cant seem to get polymorphic relations working from within the JSON column.

For example, if I have a User model with a JSON column named 'custom_fields' which is an array of objects. Would it be possible to store something like

[ { 'relationable_id': 12, 'relationable_type: 'App/Role' }, { 'relationable_id': 13, 'relationable_type: 'App/Permission' } ]

Can you provide an example of doing many to many polymorphic relations?

Query Performance

While using this brilliant package i noticed using gin indicies on the json columns
gives quite some improvement.

Should this be added to the docs?

belongsToJson not working with EloquentFilter!!

I am trying to implement a nested filter but it is not working with a belongsToJson relation in second level.
The same nested filter works if I try to do using belongsTo relation with other field.

What I mean is:
A-[hasMany]->B-[belongsTo]->C (works)
but
A-[hasMany]->B-[belongsToJson]->C (doesn't work)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.