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
3 changes: 0 additions & 3 deletions .github/workflows/code-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ jobs:
strategy:
matrix:
php:
- '7.3'
- '7.4'
- '8.0'
- '8.1'
- '8.2'
- '8.3'
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
/.cache/*
/vendor
/composer.lock
/.phpunit.result.cache
2 changes: 1 addition & 1 deletion .runConfigurations/All unit tests.run.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="All unit tests" type="PHPUnitRunConfigurationType" factoryName="PHPUnit">
<TestRunner configuration_file="$PROJECT_DIR$/phpunit.xml.dist" directory="$PROJECT_DIR$/tests" scope="XML" options="--verbose" />
<TestRunner configuration_file="$PROJECT_DIR$/phpunit.xml.dist" directory="$PROJECT_DIR$/tests" scope="XML" />
<method v="2" />
</configuration>
</component>
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Removed
- Removed support for PHP versions <= v8.0 as they are no longer
[actively supported](https://php.net/supported-versions.php) by the PHP project.

## [2.1.0] - 2021-09-11
### Added
Expand Down
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@
}
},
"require": {
"php": "^7.3 || ^8",
"php": "^8.1",
"ext-mbstring": "*"
},
"require-dev": {
"phpunit/phpunit": "^9",
"symplify/easy-coding-standard": "^12"
"phpunit/phpunit": "^10",
"symplify/easy-coding-standard": "^13"
},
"autoload-dev": {
"psr-4": {
Expand Down
19 changes: 19 additions & 0 deletions ecs.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
<?php

/**
* Phlib ECS config v2025-02
*/

declare(strict_types=1);

use Symplify\EasyCodingStandard\Config\ECSConfig;
Expand Down Expand Up @@ -48,4 +52,19 @@
[
'closure_fn_spacing' => 'none',
],
)

/*
* Rule from PER Coding Style 2.6:
* "If the list is split across multiple lines, then the last item MUST have a trailing comma."
*/
->withConfiguredRule(
\PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer::class,
[
'elements' => [
\PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer::ELEMENTS_ARGUMENTS,
\PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer::ELEMENTS_ARRAYS,
\PhpCsFixer\Fixer\ControlStructure\TrailingCommaInMultilineFixer::ELEMENTS_PARAMETERS,
],
],
);
7 changes: 4 additions & 3 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/autoload.php"
beStrictAboutOutputDuringTests="true"
cacheDirectory=".cache/phpunit"
colors="true"
>
<testsuites>
<testsuite name="Phlib Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
</source>
</phpunit>
32 changes: 13 additions & 19 deletions src/Path.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* @package Phlib\Path
* @license LGPL-3.0
*/
class Path implements \ArrayAccess, \Countable, \IteratorAggregate
class Path implements \ArrayAccess, \Countable, \IteratorAggregate, \Stringable
{
/**
* Constants for info keys, used for asking for info with the info method
Expand All @@ -25,19 +25,12 @@ class Path implements \ArrayAccess, \Countable, \IteratorAggregate

public const INFO_ALL = 15;

/**
* The separator to use for delimiting parts of the path
*
* @var string
*/
private $directorySeparator;

/**
* The parts of the path
*
* @var string[]
*/
private $parts;
private array $parts;

/**
* Array containing the info properties of the path
Expand All @@ -51,9 +44,8 @@ class Path implements \ArrayAccess, \Countable, \IteratorAggregate
* Mapping of info keys to the key names used in the output
*
* @see Path::info
* @var array
*/
private $infoKeys = [
private array $infoKeys = [
self::INFO_DIRNAME => 'dirname',
self::INFO_BASENAME => 'basename',
self::INFO_EXTENSION => 'extension',
Expand All @@ -72,7 +64,7 @@ public static function escapeName(string $name, string $directorySeparator = DIR
[
'\\' => '\\\\', // escape the escape character
$directorySeparator => "\\{$directorySeparator}", // escape the separator
]
],
);
}

Expand All @@ -89,7 +81,7 @@ public static function unescapeName(string $name, string $directorySeparator = D
[
'\\\\' => '\\',
"\\{$directorySeparator}" => "{$directorySeparator}",
]
],
);
}

Expand Down Expand Up @@ -131,11 +123,11 @@ private static function splitPath(string $path, string $directorySeparator): arr
return $out;
}

