Skip to content

Commit d866e27

Browse files
authored
Merge pull request #1 from aternosorg/get-child
add feature interface and implementation to get child from directory
2 parents 1ced66d + ef68e15 commit d866e27

File tree

8 files changed

+166
-2
lines changed

8 files changed

+166
-2
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313

1414
strategy:
1515
matrix:
16-
php-version: [ '8.3' ]
16+
php-version: [ '8.3', '8.4' ]
1717

1818
name: Run tests on PHP v${{ matrix.php-version }}
1919

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Copyright (c) 2024 Aternos GmbH
1+
Copyright (c) 2024-2025 Aternos GmbH
22

33
Permission is hereby granted, free of charge, to any person obtaining a copy
44
of this software and associated documentation files (the "Software"), to deal
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Aternos\IO\Interfaces\Features;
4+
5+
use Aternos\IO\Exception\IOException;
6+
use Aternos\IO\Interfaces\IOElementInterface;
7+
8+
interface GetChildInterface extends IOElementInterface
9+
{
10+
/**
11+
* Get a child element that has the given features
12+
*
13+
* The child element does not have to exist (yet)
14+
* The child element might support additional features
15+
*
16+
* @param string $name
17+
* @param class-string<IOElementInterface>[] $features
18+
* @return IOElementInterface
19+
* @throws IOException
20+
*/
21+
public function getChild(string $name, string ...$features): IOElementInterface;
22+
}

src/Interfaces/Types/DirectoryInterface.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Aternos\IO\Interfaces\Types;
44

5+
use Aternos\IO\Interfaces\Features\GetChildInterface;
56
use Aternos\IO\Interfaces\Features\CreateInterface;
67
use Aternos\IO\Interfaces\Features\DeleteInterface;
78
use Aternos\IO\Interfaces\Features\ExistsInterface;
@@ -17,6 +18,7 @@
1718
interface DirectoryInterface extends
1819
DeleteInterface,
1920
GetChildrenInterface,
21+
GetChildInterface,
2022
CreateInterface,
2123
ExistsInterface
2224
{

src/System/Directory/Directory.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,16 @@
1010
use Aternos\IO\Interfaces\Features\GetChildrenInterface;
1111
use Aternos\IO\Interfaces\Features\GetPathInterface;
1212
use Aternos\IO\Interfaces\Features\GetTargetInterface;
13+
use Aternos\IO\Interfaces\IOElementInterface;
1314
use Aternos\IO\Interfaces\Types\DirectoryInterface;
15+
use Aternos\IO\System\File\File;
1416
use Aternos\IO\System\FilesystemElement;
17+
use Aternos\IO\System\Link\DirectoryLink;
18+
use Aternos\IO\System\Link\FileLink;
1519
use Aternos\IO\System\Link\Link;
1620
use DirectoryIterator;
1721
use Generator;
22+
use InvalidArgumentException;
1823

1924
/**
2025
* Class Directory
@@ -55,6 +60,64 @@ public function getChildren(bool $allowOutsideLinks = false): Generator
5560
}
5661
}
5762

63+
/**
64+
* @inheritDoc
65+
* @return FilesystemElement
66+
*/
67+
public function getChild(string $name, string ...$features): FilesystemElement
68+
{
69+
/** @var class-string<FilesystemElement>[] $supportedChildClasses */
70+
$supportedChildClasses = [
71+
static::class,
72+
DirectoryLink::class,
73+
File::class,
74+
FileLink::class,
75+
Link::class
76+
];
77+
78+
/** @var class-string<FilesystemElement> $childClass */
79+
$childClass = $this->findInstanceOfAll($supportedChildClasses, $features);
80+
if (!$childClass) {
81+
throw new InvalidArgumentException("No supported child class found for features: " . implode(", ", $features));
82+
}
83+
return new $childClass($this->getPath() . DIRECTORY_SEPARATOR . $name);
84+
}
85+
86+
/**
87+
* Find a class that is an instance of all required classes
88+
*
89+
* @param class-string[] $availableClasses
90+
* @param class-string[] $requiredClasses
91+
* @return string|null
92+
*/
93+
protected function findInstanceOfAll(array $availableClasses, array $requiredClasses): ?string
94+
{
95+
foreach ($availableClasses as $availableClass) {
96+
if ($this->isInstanceOfAll($availableClass, $requiredClasses)) {
97+
return $availableClass;
98+
}
99+
}
100+
return null;
101+
}
102+
103+
/**
104+
* Check if a class is an instance of all required classes
105+
*
106+
* @param class-string $class
107+
* @param class-string[] $requiredClasses
108+
* @return bool
109+
*/
110+
protected function isInstanceOfAll(string $class, array $requiredClasses): bool
111+
{
112+
foreach ($requiredClasses as $requiredClass) {
113+
if (!is_a($class, $requiredClass, true)) {
114+
return false;
115+
}
116+
}
117+
return true;
118+
}
119+
120+
58121
/**
59122
* @inheritDoc
60123
* @throws GetTargetException

src/System/Link/DirectoryLink.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Aternos\IO\Exception\GetTargetException;
77
use Aternos\IO\Exception\SetTargetException;
88
use Aternos\IO\Interfaces\Features\GetPathInterface;
9+
use Aternos\IO\Interfaces\IOElementInterface;
910
use Aternos\IO\Interfaces\Types\DirectoryInterface;
1011
use Aternos\IO\Interfaces\Types\Link\DirectoryLinkInterface;
1112
use Aternos\IO\System\Directory\Directory;
@@ -83,4 +84,12 @@ public function getChildrenRecursive(bool $allowOutsideLinks = false, bool $foll
8384
{
8485
yield from $this->getTarget()->getChildrenRecursive($allowOutsideLinks, $followLinks, $currentDepth);
8586
}
87+
88+
/**
89+
* @inheritDoc
90+
*/
91+
public function getChild(string $name, string ...$features): IOElementInterface
92+
{
93+
return $this->getTarget()->getChild($name, ...$features);
94+
}
8695
}

