-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit ee08c45
Showing
13 changed files
with
557 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
Treenum: tree + enum in PHP | ||
=== | ||
|
||
Treenum is a lightweight library for defining and traversing items in an enum, that have a tree structure. | ||
|
||
``` | ||
composer require sam152/treenum | ||
``` | ||
|
||
## Defining a tree | ||
|
||
Trees may be defined either by identifying the parents or the children of any given item, whichever is easier for the consumer. | ||
|
||
Example of a tree identified by declaring children of any given item: | ||
|
||
```php | ||
enum Pet implements TreeEnum { | ||
use GetChildrenImplementation; | ||
|
||
case Dog; | ||
case Retriever; | ||
case Labrador; | ||
case Golden; | ||
case Terrier; | ||
case Bird; | ||
case Chicken; | ||
case Cat; | ||
|
||
public function getChildren(): array { | ||
return match ($this) { | ||
static::Dog => [ | ||
static::Retriever, | ||
static::Terrier, | ||
], | ||
static::Retriever => [ | ||
static::Labrador, | ||
static::Golden, | ||
], | ||
static::Bird => [ | ||
static::Chicken, | ||
], | ||
default => [], | ||
}; | ||
} | ||
} | ||
``` | ||
|
||
And the same tree identified by declaring the parent of any given item: | ||
|
||
```php | ||
enum Pet implements TreeEnum { | ||
use GetParentImplementation; | ||
|
||
case Dog; | ||
case Retriever; | ||
case Labrador; | ||
case Golden; | ||
case Terrier; | ||
case Bird; | ||
case Chicken; | ||
case Cat; | ||
|
||
public function getParent(): static|null { | ||
return match($this) { | ||
static::Labrador, static::Golden => static::Retriever, | ||
static::Retriever, static::Terrier => static::Dog, | ||
static::Chicken => static::Bird, | ||
default => null, | ||
}; | ||
} | ||
} | ||
|
||
``` | ||
|
||
## Public API | ||
|
||
The following methods are defined on `TreeEnum` and can be used to traverse the tree: | ||
|
||
```php | ||
public function getAncestors(): array; | ||
public function getDescendants(): array; | ||
public function getChildren(): array; | ||
public function getParent(): static | null; | ||
public function getDepth(): int; | ||
public static function rootCases(): array; | ||
public static function leafCases(): array; | ||
``` | ||
|
||
### Example usage: | ||
|
||
```php | ||
php > var_export(Pet::Dog->getChildren()); | ||
array ( | ||
Pet::Retriever, | ||
Pet::Terrier, | ||
) | ||
``` | ||
|
||
```php | ||
php > var_export(Pet::rootCases()); | ||
array ( | ||
Pet::Dog, | ||
Pet::Bird, | ||
Pet::Cat, | ||
) | ||
``` | ||
|
||
### Additional helpers | ||
|
||
```php | ||
php > print \Treenum\Internal\Utility::dumpTree(Pet::class); | ||
. | ||
├── Dog | ||
│ ├── Retriever | ||
│ │ ├── Labrador | ||
│ │ └── Golden | ||
│ └── Terrier | ||
├── Bird | ||
│ └── Chicken | ||
└── Cat | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "sam152/treenum", | ||
"description": "PHP trees based on enums.", | ||
"type": "library", | ||
"license": "MIT", | ||
"require": { | ||
"php": ">=8.1" | ||
}, | ||
"require-dev": { | ||
"phpunit/phpunit": "^10" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"Treenum\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"Tests\\Treenum": "tests/src/" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Treenum; | ||
|
||
use Treenum\Internal\BaseImplementation; | ||
use Treenum\Internal\Utility; | ||
|
||
trait GetChildrenImplementation { | ||
use BaseImplementation; | ||
|
||
public function getParent(): static | null { | ||
return Utility::find(static::cases(), fn (TreeEnum $item) => \in_array($this, $item->getChildren(), true)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Treenum; | ||
|
||
use Treenum\Internal\BaseImplementation; | ||
|
||
trait GetParentImplementation { | ||
use BaseImplementation; | ||
|
||
public function getChildren(): array { | ||
return array_values(array_filter(static::cases(), fn (TreeEnum $item) => $item->getParent() === $this)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Treenum\Internal; | ||
|
||
use Treenum\TreeEnum; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
trait BaseImplementation { | ||
public function getDepth(): int { | ||
return Utility::recurse( | ||
fn (callable $fn, TreeEnum $item, $depth = 1) => $item->getParent() ? $fn($fn, $item->getParent(), $depth + 1) : $depth, | ||
$this | ||
); | ||
} | ||
|
||
public static function rootCases(): array { | ||
return array_values(array_filter(static::cases(), fn (TreeEnum $item) => null === $item->getParent())); | ||
} | ||
|
||
public static function leafCases(): array { | ||
return array_values(array_filter(static::cases(), fn (TreeEnum $item) => empty($item->getChildren()))); | ||
} | ||
|
||
public function getAncestors(): array { | ||
return Utility::recurse( | ||
fn (callable $fn, TreeEnum $item, $parents = []) => $item->getParent() ? $fn($fn, $item->getParent(), array_merge($parents, [$item->getParent()])) : $parents, | ||
$this | ||
); | ||
} | ||
|
||
public function getDescendants(): array { | ||
return array_merge($this->getChildren(), ...array_map(fn (TreeEnum $item) => $item->getDescendants(), $this->getChildren())); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Treenum\Internal; | ||
|
||
use Treenum\TreeEnum; | ||
|
||
final class Utility { | ||
/** | ||
* @template T | ||
* | ||
* @param array<T> $array | ||
* | ||
* @return T|null | ||
*/ | ||
public static function find(array $array, callable $search): mixed { | ||
foreach ($array as $key => $value) { | ||
if ($search($value, $key)) { | ||
return $value; | ||
} | ||
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Allow arrow functions to recurse in a single expression, by passing the fn as the first argument. | ||
*/ | ||
public static function recurse(callable $arrowFn, ...$args) { | ||
return $arrowFn($arrowFn, ...$args); | ||
} | ||
|
||
/** | ||
* @param class-string<\Treenum\TreeEnum> $enum | ||
*/ | ||
public static function dumpTree(string $enum): string { | ||
return self::recurse(function (callable $printTree, array $nodes, $prefix = '') { | ||
return (empty($prefix) ? ".\n" : '') . array_reduce($nodes, function (string $tree, TreeEnum $node) use ($nodes, $prefix, $printTree) { | ||
$isLast = ($node === end($nodes)); | ||
$tree .= sprintf( | ||
"%s%s %s\n", | ||
$prefix, | ||
$isLast ? '└──' : '├──', | ||
$node->name, | ||
); | ||
$tree .= $printTree($printTree, $node->getChildren(), $isLast ? $prefix . ' ' : $prefix . '│ '); | ||
return $tree; | ||
}, ''); | ||
}, $enum::rootCases()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Treenum; | ||
|
||
interface TreeEnum extends \UnitEnum { | ||
/** | ||
* @return static[] | ||
*/ | ||
public function getAncestors(): array; | ||
|
||
/** | ||
* @return static[] | ||
*/ | ||
public function getDescendants(): array; | ||
|
||
/** | ||
* @return static[] | ||
*/ | ||
public function getChildren(): array; | ||
|
||
public function getParent(): static | null; | ||
|
||
public function getDepth(): int; | ||
|
||
/** | ||
* The root cases are those at a depth of 1, which may or may not have children. | ||
* | ||
* @return static[] | ||
*/ | ||
public static function rootCases(): array; | ||
|
||
/** | ||
* Leaf cases are the deepest items in each branch of the tree, those without any children. | ||
* | ||
* @return static[] | ||
*/ | ||
public static function leafCases(): array; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Treenum\Fixtures; | ||
|
||
use Treenum\GetChildrenImplementation; | ||
use Treenum\TreeEnum; | ||
|
||
enum Pet implements TreeEnum { | ||
use GetChildrenImplementation; | ||
|
||
case Dog; | ||
case Retriever; | ||
case Labrador; | ||
case Golden; | ||
case Terrier; | ||
case Bird; | ||
case Chicken; | ||
case Cat; | ||
|
||
public function getChildren(): array { | ||
return match ($this) { | ||
static::Dog => [ | ||
static::Retriever, | ||
static::Terrier, | ||
], | ||
static::Retriever => [ | ||
static::Labrador, | ||
static::Golden, | ||
], | ||
static::Bird => [ | ||
static::Chicken, | ||
], | ||
default => [], | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Treenum\Fixtures; | ||
|
||
use Treenum\GetParentImplementation; | ||
use Treenum\TreeEnum; | ||
|
||
enum PetWithParent implements TreeEnum { | ||
use GetParentImplementation; | ||
|
||
case Dog; | ||
case Retriever; | ||
case Labrador; | ||
case Golden; | ||
case Terrier; | ||
case Bird; | ||
case Chicken; | ||
case Cat; | ||
|
||
public function getParent(): static|null { | ||
return match($this) { | ||
static::Labrador, static::Golden => static::Retriever, | ||
static::Retriever, static::Terrier => static::Dog, | ||
static::Chicken => static::Bird, | ||
default => null, | ||
}; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Tests\Treenum; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Tests\Treenum\Fixtures\Pet; | ||
|
||
class GetChildrenImplementationTest extends TestCase { | ||
use TestCases; | ||
|
||
protected static function testWith(): string { | ||
return Pet::class; | ||
} | ||
} |
Oops, something went wrong.