Skip to content

Commit

Permalink
Add 1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed Oct 19, 2018
1 parent 4c324d4 commit 20e1c0d
Show file tree
Hide file tree
Showing 29 changed files with 1,386 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/tests export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.travis.yml export-ignore
/phpunit.xml.dist export-ignore
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/.idea
/.vagrant
/vendor
/after.sh
/aliases
/composer.lock
/Homestead.yaml
/Vagrantfile
30 changes: 30 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
language: php
sudo: required
dist: trusty

addons:
apt:
sources:
- mysql-5.7-trusty
packages:
- mysql-server
- mysql-client

php:
- 7.0
- 7.1
- 7.2

env:
- PREFER_LOWEST="--prefer-lowest"
- PREFER_LOWEST=""

before_install:
- sudo mysql -e "use mysql; update user set authentication_string=PASSWORD('password') where User='root'; update user set plugin='mysql_native_password'; flush privileges;"
- sudo mysql_upgrade -u root -ppassword
- sudo service mysql restart
- sudo mysql -u root -ppassword -e 'create database `test`;'

install: travis_retry composer update --no-interaction --no-suggest --prefer-dist --prefer-stable $PREFER_LOWEST

script: vendor/bin/phpunit
141 changes: 141 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
[![Build Status](https://travis-ci.org/staudenmeir/eloquent-json-relations.svg?branch=master)](https://travis-ci.org/staudenmeir/eloquent-json-relations)
[![Latest Stable Version](https://poser.pugx.org/staudenmeir/eloquent-json-relations/v/stable)](https://packagist.org/packages/staudenmeir/eloquent-json-relations)
[![Total Downloads](https://poser.pugx.org/staudenmeir/eloquent-json-relations/downloads)](https://packagist.org/packages/staudenmeir/eloquent-json-relations)
[![License](https://poser.pugx.org/staudenmeir/eloquent-json-relations/license)](https://packagist.org/packages/staudenmeir/eloquent-json-relations)

## Introduction
This Laravel Eloquent extension adds support for JSON foreign keys to `BelongsTo`, `HasOne`, `HasMany`, `HasManyThrough`, `MorphTo`, `MorphOne` and `MorphMany` relationships.
It also provides [many-to-many](#many-to-many-relationships) relationships with JSON arrays.

## Compatibility

Database | Laravel
:-----------------|:----------
MySQL 5.7+ | 5.5.29+
MariaDB 10.2+ | 5.8+ (2019)
PostgreSQL 9.3+ | 5.5.29+
[SQLite 3.18+](https://www.sqlite.org/json1.html) | 5.6.35+
SQL Server 2016+ | 5.6.25+

## Installation

composer require staudenmeir/eloquent-json-relations

## Usage

* [Referential Integrity](#referential-integrity)
* [Many-To-Many Relationships](#many-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`):

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

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

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

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

public function users()
{
return $this->hasMany('App\User', 'options->locale_id');
}
}
```

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

**Limitations:** Existence queries (`Locale::has('users')`) and `HasManyThrough` relationships don't work on PostgreSQL with integer keys.

### Referential Integrity

[MySQL](https://dev.mysql.com/doc/refman/en/create-table-foreign-keys.html) and [SQL Server](https://docs.microsoft.com/en-us/sql/relational-databases/tables/specify-computed-columns-in-a-table) support foreign keys on JSON columns with generated/computed columns.

Laravel migrations support this feature on MySQL:

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

On SQL Server, the migration requires raw SQL:

```php
Schema::create('users', function (Blueprint $table) {
$table->increments('id');
$table->json('options');
});

$locale_id = DB::connection()->getQueryGrammar()->wrap('options->locale_id');
DB::statement('ALTER TABLE [users] ADD "locale_id" AS CAST('.$locale_id.' AS INT) PERSISTED');

Schema::table('users', function (Blueprint $table) {
$table->foreign('locale_id')->references('id')->on('locales');
});
```

### Many-To-Many Relationships

This package also introduces two new relationship types: `BelongsToJson` and `HasManyJson`

On Laravel 5.6.25+, you can 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 (`role_ids`) are stored as an array in a JSON field (`users.options`):

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

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

public function roles()
{
return $this->belongsToJson('App\Role', 'options->role_ids');
}
}

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

public function users()
{
return $this->hasManyJson('App\User', 'options->role_ids');
}
}
```

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

```php
$user = new User;
$user->roles()->attach([1, 2]);
$user->save();

$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]
```

**Limitations:** These relationships don't work on SQLite.
30 changes: 30 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "staudenmeir/eloquent-json-relations",
"description": "Laravel Eloquent relationships with JSON keys",
"license": "MIT",
"authors": [
{
"name": "Jonas Staudenmeir",
"email": "[email protected]"
}
],
"require": {
"php": ">=7.0",
"illuminate/database": "~5.5.29|5.6.*|5.7.*",
"illuminate/support": "^5.5.14"
},
"require-dev": {
"laravel/homestead": "^7.18",
"phpunit/phpunit": "~6.5"
},
"autoload": {
"psr-4": {
"Staudenmeir\\EloquentJsonRelations\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
18 changes: 18 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnError="false"
stopOnFailure="false"
verbose="true"
>
<testsuites>
<testsuite name="JsonRelations Test Suite">
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
</phpunit>
14 changes: 14 additions & 0 deletions src/Grammars/JsonGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Staudenmeir\EloquentJsonRelations\Grammars;

interface JsonGrammar
{
/**
* Compile a "cast as JSON" statement into SQL.
*
* @param string $column
* @return string
*/
public function compileCastAsJson($column);
}
19 changes: 19 additions & 0 deletions src/Grammars/MySqlGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Staudenmeir\EloquentJsonRelations\Grammars;

use Illuminate\Database\Query\Grammars\MySqlGrammar as BaseGrammar;

class MySqlGrammar extends BaseGrammar implements JsonGrammar
{
/**
* Compile a "cast as JSON" statement into SQL.
*
* @param string $column
* @return string
*/
public function compileCastAsJson($column)
{
return 'cast('.$this->wrap($column).' as json)';
}
}
19 changes: 19 additions & 0 deletions src/Grammars/PostgresGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Staudenmeir\EloquentJsonRelations\Grammars;

use Illuminate\Database\Query\Grammars\PostgresGrammar as BaseGrammar;

class PostgresGrammar extends BaseGrammar implements JsonGrammar
{
/**
* Compile a "cast as JSON" statement into SQL.
*
* @param string $column
* @return string
*/
public function compileCastAsJson($column)
{
return $this->wrap($column).'::text::jsonb';
}
}
19 changes: 19 additions & 0 deletions src/Grammars/SqlServerGrammar.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Staudenmeir\EloquentJsonRelations\Grammars;

use Illuminate\Database\Query\Grammars\SqlServerGrammar as BaseGrammar;

class SqlServerGrammar extends BaseGrammar implements JsonGrammar
{
/**
* Compile a "cast as JSON" statement into SQL.
*
* @param string $column
* @return string
*/
public function compileCastAsJson($column)
{
return $this->wrap($column);
}
}
Loading

0 comments on commit 20e1c0d

Please sign in to comment.