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
5 changes: 2 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
php-version:
- '7.3'
- '7.4'

steps:
-
Expand Down Expand Up @@ -52,7 +52,7 @@ jobs:
strategy:
matrix:
php-version:
- '7.3'
- '7.4'

steps:
-
Expand Down Expand Up @@ -85,7 +85,6 @@ jobs:
strategy:
matrix:
php-version:
- '7.3'
- '7.4'
- '8.0'

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
}
],
"require": {
"php": "^7.3 || ^8.0",
"php": "^7.4 || ^8.0",
"psr/log": "^1.0",
"webmozart/path-util": "^2.3"
},
Expand Down
34 changes: 19 additions & 15 deletions lib/Adapter/Composer/ComposerClassToFile.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,9 @@

class ComposerClassToFile implements ClassToFile
{
/**
* @var ClassLoader
*/
private $classLoader;
private ClassLoader $classLoader;

/**
* @var LoggerInterface
*/
private $logger;
private LoggerInterface $logger;

public function __construct(ClassLoader $classLoader, LoggerInterface $logger = null)
{
Expand All @@ -31,8 +25,8 @@ public function classToFileCandidates(ClassName $className): FilePathCandidates
{
$candidates = [];
foreach ($this->getStrategies() as $strategy) {
list($prefixes, $inflector) = $strategy;
$this->resolveFile($candidates, $prefixes, $inflector, $className);
list($prefixes, $inflector, $separator) = $strategy;
$this->resolveFile($candidates, $prefixes, $inflector, $className, $separator);
}

// order with the longest prefixes first
Expand All @@ -54,30 +48,40 @@ private function getStrategies(): array
[
$this->classLoader->getPrefixesPsr4(),
new Psr4NameInflector(),
Psr4NameInflector::NAMESPACE_SEPARATOR,
],
[
$this->classLoader->getPrefixes(),
new Psr0NameInflector(),
Psr0NameInflector::NAMESPACE_SEPARATOR,
],
[
$this->classLoader->getClassMap(),
new ClassmapNameInflector(),
Psr4NameInflector::NAMESPACE_SEPARATOR,
],
[
$this->classLoader->getFallbackDirs(),
new Psr0NameInflector(),
Psr0NameInflector::NAMESPACE_SEPARATOR,
],
[
$this->classLoader->getFallbackDirsPsr4(),
// PSR0 name inflector works here as there is no prefix
new Psr0NameInflector(),
Psr0NameInflector::NAMESPACE_SEPARATOR,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, why do we pass this in if it's the same each time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not the same: Psr0NameInflector::NAMESPACE_SEPARATOR and Psr4NameInflector::NAMESPACE_SEPARATOR are used as an additional separator.

Such separator would be returned from NameInflector::getAdditionalSeparator() or something similar and the we would not need pass it there. If you agree then I can make this change.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok, confused though why would \\ be an additional separator for PSR4? but yeah if we could improve the intent of these constnats/and methods it would be good 👍

now that we've merged a PR the CI should run for you by default... 🤞

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#27

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do not forget to make a release and upgrade it in phpactor while it's not a part of the monolith.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

],
];
}

private function resolveFile(&$candidates, array $prefixes, NameInflector $inflector, ClassName $className): void
{
$fileCandidates = $this->getFileCandidates($className, $prefixes);
private function resolveFile(
&$candidates,
array $prefixes,
NameInflector $inflector,
ClassName $className,
string $separator
): void {
$fileCandidates = $this->getFileCandidates($className, $prefixes, $separator);

foreach ($fileCandidates as $prefix => $files) {
$prefixCandidates = [];
Expand All @@ -93,7 +97,7 @@ private function resolveFile(&$candidates, array $prefixes, NameInflector $infle
}
}

private function getFileCandidates(ClassName $className, array $prefixes)
private function getFileCandidates(ClassName $className, array $prefixes, string $separator)
{
$candidates = [];

Expand All @@ -102,7 +106,7 @@ private function getFileCandidates(ClassName $className, array $prefixes)
$prefix = '';
}

if ($prefix && false === $className->beginsWith($prefix)) {
if ($prefix && false === $className->beginsWith($prefix, $separator)) {
continue;
}

Expand Down
8 changes: 6 additions & 2 deletions lib/Adapter/Composer/Psr0NameInflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@

final class Psr0NameInflector implements NameInflector
{
public const NAMESPACE_SEPARATOR = '_';

public function inflectToRelativePath(string $prefix, ClassName $className, string $mappedPath): FilePath
{
if (substr($prefix, -1) === '_' && $className->beginsWith($prefix)) {
$elements = explode('_', $className);
if (
in_array(substr($prefix, -1), [self::NAMESPACE_SEPARATOR, ClassName::DEFAULT_NAMESPACE_SEPARATOR])
&& $className->beginsWith($prefix, self::NAMESPACE_SEPARATOR)) {
$elements = explode(self::NAMESPACE_SEPARATOR, $className);
$className = implode('\\', $elements);
}

Expand Down
6 changes: 4 additions & 2 deletions lib/Adapter/Composer/Psr4NameInflector.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@

final class Psr4NameInflector implements NameInflector
{
public const NAMESPACE_SEPARATOR = ClassName::DEFAULT_NAMESPACE_SEPARATOR;

public function inflectToRelativePath(string $prefix, ClassName $className, string $mappedPath): FilePath
{
$relativePath = str_replace('\\', '/', substr($className, strlen($prefix))).'.php';
$relativePath = str_replace(self::NAMESPACE_SEPARATOR, '/', substr($className, strlen($prefix))).'.php';

return FilePath::fromParts([$mappedPath, $relativePath]);
}
Expand All @@ -22,7 +24,7 @@ public function inflectToClassName(FilePath $filePath, string $pathPrefix, strin
}

$className = substr($filePath, strlen($pathPrefix) + 1);
$className = str_replace('/', '\\', $className);
$className = str_replace('/', self::NAMESPACE_SEPARATOR, $className);
$className = $classPrefix.$className;
$className = preg_replace('{\.(.+)$}', '', $className);

Expand Down
42 changes: 34 additions & 8 deletions lib/Domain/ClassName.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

namespace Phpactor\ClassFileConverter\Domain;

use function in_array;

final class ClassName
{
private $fullyQualifiedName;
public const DEFAULT_NAMESPACE_SEPARATOR = '\\';

private string $fullyQualifiedName;

private function __construct()
{
Expand All @@ -15,30 +19,52 @@ public function __toString()
return $this->fullyQualifiedName;
}

public static function fromString($string)
public static function fromString(string $string): self
{
$new = new self();
$new->fullyQualifiedName = $string;

return $new;
}

public function namespace()
public function namespace(): string
{
return substr($this->fullyQualifiedName, 0, (int) strrpos($this->fullyQualifiedName, '\\'));
return substr($this->fullyQualifiedName, 0, (int) strrpos(
$this->fullyQualifiedName,
self::DEFAULT_NAMESPACE_SEPARATOR,
));
}

public function name()
public function name(): string
{
$pos = strrpos($this->fullyQualifiedName, '\\');
$pos = strrpos($this->fullyQualifiedName, self::DEFAULT_NAMESPACE_SEPARATOR);
if (false === $pos) {
return $this->fullyQualifiedName;
}
return substr($this->fullyQualifiedName, $pos + 1);
}

public function beginsWith($prefix)
public function beginsWith(string $prefix, string $additionalNseparator = self::DEFAULT_NAMESPACE_SEPARATOR): bool
{
return 0 === strpos($this->fullyQualifiedName, $prefix);
if ($prefix === $this->fullyQualifiedName) {
return true;
}

if (0 !== strpos($this->fullyQualifiedName, $prefix)) {
return false;
}

if ($this->isNamespaceSeparator(mb_substr($prefix, -1, 1), $additionalNseparator)) {
return true;
}

return mb_substr($this->fullyQualifiedName, mb_strlen($prefix), 1) === $additionalNseparator;
}

private function isNamespaceSeparator(
string $character,
string $additionalNseparator = self::DEFAULT_NAMESPACE_SEPARATOR
): bool {
return in_array($character, [self::DEFAULT_NAMESPACE_SEPARATOR, $additionalNseparator], true);
}
}
35 changes: 0 additions & 35 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -95,41 +95,6 @@ parameters:
count: 1
path: lib/Domain/ChainFileToClass.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:beginsWith\\(\\) has no return typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:beginsWith\\(\\) has parameter \\$prefix with no typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:fromString\\(\\) has no return typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:fromString\\(\\) has parameter \\$string with no typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:name\\(\\) has no return typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Method Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:namespace\\(\\) has no return typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Property Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassName\\:\\:\\$fullyQualifiedName has no typehint specified\\.$#"
count: 1
path: lib/Domain/ClassName.php

-
message: "#^Class Phpactor\\\\ClassFileConverter\\\\Domain\\\\ClassNameCandidates implements generic interface IteratorAggregate but does not specify its types\\: TKey, TValue$#"
count: 1
Expand Down
12 changes: 11 additions & 1 deletion tests/Integration/Composer/ComposerClassToFileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ public function testPsr4(): void
$this->assertClassNameToFilePath('Acme\\Test\\Foo\\Class', ['psr4/Foo/Class.php']);
}

/**
* @testdox PSR-4 class name to a file path.
*/
public function testPsr4WithClassmapAuthoritative(): void
{
$this->loadExample('psr4-classmap-authoritative.json');
$this->getClassLoader()->addClassMap(['Acme\\Test\\Foo\\Bar' => $this->workspacePath() . '/psr4/Foo/Bar.php']);
$this->assertClassNameToFilePath('Acme\\Test\\Foo\\Bar2', ['psr4/Foo/Bar2.php']);
$this->assertClassNameToFilePath('Acme\\Test\\Foo\\Class2', ['psr4/Foo/Class2.php']);
}

/**
* @testdox PSR-4 class in dev namespace
*/
Expand Down Expand Up @@ -118,7 +129,6 @@ public function testPsr0ShortNamePrefix2(): void
$this->assertClassNameToFilePath('Twig_Tests_Extension', [ 'psr0/twig/Twig/Tests/Extension.php' ]);
}


/**
* @testdox PSR-4 fallback
*/
Expand Down
3 changes: 2 additions & 1 deletion tests/Integration/Composer/ComposerTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Phpactor\ClassFileConverter\Tests\Integration\Composer;

use Composer\Autoload\ClassLoader;
use Phpactor\ClassFileConverter\Tests\Integration\IntegrationTestCase;
use Symfony\Component\Filesystem\Filesystem;

Expand All @@ -26,7 +27,7 @@ protected function loadExample($composerFile): void
exec('composer install 2> /dev/null');
}

protected function getClassLoader()
protected function getClassLoader(): ClassLoader
{
return require $this->workspacePath().'/vendor/autoload.php';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "dantleech/basic",
"authors": [
{
"name": "dantleech",
"email": "[email protected]"
}
],
"require": {},
"config": {
"classmap-authoritative": true
},
"autoload": {
"psr-4": {
"Acme\\Test\\": "psr4/"
}
}
}