public function __construct(array $parts, string $directorySeparator = DIRECTORY_SEPARATOR)
{
public function __construct(
array $parts,
private readonly string $directorySeparator = DIRECTORY_SEPARATOR,
) {
$parts = $this->trimEmptyParts($parts);

$this->directorySeparator = $directorySeparator;
$this->parts = $parts;
}

Expand Down Expand Up @@ -188,6 +180,7 @@ public function toString(): string
* @see pathinfo
* @return array|string
*/
#[\ReturnTypeWillChange]
public function info(int $options = self::INFO_ALL)
{
$this->parseInfo();
Expand Down Expand Up @@ -247,7 +240,7 @@ private function parseInfo(): void
*/
private function escapeParts(array $parts): array
{
return array_map(function ($name) {
return array_map(function (string $name): string {
return self::escapeName($name);
}, $parts);
}
Expand All @@ -266,7 +259,7 @@ private function trimEmptyParts(array $parts): array
return [];
}

return array_filter($parts, function ($part, $index) use ($emptyLeading) {
return array_filter($parts, function ($part, $index) use ($emptyLeading): bool {
if ($index === 0) {
// never trim first element to keep leading separators (e.g. '/foo')
return true;
Expand All @@ -289,6 +282,7 @@ public function offsetExists($offset): bool
return array_key_exists($offset, $this->parts);
}

#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->parts[$offset];
Expand Down
69 changes: 32 additions & 37 deletions tests/PathTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Phlib;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -16,14 +17,12 @@ public function testCreateFromParts(): void
{
$parts = ['foo', 'bar/baz', 'taz'];
$phlibPath = new Path($parts);
$this->assertEquals('foo/bar\\/baz/taz', $phlibPath->toString());
static::assertSame('foo/bar\\/baz/taz', $phlibPath->toString());
// Also test magic `__toString()`
$this->assertEquals('foo/bar\\/baz/taz', (string)$phlibPath);
static::assertSame('foo/bar\\/baz/taz', (string)$phlibPath);
}

/**
* @dataProvider matchesPathInfoProvider
*/
#[DataProvider('matchesPathInfoProvider')]
public function testMatchesPathInfo(string $path): void
{
$phpPathInfo = pathinfo($path);
Expand All @@ -32,11 +31,11 @@ public function testMatchesPathInfo(string $path): void
$this->assertPathInfoEquals(
$phpPathInfo,
$phlibPathInfo,
"Failed asserting path info matched for path '{$path}'"
"Failed asserting path info matched for path '{$path}'",
);
}

public function matchesPathInfoProvider(): array
public static function matchesPathInfoProvider(): array
{
return [
['foo'],
Expand Down Expand Up @@ -71,24 +70,22 @@ public function testEscapingPathSeparators(): void
'boz/.woz',
];

$parts = array_map(function ($part) {
$parts = array_map(function (string $part): string {
return Path::escapeName($part);
}, $parts);

$phlibPath = Path::fromString(implode('/', $parts));
$this->assertEquals(3, count($phlibPath));
static::assertSame(3, count($phlibPath));
}

/**
* @dataProvider ignoresEscapedSeparatorsProvider
*/
#[DataProvider('ignoresEscapedSeparatorsProvider')]
public function testIgnoresEscapedSeparators(string $path, int $expectedCount): void
{
$phlibPath = Path::fromString($path);
$this->assertEquals($expectedCount, count($phlibPath), "Failed asserting path count for path '{$path}'");
static::assertSame($expectedCount, count($phlibPath), "Failed asserting path count for path '{$path}'");
}

public function ignoresEscapedSeparatorsProvider(): array
public static function ignoresEscapedSeparatorsProvider(): array
{
return [
['foo\\/bar', 1],
Expand All @@ -105,10 +102,10 @@ public function testPathInfoOption(): void
{
$path = 'foo/bar/baz.taz';
$phlibPath = Path::fromString($path);
$this->assertEquals(pathinfo($path, PATHINFO_BASENAME), $phlibPath->info(Path::INFO_BASENAME));
$this->assertEquals(pathinfo($path, PATHINFO_DIRNAME), $phlibPath->info(Path::INFO_DIRNAME));
$this->assertEquals(pathinfo($path, PATHINFO_FILENAME), $phlibPath->info(Path::INFO_FILENAME));
$this->assertEquals(pathinfo($path, PATHINFO_EXTENSION), $phlibPath->info(Path::INFO_EXTENSION));
static::assertSame(pathinfo($path, PATHINFO_BASENAME), $phlibPath->info(Path::INFO_BASENAME));
static::assertSame(pathinfo($path, PATHINFO_DIRNAME), $phlibPath->info(Path::INFO_DIRNAME));
static::assertSame(pathinfo($path, PATHINFO_FILENAME), $phlibPath->info(Path::INFO_FILENAME));
static::assertSame(pathinfo($path, PATHINFO_EXTENSION), $phlibPath->info(Path::INFO_EXTENSION));
}

public function testPathInfoMultipleOptions(): void
Expand Down Expand Up @@ -136,21 +133,21 @@ public function testPathInfoMultipleOptionsNotAllPresent(): void
];
$actual = $phlibPath->info(Path::INFO_FILENAME | Path::INFO_EXTENSION);

$this->assertIsArray($actual);
static::assertIsArray($actual);
$this->assertPathInfoEquals($expected, $actual);
}

public function testCountable(): void
{
$phlibPath = Path::fromString('foo/bar/baz.taz');
$this->assertEquals(3, count($phlibPath));
static::assertSame(3, count($phlibPath));
}

public function testOffsetAccess(): void
{
$phlibPath = Path::fromString('foo/bar/baz.taz');
$this->assertTrue(isset($phlibPath[1]));
$this->assertEquals('baz.taz', $phlibPath[2]);
static::assertTrue(isset($phlibPath[1]));
static::assertSame('baz.taz', $phlibPath[2]);
}

public function testOffsetsAreImmutable(): void
Expand All @@ -176,7 +173,7 @@ public function testIterable(): void
foreach ($phlibPath as $part) {
$out[] = $part;
}
$this->assertEquals('foo::bar::baz.taz', implode('::', $out));
static::assertSame('foo::bar::baz.taz', implode('::', $out));
}

public function testPartsAreUnescaped(): void
Expand All @@ -186,8 +183,8 @@ public function testPartsAreUnescaped(): void
$path = $dir . '/' . Path::escapeName($name);
$phlibPath = Path::fromString($path);

$this->assertEquals($name, $phlibPath->info(Path::INFO_BASENAME));
$this->assertEquals($name, $phlibPath[1]);
static::assertSame($name, $phlibPath->info(Path::INFO_BASENAME));
static::assertSame($name, $phlibPath[1]);
}

public function testToStringReEscapes(): void
Expand All @@ -197,7 +194,7 @@ public function testToStringReEscapes(): void
$path = $dir . '/' . Path::escapeName($name);
$phlibPath = Path::fromString($path);

$this->assertEquals($path, $phlibPath->toString());
static::assertSame($path, $phlibPath->toString());
}

public function testDirnamePath(): void
Expand All @@ -209,20 +206,19 @@ public function testDirnamePath(): void
$phlibPath = Path::fromString($path);
$dirPath = $phlibPath->getDirnamePath();

$this->assertInstanceOf('\Phlib\Path', $dirPath);
$this->assertEquals($dir, $dirPath->info(Path::INFO_BASENAME));
$this->assertEquals($root, $dirPath->info(Path::INFO_DIRNAME));
static::assertSame($dir, $dirPath->info(Path::INFO_BASENAME));
static::assertSame($root, $dirPath->info(Path::INFO_DIRNAME));
}

public function testSlice(): void
{
$path = 'root/dir/file';
$phlibPath = Path::fromString($path);

$this->assertEquals('dir/file', $phlibPath->slice(1)->toString());
$this->assertEquals('root/dir', $phlibPath->slice(0, -1)->toString());
$this->assertEquals('file', $phlibPath->slice(-1)->toString());
$this->assertEquals('root', $phlibPath->slice(0, 1)->toString());
static::assertSame('dir/file', $phlibPath->slice(1)->toString());
static::assertSame('root/dir', $phlibPath->slice(0, -1)->toString());
static::assertSame('file', $phlibPath->slice(-1)->toString());
static::assertSame('root', $phlibPath->slice(0, 1)->toString());
}

public function testTrimStart(): void
Expand All @@ -232,9 +228,8 @@ public function testTrimStart(): void

$trimmed = $phlibPath->trimStart();

$this->assertInstanceOf('\Phlib\Path', $trimmed);
$this->assertEquals('root/dir/file', $trimmed->toString());
$this->assertEquals(3, $trimmed->count());
static::assertSame('root/dir/file', $trimmed->toString());
static::assertSame(3, $trimmed->count());
}

/**
Expand All @@ -244,6 +239,6 @@ private function assertPathInfoEquals(array $expected, array $actual, string $me
{
ksort($expected);
ksort($actual);
$this->assertEquals($expected, $actual, $message);
static::assertSame($expected, $actual, $message);
}
}