Skip to content

Commit

Permalink
Merge pull request #474 from DirectoryTree/feature-chunk-relations-re…
Browse files Browse the repository at this point in the history
…cursively

Add ability to chunk relations recursively
  • Loading branch information
stevebauman authored Jun 16, 2022
2 parents 5dbfb05 + d8283ee commit a246840
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 7 deletions.
12 changes: 12 additions & 0 deletions src/Models/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@

class Collection extends QueryCollection
{
/**
* Get a collection of the model's distinguished names.
*
* @return static
*/
public function modelDns()
{
return $this->map(function (Model $model) {
return $model->getDn();
});
}

/**
* Determine if the collection contains all of the given models, or any models.
*
Expand Down
60 changes: 56 additions & 4 deletions src/Models/Relations/HasMany.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
use Closure;
use LdapRecord\DetectsErrors;
use LdapRecord\LdapRecordException;
use LdapRecord\Models\Collection;
use LdapRecord\Models\Model;
use LdapRecord\Models\ModelNotFoundException;
use LdapRecord\Query\Collection;

class HasMany extends OneToMany
{
Expand Down Expand Up @@ -108,18 +108,70 @@ protected function paginateOnceUsing($pageSize)
return $result;
}

/**
* Execute a callback over each result while chunking.
*
* @param Closure $callback
* @param int $pageSize
*
* @return bool
*/
public function each(Closure $callback, $pageSize = 1000)
{
$this->chunk($pageSize, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
}
}
});
}

/**
* Chunk the relation results using the given callback.
*
* @param int $pageSize
* @param Closure $callback
* @param array $loaded
*
* @return void
* @return bool
*/
public function chunk($pageSize, Closure $callback)
{
$this->getRelationQuery()->chunk($pageSize, function ($entries) use ($callback) {
$callback($this->transformResults($entries));
return $this->chunkRelation($pageSize, $callback);
}

/**
* Execute the callback over chunks of relation results.
*
* @param int $pageSize
* @param Closure $callback
* @param array $loaded
*
* @return bool
*/
protected function chunkRelation($pageSize, Closure $callback, $loaded = [])
{
return $this->getRelationQuery()->chunk($pageSize, function (Collection $results) use ($pageSize, $callback, $loaded) {
$models = $this->transformResults($results)->when($this->recursive, function (Collection $models) use ($loaded) {
return $models->reject(function (Model $model) use ($loaded) {
return in_array($model->getDn(), $loaded);
});
});

if ($callback($models) === false) {
return false;
}

$models->when($this->recursive, function (Collection $models) use ($pageSize, $callback, $loaded) {
$models->each(function (Model $model) use ($pageSize, $callback, $loaded) {
if (method_exists($model, $this->relationName)) {
$loaded[] = $model->getDn();

return $model->{$this->relationName}()->recursive()->chunkRelation($pageSize, $callback, $loaded);
}
});
});
});
}

Expand Down
6 changes: 3 additions & 3 deletions src/Query/Builder.php
Original file line number Diff line number Diff line change
Expand Up @@ -513,13 +513,13 @@ protected function runPaginate($filter, $perPage, $isCritical)
* Execute a callback over each item while chunking.
*
* @param Closure $callback
* @param int $count
* @param int $pageSize
*
* @return bool
*/
public function each(Closure $callback, $count = 1000)
public function each(Closure $callback, $pageSize = 1000)
{
return $this->chunk($count, function ($results) use ($callback) {
return $this->chunk($pageSize, function ($results) use ($callback) {
foreach ($results as $key => $value) {
if ($callback($value, $key) === false) {
return false;
Expand Down
60 changes: 60 additions & 0 deletions tests/Unit/Models/ModelHasManyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

namespace LdapRecord\Unit\Tests\Models;

use Closure;
use LdapRecord\Connection;
use LdapRecord\Container;
use LdapRecord\Models\Attributes\EscapedValue;
use LdapRecord\Models\Collection as ModelsCollection;
use LdapRecord\Models\Entry;
use LdapRecord\Models\Model;
use LdapRecord\Models\Relations\HasMany;
Expand Down Expand Up @@ -75,6 +77,64 @@ public function test_get_recursive_results()
$this->assertCount(2, $results);
}

public function test_chunk()
{
$relation = $this->getRelation();

$parent = $relation->getParent();
$parent->shouldReceive('getDn')->andReturn('foo');

$query = $relation->getQuery();
$query->shouldReceive('escape')->once()->with('foo')->andReturn(new EscapedValue('foo'));
$query->shouldReceive('getSelects')->once()->withNoArgs()->andReturn(['*']);
$query->shouldReceive('whereRaw')->once()->with('member', '=', EscapedValue::class)->andReturnSelf();
$query->shouldReceive('chunk')->once()->with(1000, m::on(function ($callback) {
$related = m::mock(ModelHasManyStub::class);

$related->shouldReceive('getDn')->andReturn('bar');
$related->shouldReceive('convert')->once()->andReturnSelf();
$related->shouldReceive('getObjectClasses')->once()->andReturn([]);

$callback(new ModelsCollection([$related]));

return true;
}));

$relation->chunk(1000, function () {
});
}

public function test_recursive_chunk()
{
$relation = $this->getRelation();

$parent = $relation->getParent();
$parent->shouldReceive('getDn')->andReturn('foo');

$query = $relation->getQuery();
$query->shouldReceive('escape')->once()->with('foo')->andReturn(new EscapedValue('foo'));
$query->shouldReceive('getSelects')->once()->withNoArgs()->andReturn(['*']);
$query->shouldReceive('whereRaw')->once()->with('member', '=', EscapedValue::class)->andReturnSelf();
$query->shouldReceive('chunk')->once()->with(1000, m::on(function ($callback) {
$related = m::mock(ModelHasManyStub::class);

$related->shouldReceive('getDn')->andReturn('bar');
$related->shouldReceive('convert')->once()->andReturnSelf();
$related->shouldReceive('getObjectClasses')->once()->andReturn([]);

$related->shouldReceive('relation')->once()->andReturnSelf();
$related->shouldReceive('recursive')->once()->andReturnSelf();
$related->shouldReceive('chunkRelation')->once()->with(1000, Closure::class, ['bar']);

$callback(new ModelsCollection([$related]));

return true;
}));

$relation->recursive()->chunk(1000, function () {
});
}

public function test_page_size_can_be_set()
{
$relation = $this->getRelation();
Expand Down

0 comments on commit a246840

Please sign in to comment.