Skip to content

Commit

Permalink
Merge branch '1.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed Mar 18, 2024
2 parents b5286ce + d871de1 commit c3fb844
Show file tree
Hide file tree
Showing 14 changed files with 914 additions and 62 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Use this command if you are in PowerShell on Windows (e.g. in VS Code):
- [Many-To-Many Relationships](#many-to-many-relationships)
- [Array of IDs](#array-of-ids)
- [Array of Objects](#array-of-objects)
- [Composite Keys](#composite-keys)
- [Query Performance](#query-performance)
- [Has-Many-Through Relationships](#has-many-through-relationships)
- [Deep Relationship Concatenation](#deep-relationship-concatenation)
Expand Down Expand Up @@ -223,6 +224,38 @@ $user->roles()->toggle([2 => ['active' => true], 3])->save();

**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:

```php
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
Expand Down
29 changes: 19 additions & 10 deletions src/HasJsonRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ trait HasJsonRelationships
*/
public function getAttribute($key)
{
$attribute = preg_split('/(->|\[\])/', $key)[0];
$attribute = preg_split('/(->|\[])/', $key)[0];

if (array_key_exists($attribute, $this->attributes)) {
return $this->getAttributeValue($key);
Expand Down Expand Up @@ -221,8 +221,8 @@ protected function newMorphMany(Builder $query, Model $parent, $type, $id, $loca
* Define an inverse one-to-one or many JSON relationship.
*
* @param string $related
* @param string $foreignKey
* @param string $ownerKey
* @param string|array $foreignKey
* @param string|array $ownerKey
* @param string $relation
* @return \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
*/
Expand Down Expand Up @@ -251,8 +251,8 @@ public function belongsToJson($related, $foreignKey, $ownerKey = null, $relation
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $child
* @param string $foreignKey
* @param string $ownerKey
* @param string|array $foreignKey
* @param string|array $ownerKey
* @param string $relation
* @return \Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson
*/
Expand All @@ -265,21 +265,30 @@ protected function newBelongsToJson(Builder $query, Model $child, $foreignKey, $
* Define a one-to-many JSON relationship.
*
* @param string $related
* @param string $foreignKey
* @param string $localKey
* @param string|array $foreignKey
* @param string|array $localKey
* @return \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
*/
public function hasManyJson($related, $foreignKey, $localKey = null)
{
/** @var \Illuminate\Database\Eloquent\Model $instance */
$instance = $this->newRelatedInstance($related);

if (is_array($foreignKey)) {
$foreignKey = array_map(
fn (string $key) => "{$instance->getTable()}.$key",
(array) $foreignKey
);
} else {
$foreignKey = "{$instance->getTable()}.$foreignKey";
}

$localKey = $localKey ?: $this->getKeyName();

return $this->newHasManyJson(
$instance->newQuery(),
$this,
$instance->getTable().'.'.$foreignKey,
$foreignKey,
$localKey
);
}
Expand All @@ -289,8 +298,8 @@ public function hasManyJson($related, $foreignKey, $localKey = null)
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string $foreignKey
* @param string $localKey
* @param string|array $foreignKey
* @param string|array $localKey
* @return \Staudenmeir\EloquentJsonRelations\Relations\HasManyJson
*/
protected function newHasManyJson(Builder $query, Model $parent, $foreignKey, $localKey)
Expand Down
107 changes: 93 additions & 14 deletions src/Relations/BelongsToJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Support\Arr;
use Illuminate\Support\Collection as BaseCollection;
use Staudenmeir\EloquentHasManyDeepContracts\Interfaces\ConcatenableRelation;
use Staudenmeir\EloquentJsonRelations\Relations\Traits\CompositeKeys\SupportsBelongsToJsonCompositeKeys;
use Staudenmeir\EloquentJsonRelations\Relations\Traits\Concatenation\IsConcatenableBelongsToJsonRelation;
use Staudenmeir\EloquentJsonRelations\Relations\Traits\IsJsonRelation;

Expand All @@ -17,6 +18,43 @@ class BelongsToJson extends BelongsTo implements ConcatenableRelation
use InteractsWithPivotRecords;
use IsConcatenableBelongsToJsonRelation;
use IsJsonRelation;
use SupportsBelongsToJsonCompositeKeys;

/**
* The foreign key of the parent model.
*
* @var string|array
*/
protected $foreignKey;

/**
* The associated key on the parent model.
*
* @var string|array
*/
protected $ownerKey;

/**
* Create a new belongs to JSON relationship instance.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $child
* @param string $foreignKey
* @param string $ownerKey
* @param string $relationName
* @return void
*/
public function __construct(Builder $query, Model $child, $foreignKey, $ownerKey, $relationName)
{
$segments = is_array($foreignKey)
? explode('[]->', $foreignKey[0])
: explode('[]->', $foreignKey);

$this->path = $segments[0];
$this->key = $segments[1] ?? null;

parent::__construct($query, $child, $foreignKey, $ownerKey, $relationName);
}

/**
* Get the results of the relationship.
Expand Down Expand Up @@ -61,10 +99,33 @@ public function addConstraints()
if (static::$constraints) {
$table = $this->related->getTable();

$this->query->whereIn($table.'.'.$this->ownerKey, $this->getForeignKeys());
$ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey;

$this->query->whereIn("$table.$ownerKey", $this->getForeignKeys());

if ($this->hasCompositeKey()) {
$this->addConstraintsWithCompositeKey();
}
}
}

/**
* Set the constraints for an eager load of the relation.
*
* @param array $models
* @return void
*/
public function addEagerConstraints(array $models)
{
if ($this->hasCompositeKey()) {
$this->addEagerConstraintsWithCompositeKey($models);

return;
}

parent::addEagerConstraints($models);
}

/**
* Gather the keys from an array of related models.
*
Expand Down Expand Up @@ -94,22 +155,30 @@ protected function getEagerModelKeys(array $models)
*/
public function match(array $models, Collection $results, $relation)
{
$dictionary = $this->buildDictionary($results);
if ($this->hasCompositeKey()) {
$this->matchWithCompositeKey($models, $results, $relation);
} else {
$dictionary = $this->buildDictionary($results);

foreach ($models as $model) {
$matches = [];
foreach ($models as $model) {
$matches = [];

foreach ($this->getForeignKeys($model) as $id) {
if (isset($dictionary[$id])) {
$matches[] = $dictionary[$id];
foreach ($this->getForeignKeys($model) as $id) {
if (isset($dictionary[$id])) {
$matches[] = $dictionary[$id];
}
}
}

$model->setRelation($relation, $collection = $this->related->newCollection($matches));
$collection = $this->related->newCollection($matches);

$model->setRelation($relation, $collection);
}
}

foreach ($models as $model) {
if ($this->key) {
$this->hydratePivotRelation(
$collection,
$model->getRelation($relation),
$model,
fn (Model $model, Model $parent) => $parent->{$this->path}
);
Expand Down Expand Up @@ -150,7 +219,9 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery,
return $this->getRelationExistenceQueryForSelfRelation($query, $parentQuery, $columns);
}

[$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, $this->ownerKey);
$ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey;

[$sql, $bindings] = $this->relationExistenceQueryOwnerKey($query, $ownerKey);

$query->addBinding($bindings);

Expand All @@ -160,6 +231,10 @@ public function getRelationExistenceQuery(Builder $query, Builder $parentQuery,
$query->getQuery()->connection->raw($sql)
);

if ($this->hasCompositeKey()) {
$this->getRelationExistenceQueryWithCompositeKey($query);
}

return $query->select($columns);
}

Expand Down Expand Up @@ -237,9 +312,11 @@ public function pivotAttributes(Model $model, Model $parent, array $records)
{
$key = str_replace('->', '.', $this->key);

$ownerKey = $this->hasCompositeKey() ? $this->ownerKey[0] : $this->ownerKey;

$record = (new BaseCollection($records))
->filter(function ($value) use ($key, $model) {
return Arr::get($value, $key) == $model->{$this->ownerKey};
->filter(function ($value) use ($key, $model, $ownerKey) {
return Arr::get($value, $key) == $model->$ownerKey;
})->first();

return Arr::except($record, $key);
Expand All @@ -255,7 +332,9 @@ public function getForeignKeys(Model $model = null)
{
$model = $model ?: $this->child;

return (new BaseCollection($model->{$this->foreignKey}))->filter(fn ($key) => $key !== null)->all();
$foreignKey = $this->hasCompositeKey() ? $this->foreignKey[0] : $this->foreignKey;

return (new BaseCollection($model->$foreignKey))->filter(fn ($key) => $key !== null)->all();
}

/**
Expand Down
Loading

0 comments on commit c3fb844

Please sign in to comment.