Skip to content

Commit

Permalink
Merge branch 'refs/heads/1.10'
Browse files Browse the repository at this point in the history
  • Loading branch information
staudenmeir committed Jul 21, 2024
2 parents a267a2f + 3d7a0a4 commit e9480d3
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 7 deletions.
18 changes: 18 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)
- [HasOneJson](#hasonejson)
- [Composite Keys](#composite-keys)
- [Query Performance](#query-performance)
- [Has-Many-Through Relationships](#has-many-through-relationships)
Expand Down Expand Up @@ -224,6 +225,23 @@ $user->roles()->toggle([2 => ['active' => true], 3])->save();

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

#### HasOneJson

Define a `HasOneJson relationship if you only want to retrieve a single related instance:

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

public function latestUser(): \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson
{
return $this->hasOneJson(User::class, 'options->roles[]->role_id')
->latest();
}
}
```

#### Composite Keys

If multiple columns need to match, you can define a composite key.
Expand Down
47 changes: 47 additions & 0 deletions src/HasJsonRelationships.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use RuntimeException;
use Staudenmeir\EloquentJsonRelations\Relations\BelongsToJson;
use Staudenmeir\EloquentJsonRelations\Relations\HasManyJson;
use Staudenmeir\EloquentJsonRelations\Relations\HasOneJson;
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\BelongsTo as BelongsToPostgres;
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasMany as HasManyPostgres;
use Staudenmeir\EloquentJsonRelations\Relations\Postgres\HasManyThrough as HasManyThroughPostgres;
Expand Down Expand Up @@ -307,6 +308,52 @@ protected function newHasManyJson(Builder $query, Model $parent, $foreignKey, $l
return new HasManyJson($query, $parent, $foreignKey, $localKey);
}

/**
* Define a one-to-one JSON relationship.
*
* @param string $related
* @param string|array $foreignKey
* @param string|array|null $localKey
* @return \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson
*/
public function hasOneJson(string $related, string|array $foreignKey, string|array|null $localKey = null): HasOneJson
{
/** @var \Illuminate\Database\Eloquent\Model $instance */
$instance = $this->newRelatedInstance($related);

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

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

return $this->newHasOneJson(
$instance->newQuery(),
$this,
$foreignKey,
$localKey
);
}

/**
* Instantiate a new HasOneJson relationship.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param \Illuminate\Database\Eloquent\Model $parent
* @param string|array $foreignKey
* @param string|array $localKey
* @return \Staudenmeir\EloquentJsonRelations\Relations\HasOneJson
*/
protected function newHasOneJson(Builder $query, Model $parent, string|array $foreignKey, string|array $localKey): HasOneJson
{
return new HasOneJson($query, $parent, $foreignKey, $localKey);
}

/**
* Define has-many-through JSON relationship.
*
Expand Down
2 changes: 1 addition & 1 deletion src/Relations/HasManyJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ protected function parentKeyToArray($parentKey)
protected function matchOneOrMany(array $models, Collection $results, $relation, $type)
{
if ($this->hasCompositeKey()) {
$this->matchWithCompositeKey($models, $results, $relation);
$this->matchWithCompositeKey($models, $results, $relation, 'many');
} else {
parent::matchOneOrMany($models, $results, $relation, $type);
}
Expand Down
72 changes: 72 additions & 0 deletions src/Relations/HasOneJson.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace Staudenmeir\EloquentJsonRelations\Relations;

use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\Concerns\SupportsDefaultModels;
use Illuminate\Database\Eloquent\Relations\HasOneOrMany;

class HasOneJson extends HasManyJson
{
use SupportsDefaultModels;

/** @inheritDoc */
public function getResults()
{
if (is_null($this->getParentKey())) {
return $this->getDefaultFor($this->parent);
}

return $this->first() ?: $this->getDefaultFor($this->parent);
}

/** @inheritDoc */
public function initRelation(array $models, $relation)
{
foreach ($models as $model) {
$model->setRelation($relation, $this->getDefaultFor($model));
}

return $models;
}

/** @inheritDoc */
public function match(array $models, Collection $results, $relation)
{
return $this->matchOne($models, $results, $relation);
}

/** @inheritDoc */
public function matchOne(array $models, Collection $results, $relation)
{
if ($this->hasCompositeKey()) {
$this->matchWithCompositeKey($models, $results, $relation, 'one');
} else {
HasOneOrMany::matchOneOrMany($models, $results, $relation, 'one');
}

if ($this->key) {
foreach ($models as $model) {
$model->setRelation(
$relation,
$this->hydratePivotRelation(
new Collection(
array_filter([$model->$relation])
),
$model,
fn (Model $model) => $model->{$this->getPathName()}
)->first()
);
}
}

return $models;
}

/** @inheritDoc */
public function newRelatedInstanceFor(Model $parent)
{
return $this->related->newInstance();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,10 @@ function (Builder $query) use ($key) {
* @param array $models
* @param \Illuminate\Database\Eloquent\Collection $results
* @param string $relation
* @param string $type
* @return array
*/
protected function matchWithCompositeKey(array $models, Collection $results, string $relation): array
protected function matchWithCompositeKey(array $models, Collection $results, string $relation, string $type): array
{
$dictionary = $this->buildDictionaryWithCompositeKey($results);

Expand All @@ -105,7 +106,7 @@ protected function matchWithCompositeKey(array $models, Collection $results, str
if (isset($dictionary[$key])) {
$model->setRelation(
$relation,
$this->getRelationValue($dictionary, $key, 'many')
$this->getRelationValue($dictionary, $key, $type)
);
}
}
Expand Down
6 changes: 4 additions & 2 deletions src/Relations/Traits/IsJsonRelation.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ trait IsJsonRelation
* @param \Illuminate\Database\Eloquent\Collection $models
* @param \Illuminate\Database\Eloquent\Model $parent
* @param callable $callback
* @return void
* @return \Illuminate\Database\Eloquent\Collection
*/
public function hydratePivotRelation(Collection $models, Model $parent, callable $callback)
public function hydratePivotRelation(Collection $models, Model $parent, callable $callback): Collection
{
foreach ($models as $i => $model) {
$clone = clone $model;
Expand All @@ -49,6 +49,8 @@ public function hydratePivotRelation(Collection $models, Model $parent, callable
$this->pivotRelation($clone, $parent, $callback)
);
}

return $models;
}

/**
Expand Down
85 changes: 85 additions & 0 deletions tests/CompositeKeys/HasOneJsonTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

namespace Tests\CompositeKeys;

use Illuminate\Database\Capsule\Manager as DB;
use Tests\Models\Task;
use Tests\TestCase;

class HasOneJsonTest extends TestCase
{
public function testLazyLoading()
{
$employee = Task::find(101)->employee;

$this->assertEquals(121, $employee->id);
}

public function testLazyLoadingWithObjects()
{
if (in_array($this->connection, ['sqlite', 'sqlsrv'])) {
$this->markTestSkipped();
}

$employee = Task::find(101)->employeeWithObjects;

$this->assertEquals(121, $employee->id);
$this->assertEquals(['work_stream' => ['active' => true]], $employee->pivot->getAttributes());
}

public function testEmptyLazyLoading()
{
DB::enableQueryLog();

$employee = (new Task())->employee;

$this->assertNull($employee);
$this->assertEmpty(DB::getQueryLog());
}

public function testEagerLoading()
{
$tasks = Task::with('employee')->get();

$this->assertEquals(121, $tasks[0]->employee->id);
$this->assertEquals(122, $tasks[1]->employee->id);
$this->assertNull($tasks[5]->employee);
}

public function testEagerLoadingWithObjects()
{
if (in_array($this->connection, ['sqlite', 'sqlsrv'])) {
$this->markTestSkipped();
}

$tasks = Task::with('employeeWithObjects')->get();

$this->assertEquals(121, $tasks[0]->employeeWithObjects->id);
$this->assertEquals(122, $tasks[1]->employeeWithObjects->id);
$this->assertNull($tasks[5]->employeeWithObjects);
$this->assertEquals(['work_stream' => ['active' => true]], $tasks[0]->employeeWithObjects->pivot->getAttributes());
}

public function testLazyEagerLoading()
{
$tasks = Task::all()->load('employee');

$this->assertEquals(121, $tasks[0]->employee->id);
$this->assertEquals(122, $tasks[1]->employee->id);
$this->assertNull($tasks[5]->employee);
}

public function testLazyEagerLoadingWithObjects()
{
if (in_array($this->connection, ['sqlite', 'sqlsrv'])) {
$this->markTestSkipped();
}

$tasks = Task::all()->load('employeeWithObjects');

$this->assertEquals(121, $tasks[0]->employeeWithObjects->id);
$this->assertEquals(122, $tasks[1]->employeeWithObjects->id);
$this->assertNull($tasks[5]->employeeWithObjects);
$this->assertEquals(['work_stream' => ['active' => true]], $tasks[0]->employeeWithObjects->pivot->getAttributes());
}
}
4 changes: 2 additions & 2 deletions tests/HasManyJsonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ public function testEmptyLazyLoading()
{
DB::enableQueryLog();

$roles = (new Role())->users;
$users = (new Role())->users;

$this->assertInstanceOf(Collection::class, $roles);
$this->assertInstanceOf(Collection::class, $users);
$this->assertEmpty(DB::getQueryLog());
}

Expand Down
Loading

0 comments on commit e9480d3

Please sign in to comment.