Skip to content
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:

strategy:
matrix:
php-version: [ '8.3' ]
php-version: [ '8.3', '8.4' ]

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

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright (c) 2024 Aternos GmbH
Copyright (c) 2024-2025 Aternos GmbH

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
22 changes: 22 additions & 0 deletions src/Interfaces/Features/GetChildInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace Aternos\IO\Interfaces\Features;

use Aternos\IO\Exception\IOException;
use Aternos\IO\Interfaces\IOElementInterface;

interface GetChildInterface extends IOElementInterface
{
/**
* Get a child element that has the given features
*
* The child element does not have to exist (yet)
* The child element might support additional features
*
* @param string $name
* @param class-string<IOElementInterface>[] $features
* @return IOElementInterface
* @throws IOException
*/
public function getChild(string $name, string ...$features): IOElementInterface;
}
2 changes: 2 additions & 0 deletions src/Interfaces/Types/DirectoryInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Aternos\IO\Interfaces\Types;

use Aternos\IO\Interfaces\Features\GetChildInterface;
use Aternos\IO\Interfaces\Features\CreateInterface;
use Aternos\IO\Interfaces\Features\DeleteInterface;
use Aternos\IO\Interfaces\Features\ExistsInterface;
Expand All @@ -17,6 +18,7 @@
interface DirectoryInterface extends
DeleteInterface,
GetChildrenInterface,
GetChildInterface,
CreateInterface,
ExistsInterface
{
Expand Down
63 changes: 63 additions & 0 deletions src/System/Directory/Directory.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@
use Aternos\IO\Interfaces\Features\GetChildrenInterface;
use Aternos\IO\Interfaces\Features\GetPathInterface;
use Aternos\IO\Interfaces\Features\GetTargetInterface;
use Aternos\IO\Interfaces\IOElementInterface;
use Aternos\IO\Interfaces\Types\DirectoryInterface;
use Aternos\IO\System\File\File;
use Aternos\IO\System\FilesystemElement;
use Aternos\IO\System\Link\DirectoryLink;
use Aternos\IO\System\Link\FileLink;
use Aternos\IO\System\Link\Link;
use DirectoryIterator;
use Generator;
use InvalidArgumentException;

/**
* Class Directory
Expand Down Expand Up @@ -55,6 +60,64 @@ public function getChildren(bool $allowOutsideLinks = false): Generator
}
}

/**
* @inheritDoc
* @return FilesystemElement
*/
public function getChild(string $name, string ...$features): FilesystemElement
{
/** @var class-string<FilesystemElement>[] $supportedChildClasses */
$supportedChildClasses = [
static::class,
DirectoryLink::class,
File::class,
FileLink::class,
Link::class
];

/** @var class-string<FilesystemElement> $childClass */
$childClass = $this->findInstanceOfAll($supportedChildClasses, $features);
if (!$childClass) {
throw new InvalidArgumentException("No supported child class found for features: " . implode(", ", $features));
}
return new $childClass($this->getPath() . DIRECTORY_SEPARATOR . $name);
}

/**
* Find a class that is an instance of all required classes
*
* @param class-string[] $availableClasses
* @param class-string[] $requiredClasses
* @return string|null
*/
protected function findInstanceOfAll(array $availableClasses, array $requiredClasses): ?string
{
foreach ($availableClasses as $availableClass) {
if ($this->isInstanceOfAll($availableClass, $requiredClasses)) {
return $availableClass;
}
}
return null;
}

/**
* Check if a class is an instance of all required classes
*
* @param class-string $class
* @param class-string[] $requiredClasses
* @return bool
*/
protected function isInstanceOfAll(string $class, array $requiredClasses): bool
{
foreach ($requiredClasses as $requiredClass) {
if (!is_a($class, $requiredClass, true)) {
return false;
}
}
return true;
}


/**
* @inheritDoc
* @throws GetTargetException
Expand Down
9 changes: 9 additions & 0 deletions src/System/Link/DirectoryLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Aternos\IO\Exception\GetTargetException;
use Aternos\IO\Exception\SetTargetException;
use Aternos\IO\Interfaces\Features\GetPathInterface;
use Aternos\IO\Interfaces\IOElementInterface;
use Aternos\IO\Interfaces\Types\DirectoryInterface;
use Aternos\IO\Interfaces\Types\Link\DirectoryLinkInterface;
use Aternos\IO\System\Directory\Directory;
Expand Down Expand Up @@ -83,4 +84,12 @@ public function getChildrenRecursive(bool $allowOutsideLinks = false, bool $foll
{
yield from $this->getTarget()->getChildrenRecursive($allowOutsideLinks, $followLinks, $currentDepth);
}

/**
* @inheritDoc
*/
public function getChild(string $name, string ...$features): IOElementInterface
{
return $this->getTarget()->getChild($name, ...$features);
}
}
53 changes: 53 additions & 0 deletions test/Unit/System/Directory/DirectoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,23 @@
use Aternos\IO\Exception\GetTargetException;
use Aternos\IO\Exception\IOException;
use Aternos\IO\Exception\MissingPermissionsException;
use Aternos\IO\Interfaces\Features\GetChildrenInterface;
use Aternos\IO\Interfaces\Features\ReadInterface;
use Aternos\IO\Interfaces\Features\WriteInterface;
use Aternos\IO\Interfaces\IOElementInterface;
use Aternos\IO\Interfaces\Types\DirectoryInterface;
use Aternos\IO\Interfaces\Types\FileInterface;
use Aternos\IO\Interfaces\Types\Link\LinkInterface;
use Aternos\IO\System\Directory\Directory;
use Aternos\IO\System\File\File;
use Aternos\IO\System\FilesystemElement;
use Aternos\IO\System\Link\DirectoryLink;
use Aternos\IO\System\Link\FileLink;
use Aternos\IO\System\Link\Link;
use Aternos\IO\Test\Unit\System\FilesystemTestCase;
use Generator;
use InvalidArgumentException;
use PHPUnit\Framework\Attributes\TestWith;