test/Unit/System/Directory/DirectoryTest.php

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,23 @@
77
use Aternos\IO\Exception\GetTargetException;
88
use Aternos\IO\Exception\IOException;
99
use Aternos\IO\Exception\MissingPermissionsException;
10+
use Aternos\IO\Interfaces\Features\GetChildrenInterface;
11+
use Aternos\IO\Interfaces\Features\ReadInterface;
12+
use Aternos\IO\Interfaces\Features\WriteInterface;
1013
use Aternos\IO\Interfaces\IOElementInterface;
1114
use Aternos\IO\Interfaces\Types\DirectoryInterface;
1215
use Aternos\IO\Interfaces\Types\FileInterface;
1316
use Aternos\IO\Interfaces\Types\Link\LinkInterface;
1417
use Aternos\IO\System\Directory\Directory;
18+
use Aternos\IO\System\File\File;
19+
use Aternos\IO\System\FilesystemElement;
1520
use Aternos\IO\System\Link\DirectoryLink;
1621
use Aternos\IO\System\Link\FileLink;
1722
use Aternos\IO\System\Link\Link;
1823
use Aternos\IO\Test\Unit\System\FilesystemTestCase;
1924
use Generator;
25+
use InvalidArgumentException;
26+
use PHPUnit\Framework\Attributes\TestWith;
2027

2128
class DirectoryTest extends FilesystemTestCase
2229
{
@@ -120,6 +127,52 @@ public function testThrowsExceptionOnGetChildrenRecursiveWithMissingPermissions(
120127
chmod($path, 0777);
121128
}
122129

130+
/**
131+
* @param class-string[] $features
132+
* @param class-string $expected
133+
* @return void
134+
* @throws IOException
135+
*/
136+
#[TestWith([FileInterface::class])]
137+
#[TestWith([File::class])]
138+
#[TestWith([DirectoryInterface::class])]
139+
#[TestWith([Directory::class])]
140+
#[TestWith([LinkInterface::class])]
141+
#[TestWith([FileLink::class])]
142+
#[TestWith([DirectoryLink::class])]
143+
#[TestWith([Link::class])]
144+
#[TestWith([[WriteInterface::class, ReadInterface::class], File::class])]
145+
#[TestWith([[GetChildrenInterface::class], Directory::class])]
146+
public function testGetChild(array|string $features, ?string $expected = null): void
147+
{
148+
if (is_string($features)) {
149+
$features = [$features];
150+
}
151+
152+
if ($expected === null) {
153+
$expected = $features[0];
154+
}
155+
156+
$path = $this->getTmpPath();
157+
$element = $this->createElement($path);
158+
159+
$child = $element->getChild("test", ...$features);
160+
$this->assertInstanceOf($expected, $child);
161+
$this->assertEquals($path . "/test", $child->getPath());
162+
}
163+
164+
/**
165+
* @return void
166+
* @throws IOException
167+
*/
168+
public function testGetChildThrowsExceptionOnInvalidFeatureCombination(): void
169+
{
170+
$this->expectException(InvalidArgumentException::class);
171+
$this->expectExceptionMessage("No supported child class found for features: Aternos\IO\Interfaces\Features\WriteInterface, Aternos\IO\Interfaces\Features\GetChildrenInterface");
172+
$element = $this->createElement($this->getTmpPath());
173+
$element->getChild("test", WriteInterface::class, GetChildrenInterface::class);
174+
}
175+
123176
/**
124177
* @throws GetTargetException
125178
* @throws MissingPermissionsException

test/Unit/System/Link/DirectoryLinkTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,4 +261,19 @@ public function testGetChildrenRecursive(): void
261261
$this->assertPathHasTypeInArray($path . "/file1", FileInterface::class, $children);
262262
$this->assertPathHasTypeInArray($path . "/file2", FileInterface::class, $children);
263263
}
264+
265+
/**
266+
* @return void
267+
* @throws DeleteException
268+
* @throws IOException
269+
* @throws SetTargetException
270+
*/
271+
public function testGetChild(): void
272+
{
273+
mkdir($this->getTmpPath() . "/test-target");
274+
$element = $this->createElement($this->getTmpPath() . "/test");
275+
$element->setTarget(new Directory($this->getTmpPath() . "/test-target"));
276+
277+
$this->assertInstanceOf(File::class, $element->getChild("test", FileInterface::class));
278+
}
264279
}

0 commit comments

Comments
 (0)