Allow external path implementations

master
Anton Smirnov 11 months ago
parent de8ad536d6
commit 93df980c8a
  1. 3
      composer.json
  2. 37
      src/AbstractPath.php
  3. 26
      src/Helpers/DataTypeHelper.php
  4. 2
      src/PathInterface.php
  5. 7
      src/RelativePath.php
  6. 10
      src/RelativePathInterface.php
  7. 62
      tests/RelativePathTest.php

@ -28,7 +28,8 @@
"require": {
"php": "^7.4 | ^8.0",
"nikic/iter": "^2.0",
"symfony/polyfill-php80": "^1.17"
"symfony/polyfill-php80": "^1.22",
"symfony/polyfill-php81": "^1.22"
},
"require-dev": {
"phpunit/phpunit": ">= 7 < 10",

@ -4,21 +4,26 @@ declare(strict_types=1);
namespace Arokettu\Path;
use Arokettu\Path\Helpers\DataTypeHelper;
/**
* @internal
*/
abstract class AbstractPath implements PathInterface
{
protected string $prefix;
protected \SplDoublyLinkedList $components;
abstract protected function parsePath(string $path): \SplDoublyLinkedList;
abstract protected function parsePath(string $path): void;
public function __construct(string $path)
{
$this->components = $this->parsePath($path);
$this->parsePath($path);
}
/**
* @param RelativePath|string $path
* @static
* @param RelativePathInterface|string $path
* @return static
*/
public function resolveRelative($path, bool $strict = false): self
{
@ -37,20 +42,31 @@ abstract class AbstractPath implements PathInterface
/**
* @return static
*/
protected function doResolveRelative(RelativePath $path, bool $strict): self
protected function doResolveRelative(RelativePathInterface $path, bool $strict): self
{
$relativeComponents = $path->components;
if ($path instanceof RelativePath) {
// optimize
$relativeComponents = $path->components;
} else {
// allow external implementations
$relativeComponents = $path->getComponents();
if (!array_is_list($relativeComponents)) {
throw new \InvalidArgumentException(
'Poor RelativePathInterface implementation: getComponents() must return a list'
);
}
}
if ($path->isRoot()) {
$newPath = clone $this;
$newPath->components = clone $relativeComponents;
$newPath->components = DataTypeHelper::iterableToNewListInstance($relativeComponents);
return $newPath;
}
$components = clone $this->components;
$numComponents = $relativeComponents->count();
$numComponents = \count($relativeComponents);
for ($i = 0; $i < $numComponents; $i++) {
if ($relativeComponents[$i] === '.') {
continue;
@ -120,6 +136,11 @@ abstract class AbstractPath implements PathInterface
return $components;
}
public function toString(): string
{
return $this->prefix . \iter\join('/', $this->components);
}
public function __toString(): string
{
return $this->toString();

@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path\Helpers;
/**
* @internal
*/
final class DataTypeHelper
{
public static function iterableToNewListInstance(iterable $iterable): \SplDoublyLinkedList
{
if ($iterable instanceof \SplDoublyLinkedList) {
return clone $iterable;
}
$list = new \SplDoublyLinkedList();
foreach ($iterable as $value) {
$list->push($value);
}
return $list;
}
}

@ -10,7 +10,7 @@ interface PathInterface extends \Stringable
public function toString(): string;
/**
* @param RelativePath|string $path
* @param RelativePathInterface|string $path
* @return static
*/
public function resolveRelative($path, bool $strict = false): self;

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Arokettu\Path;
final class RelativePath extends AbstractPath
final class RelativePath extends AbstractPath implements RelativePathInterface
{
private bool $windows;
@ -25,7 +25,7 @@ final class RelativePath extends AbstractPath
return new self($path, true);
}
protected function parsePath(string $path): \SplDoublyLinkedList
protected function parsePath(string $path): void
{
$components = explode('/', $path);
@ -45,7 +45,8 @@ final class RelativePath extends AbstractPath
$parsedComponents->unshift('.');
}
return $parsedComponents;
$this->prefix = '';
$this->components = $parsedComponents;
}
protected function buildRelative(string $path): RelativePath

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Arokettu\Path;
interface RelativePathInterface extends PathInterface
{
public function isRoot(): bool;
}

@ -4,7 +4,9 @@ declare(strict_types=1);
namespace Arokettu\Path\Tests;
use Arokettu\Path\PathInterface;
use Arokettu\Path\RelativePath;
use Arokettu\Path\RelativePathInterface;
use PHPUnit\Framework\TestCase;
class RelativePathTest extends TestCase
@ -145,4 +147,64 @@ class RelativePathTest extends TestCase
$p->resolveRelative($rp, true);
}
public function testExternalRelativeImplementations(): void
{
$p = new RelativePath('i/am/test/path');
$rp1 = new class implements RelativePathInterface {
public function __toString(): string {
return '';
}
public function getComponents(): array
{
return explode('/', '../../i/am/test/path');
}
public function toString(): string
{
throw new \BadMethodCallException('Irrelevant');
}
public function resolveRelative($path, bool $strict = false): PathInterface
{
throw new \BadMethodCallException('Irrelevant');
}
public function isRoot(): bool
{
return false;
}
};
$rp2 = new class implements RelativePathInterface {
public function __toString(): string {
return '';
}
public function getComponents(): array
{
return explode('/', 'i/am/test/path');
}
public function toString(): string
{
throw new \BadMethodCallException('Irrelevant');
}
public function resolveRelative($path, bool $strict = false): PathInterface
{
throw new \BadMethodCallException('Irrelevant');
}
public function isRoot(): bool
{
return true;
}
};
self::assertEquals('i/am/i/am/test/path', $p->resolveRelative($rp1)->toString());
self::assertEquals('/i/am/test/path', $p->resolveRelative($rp2)->toString());
}
}

Loading…
Cancel
Save