class DirectoryTest extends FilesystemTestCase
{
Expand Down Expand Up @@ -120,6 +127,52 @@ public function testThrowsExceptionOnGetChildrenRecursiveWithMissingPermissions(
chmod($path, 0777);
}

/**
* @param class-string[] $features
* @param class-string $expected
* @return void
* @throws IOException
*/
#[TestWith([FileInterface::class])]
#[TestWith([File::class])]
#[TestWith([DirectoryInterface::class])]
#[TestWith([Directory::class])]
#[TestWith([LinkInterface::class])]
#[TestWith([FileLink::class])]
#[TestWith([DirectoryLink::class])]
#[TestWith([Link::class])]
#[TestWith([[WriteInterface::class, ReadInterface::class], File::class])]
#[TestWith([[GetChildrenInterface::class], Directory::class])]
public function testGetChild(array|string $features, ?string $expected = null): void
{
if (is_string($features)) {
$features = [$features];
}

if ($expected === null) {
$expected = $features[0];
}

$path = $this->getTmpPath();
$element = $this->createElement($path);

$child = $element->getChild("test", ...$features);
$this->assertInstanceOf($expected, $child);
$this->assertEquals($path . "/test", $child->getPath());
}

/**
* @return void
* @throws IOException
*/
public function testGetChildThrowsExceptionOnInvalidFeatureCombination(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("No supported child class found for features: Aternos\IO\Interfaces\Features\WriteInterface, Aternos\IO\Interfaces\Features\GetChildrenInterface");
$element = $this->createElement($this->getTmpPath());
$element->getChild("test", WriteInterface::class, GetChildrenInterface::class);
}

/**
* @throws GetTargetException
* @throws MissingPermissionsException
Expand Down
15 changes: 15 additions & 0 deletions test/Unit/System/Link/DirectoryLinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,19 @@ public function testGetChildrenRecursive(): void
$this->assertPathHasTypeInArray($path . "/file1", FileInterface::class, $children);
$this->assertPathHasTypeInArray($path . "/file2", FileInterface::class, $children);
}

/**
* @return void
* @throws DeleteException
* @throws IOException
* @throws SetTargetException
*/
public function testGetChild(): void
{
mkdir($this->getTmpPath() . "/test-target");
$element = $this->createElement($this->getTmpPath() . "/test");
$element->setTarget(new Directory($this->getTmpPath() . "/test-target"));

$this->assertInstanceOf(File::class, $element->getChild("test", FileInterface::class));
}
}
Loading