Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for PHPDoc #462

Open
marekdedic opened this issue Mar 10, 2021 · 2 comments
Open

Add support for PHPDoc #462

marekdedic opened this issue Mar 10, 2021 · 2 comments

Comments

@marekdedic
Copy link
Contributor

Feature Request

It would be great if php-scoper supported PHPDoc prefixing as many linters use those (phan for example)

@theofidry
Copy link
Member

It would be ideal but I'm also worried about getting into a rabbit hole as soon as you're hitting PHPStan or psalm related doc-blocks. And since the scoped file is not really meant to be analysed. It should still be readable if needed, i.e. it shouldn't be obfuscated, but at least at the moment that's it.

If you feel strongly about this feature though PRs are welcomed

@ragulka
Copy link

ragulka commented Sep 25, 2024

FWIW, we recently implemented a basic Docblock patcher in one of our projects. It's not 100% foolproof - for example, it does not support union types, it hasn't been tested with psalm, but it works well enough for us now.

I'm sharing our implementation below, as I think it highlights something that PHP Scoper itself could improve on: namely, that it's currently not possible to access any of the fully resolved config inside patchers. Below, I needed to create a new instance of Reflector manually, but it would have been nicer if I could just access the instance that scoper itself uses.

use Humbug\PhpScoper\Patcher\Patcher;
use Humbug\PhpScoper\Symbol\Reflector;

/**
 * Custom patcher for PHP Scoper to prefix class names in docblocks.
 */
final class DocblockPatcher implements Patcher
{
	protected Reflector $reflector;

	public function __construct(protected array $config)
	{
		$this->reflector = Reflector::createWithPhpStormStubs();
	}

	public function __invoke(string $filePath, string $prefix, string $contents): string
	{
		// Pattern for @param, @return, @var, @see, and @throws annotations
		$pattern1 = '/@(?:param|return|var|throws|see)\s+\\\\([a-zA-Z_][\w\\\\]*)/';

		// Pattern for class-string<> instances
		$pattern2 = '/class-string<\\\\([a-zA-Z_][\w\\\\]*)>/';

		$contents = preg_replace_callback($pattern1, fn($matches) => $this->replaceCallback($matches, $prefix), $contents);
		$contents = preg_replace_callback($pattern2, fn($matches) => $this->replaceCallback($matches, $prefix), $contents);

		return $contents;
	}

	protected function replaceCallback(array $matches, string $prefix): string
	{
		$symbol = $matches[1];

		// Ignore if the symbol is already prefixed
		if (str_starts_with($symbol, $prefix . '\\')) {
			return $matches[0];
		}

		// Ignore if the symbol is in the exclude list or is internal
		if ($this->isExcludedOrInternal($symbol)) {
			return $matches[0];
		}

		return str_replace($symbol, $prefix . '\\' . $symbol, $matches[0]);
	}

	/**
	 * Determine if a symbol should be excluded or is internal.
	 *
	 * Since there's no way to know what type the symbol inside a docblock is,
	 * we check for excluded/internal classes, functions and constants.
	 */
	protected function isExcludedOrInternal(string $symbol): bool
	{
		return $this->isClassExcluded($symbol)
			|| $this->isClassInternal($symbol)
			|| $this->isFunctionExcluded($symbol)
			|| $this->isFunctionInternal($symbol)
			|| $this->isConstantExcluded($symbol)
			|| $this->isConstantInternal($symbol);
	}

	protected function isClassExcluded(string $symbol): bool
	{
		return in_array($symbol, $this->config['exclude-classes'], true);
	}

	protected function isClassInternal(string $symbol): bool
	{
		return $this->reflector->isClassInternal($symbol);
	}

	protected function isFunctionExcluded(string $symbol): bool
	{
		return in_array($symbol, $this->config['exclude-functions'], true);
	}

	protected function isFunctionInternal(string $symbol): bool
	{
		return $this->reflector->isFunctionInternal($symbol);
	}

	protected function isConstantExcluded(string $symbol): bool
	{
		return in_array($symbol, $this->config['exclude-constants'], true);
	}

	protected function isConstantInternal(string $symbol): bool
	{
		return $this->reflector->isConstantInternal($symbol);
	}

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants