Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/add ecs #3

Merged
merged 2 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@
},
"require-dev": {
"phpunit/phpunit": "*",
"infection/infection": "*"
"infection/infection": "*",
"netlogix/coding-guidelines-php": "^1.0"
},
"config": {
"allow-plugins": {
"infection/extension-installer": true
}
},
"scripts": {
"lint": "ecs check",
"lint-fix": "ecs check --fix",
"test": "phpunit",
"infection": "infection",
"test-coverage": "XDEBUG_MODE=coverage phpunit --coverage-html=coverage"
Expand Down
17 changes: 17 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

use Netlogix\CodingGuidelines\Php\DefaultPhp;
use Symplify\EasyCodingStandard\Config\ECSConfig;

return static function (ECSConfig $ecsConfig): void {
(new DefaultPhp())->configure($ecsConfig);

$ecsConfig->paths(
[
__DIR__ . '/src',
__DIR__ . '/tests',
]
);
};
8 changes: 4 additions & 4 deletions src/ResettableTaskPoolInterface.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

interface ResettableTaskPoolInterface extends TaskPoolInterface {

function reset(): self;

interface ResettableTaskPoolInterface extends TaskPoolInterface
{
public function reset(): self;
}
18 changes: 10 additions & 8 deletions src/Task.php
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

use JetBrains\PhpStorm\NoReturn;
use InvalidArgumentException;

class Task implements TaskInterface
{
Expand All @@ -15,13 +16,12 @@ class Task implements TaskInterface
/**
* @param array<string> $dependencies
*/
function __construct(
public function __construct(
private readonly string $name,
private readonly array $dependencies = []
)
{
if (array_filter($dependencies, fn($i) => !is_string($i))) {
throw new \InvalidArgumentException('Dependencies must be strings');
private readonly array $dependencies = []
) {
if (array_filter($dependencies, fn ($i) => !is_string($i))) {
throw new InvalidArgumentException('Dependencies must be strings');
}
}

Expand All @@ -40,12 +40,13 @@ public function getDependencies(): array
*/
public function checkDependencies(array $resolvedTasks): bool
{
return [] == array_diff($this->getDependencies(), $resolvedTasks);
return [] === array_diff($this->getDependencies(), $resolvedTasks);
}

public function resolve(): self
{
$this->resolved = true;

return $this;
}

Expand All @@ -57,6 +58,7 @@ public function isResolved(): bool
public function reset(): self
{
$this->resolved = false;

return $this;
}
}
27 changes: 15 additions & 12 deletions src/TaskGraph.php
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

use Traversable;
use Exception;
use Generator;
use InvalidArgumentException;
use IteratorAggregate;

class TaskGraph implements \IteratorAggregate
class TaskGraph implements IteratorAggregate
{
function __construct(
public function __construct(
private readonly TaskPoolInterface $tasks
)
{
) {
}

/**
* @return \Generator<\Generator<Task>>
* @throws \Exception
* @return Generator<Generator<Task>>
*/
public function getIterator(): \Generator
public function getIterator(): Generator
{
$tasksToResolve = [];

Expand All @@ -41,17 +43,18 @@ public function getIterator(): \Generator
!$this->hasResolvableTasks($tasksToResolve, $resolvedTasks) &&
!empty($tasksToResolve)
) {
throw new \Exception("Dependency cycle detected. Cannot resolve dependencies.");
throw new Exception("Dependency cycle detected. Cannot resolve dependencies.");
}
}
}

private function hasResolvableTasks(iterable $tasksToResolve, array $resolvedTasks): bool
{
return $this->getResolvableTasks($tasksToResolve, $resolvedTasks)->valid();
return $this->getResolvableTasks($tasksToResolve, $resolvedTasks)
->valid();
}

private function getResolvableTasks(iterable $tasksToResolve, array $resolvedTasks): \Generator
private function getResolvableTasks(iterable $tasksToResolve, array $resolvedTasks): Generator
{
foreach ($tasksToResolve as $taskName) {
$task = $this->tasks->getTask($taskName);
Expand All @@ -64,7 +67,7 @@ private function getResolvableTasks(iterable $tasksToResolve, array $resolvedTas
public function resetPool(): self
{
if (!($this->tasks instanceof ResettableTaskPoolInterface)) {
throw new \InvalidArgumentException('TaskPool is not resettable');
throw new InvalidArgumentException('TaskPool is not resettable');
}

$this->tasks->reset();
Expand Down
14 changes: 7 additions & 7 deletions src/TaskInterface.php
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

interface TaskInterface
{
function getName(): string;
public function getName(): string;

/**
* @return string[] Names of tasks that need to be resolved before this task can be resolved
*/
function getDependencies(): array;
public function getDependencies(): array;

/**
* @param string[] $resolvedTasks Names of tasks that are already resolved
* @return bool
*/
function checkDependencies(array $resolvedTasks): bool;
public function checkDependencies(array $resolvedTasks): bool;

function resolve(): self;
public function resolve(): self;

function reset(): self;
public function reset(): self;

function isResolved(): bool;
public function isResolved(): bool;
}
28 changes: 17 additions & 11 deletions src/TaskPool.php
Original file line number Diff line number Diff line change
@@ -1,47 +1,53 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

use ArrayIterator;
use InvalidArgumentException;
use Traversable;

class TaskPool implements ResettableTaskPoolInterface
{
private array $tasks = [];
function __construct(
iterable $tasks = []
)

public function __construct(iterable $tasks = [])
{
foreach ($tasks as $task) {
$this->addTask($task);
}
}

function addTask(TaskInterface $task): self
public function addTask(TaskInterface $task): self
{
if (isset($this->tasks[$task->getName()])) {
throw new \InvalidArgumentException('Task already exists');
throw new InvalidArgumentException('Task already exists');
}

$this->tasks[$task->getName()] = $task;

return $this;
}

function getTask(string $name): TaskInterface
public function getTask(string $name): TaskInterface
{
if (!isset($this->tasks[$name])) {
throw new \InvalidArgumentException(sprintf('Task "%s" does not exist', $name));
throw new InvalidArgumentException(sprintf('Task "%s" does not exist', $name));
}

return $this->tasks[$name];
}

function getIterator(): \Traversable
public function getIterator(): Traversable
{
return new \ArrayIterator($this->tasks);
return new ArrayIterator($this->tasks);
}

function reset(): self
public function reset(): self
{
array_map(fn($t) => $t->reset(), $this->tasks);
array_map(fn ($t) => $t->reset(), $this->tasks);

return $this;
}
}
12 changes: 8 additions & 4 deletions src/TaskPoolInterface.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver;

interface TaskPoolInterface extends \IteratorAggregate {
use IteratorAggregate;
use Traversable;

interface TaskPoolInterface extends IteratorAggregate
{
/**
* @return \Traversable<TaskInterface>
* @return Traversable<TaskInterface>
*/
function getIterator(): \Traversable;
public function getIterator(): Traversable;

function getTask(string $name): TaskInterface;
public function getTask(string $name): TaskInterface;
}
25 changes: 15 additions & 10 deletions tests/Unit/TaskGraphTest.php
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<?php

declare(strict_types=1);

namespace Netlogix\DependencyResolver\Tests\Unit;

use ArrayIterator;
use Exception;
use InvalidArgumentException;
use Netlogix\DependencyResolver\ResettableTaskPoolInterface;
use Netlogix\DependencyResolver\Task;
use Netlogix\DependencyResolver\TaskGraph;
Expand All @@ -14,7 +18,8 @@ class TaskGraphTest extends TestCase
public function testReset(): void
{
$taskPool = self::createMock(ResettableTaskPoolInterface::class);
$taskPool->expects($this->once())->method('reset');
$taskPool->expects($this->once())
->method('reset');

$graph = new TaskGraph($taskPool);
$graph->resetPool();
Expand All @@ -23,7 +28,7 @@ public function testReset(): void
public function testResetException(): void
{
$taskPool = self::createMock(TaskPoolInterface::class);
self::expectException(\InvalidArgumentException::class);
self::expectException(InvalidArgumentException::class);
$graph = new TaskGraph($taskPool);
$graph->resetPool();
}
Expand All @@ -33,13 +38,15 @@ public function testResolveDependencies(): void
$tasks = [
'TaskA' => new Task('TaskA'),
'TaskB' => new Task('TaskB', ['TaskC']),
'TaskC' => new Task('TaskC', ['TaskA'])
'TaskC' => new Task('TaskC', ['TaskA']),
];

$taskPool = self::createMock(TaskPoolInterface::class);

$taskPool->method('getIterator')->willReturn(new \ArrayIterator($tasks));
$taskPool->method('getTask')->willReturnCallback(fn($name) => $tasks[$name]);
$taskPool->method('getIterator')
->willReturn(new ArrayIterator($tasks));
$taskPool->method('getTask')
->willReturnCallback(fn ($name) => $tasks[$name]);

$graph = new TaskGraph($taskPool);

Expand All @@ -56,14 +63,12 @@ public function testResolveDependencies(): void

public function testResolveCycle(): void
{
$this->expectException(\Exception::class);
$this->expectException(Exception::class);

$taskPool = self::createMock(TaskPoolInterface::class);

$taskPool->method('getIterator')->willReturn(new \ArrayIterator([
new Task('TaskB', ['TaskA']),
new Task('TaskA', ['TaskB'])
]));
$taskPool->method('getIterator')
->willReturn(new ArrayIterator([new Task('TaskB', ['TaskA']), new Task('TaskA', ['TaskB'])]));

foreach ((new TaskGraph($taskPool))->getIterator() as $tasks) {
foreach ($tasks as $task) {
Expand Down
Loading
Loading