PHP: 사용자 지정 도구 확장을 사용한 코드 품질
32435 단어 php
수년 동안 PHPStan, PHP-CS-Fixer, PHP_CodeSniffer 등을 사용한 후 한 가지 조언을 드리겠습니다. 코드 품질 도구를 확장하기 위해 사용자 정의 코드를 추가하십시오.
거의 모든 프로젝트에는 제품/프로젝트의 실제 가치를 조달하는 사용자 지정 코드가 있지만 이 사용자 지정 코드 자체는 종종 PHP-CS-Fixer, PHPStan, Psalm 및 기타 도구로 실제로 개선되지 않습니다. 도구는 이 사용자 정의 코드가 어떻게 작동하는지 알지 못하므로 일부 확장을 직접 작성해야 합니다.
예: 직장에서 Active Record 클래스의 일부 속성을 사용하는 HFE(Html-Form-Element) 클래스가 있으며 그 당시 문자열을 사용하여 두 클래스를 연결했습니다. :-/
힌트: 문자열은 매우 유연하지만 미래에 프로그래밍 방식으로 사용하기에는 끔찍합니다. 가능한 한 일반 문자열을 피하는 것이 좋습니다.
1. 커스텀 PHP-CS-픽서
그래서 문자열을 일부 메타데이터로 대체할 빠른 스크립트를 작성했습니다. 가장 큰 장점은 이 사용자 정의 PHP-CS-Fixer가 향후 생성될 코드를 자동으로 수정하고 CI-pipline 또는 예를 들어 이를 적용/확인할 수 있다는 것입니다. 사전 커밋 후크 또는 PhpStorm에서 직접.
<?php
declare(strict_types=1);
use PhpCsFixer\Tokenizer\Analyzer\ArgumentsAnalyzer;
use PhpCsFixer\Tokenizer\Analyzer\FunctionsAnalyzer;
use PhpCsFixer\Tokenizer\Token;
use PhpCsFixer\Tokenizer\Tokens;
final class MeerxUseMetaFromActiveRowForHFECallsFixer extends AbstractMeerxFixerHelper
{
/**
* {@inheritdoc}
*/
public function getDocumentation(): string
{
return 'Use ActiveRow->m() for "HFE_"-calls, if it is possible.';
}
/**
* {@inheritdoc}
*/
public function getSampleCode(): string
{
return <<<'PHP'
<?php
$element = UserFactory::singleton()->fetchEmpty();
$foo = HFE_Date::Gen($element, 'created_date');
PHP;
}
public function isRisky(): bool
{
return true;
}
/**
* {@inheritdoc}
*/
public function isCandidate(Tokens $tokens): bool
{
return $tokens->isTokenKindFound(\T_STRING);
}
public function getPriority(): int {
// must be run after NoAliasFunctionsFixer
// must be run before MethodArgumentSpaceFixer
return -1;
}
protected function applyFix(SplFileInfo $file, Tokens $tokens): void
{
if (v_str_contains($file->getFilename(), 'HFE_')) {
return;
}
$functionsAnalyzer = new FunctionsAnalyzer();
// fix for "HFE_*::Gen()"
foreach ($tokens as $index => $token) {
$index = (int)$index;
// only for "Gen()"-calls
if (!$token->equals([\T_STRING, 'Gen'], false)) {
continue;
}
// only for "HFE_*"-classes
$object = (string)$tokens[$index - 2]->getContent();
if (!v_str_starts_with($object, 'HFE_')) {
continue;
}
if ($functionsAnalyzer->isGlobalFunctionCall($tokens, $index)) {
continue;
}
$argumentsIndices = $this->getArgumentIndices($tokens, $index);
if (\count($argumentsIndices) >= 2) {
[
$firstArgumentIndex,
$secondArgumentIndex
] = array_keys($argumentsIndices);
// If the second argument is not a string, we cannot make a swap.
if (!$tokens[$secondArgumentIndex]->isGivenKind(\T_CONSTANT_ENCAPSED_STRING)) {
continue;
}
$content = trim($tokens[$secondArgumentIndex]->getContent(), '\'"');
if (!$content) {
continue;
}
$newContent = $tokens[$firstArgumentIndex]->getContent() . '->m()->' . $content;
$tokens[$secondArgumentIndex] = new Token([\T_CONSTANT_ENCAPSED_STRING, $newContent]);
}
}
}
/**
* @param Token[]|Tokens $tokens <phpdoctor-ignore-this-line/>
* @param int $functionNameIndex
*
* @return array<int, int> In the format: startIndex => endIndex
*/
private function getArgumentIndices(Tokens $tokens, $functionNameIndex): array
{
$argumentsAnalyzer = new ArgumentsAnalyzer();
$openParenthesis = $tokens->getNextTokenOfKind($functionNameIndex, ['(']);
$closeParenthesis = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_PARENTHESIS_BRACE, $openParenthesis);
// init
$indices = [];
foreach ($argumentsAnalyzer->getArguments($tokens, $openParenthesis, $closeParenthesis) as $startIndexCandidate => $endIndex) {
$indices[$tokens->getNextMeaningfulToken($startIndexCandidate - 1)] = $tokens->getPrevMeaningfulToken($endIndex + 1);
}
return $indices;
}
}
사용자 정의 수정 사항을 사용하려면 등록하고 활성화할 수 있습니다. https://cs.symfony.com/doc/custom_rules.html
예-결과:
$fieldGroup->addElement(HFE_Customer::Gen($element, 'customer_id'));
// <- will be replaced with ->
$fieldGroup->addElement(HFE_Customer::Gen($element, $element->m()->customer_id));
힌트: GitHub에는 PHP_CodeSniffer 및 Fixer Rules에 대한 많은 예가 있습니다. 종종 사용 사례에 50-70% 맞는 것을 선택한 다음 필요에 따라 수정할 수 있습니다.
"m()"메서드는 다음과 같으며 간단한 "ActiveRowMeta"클래스를 호출합니다. 이 클래스는 실제 값 대신 속성 이름 자체를 반환합니다.
/**
* (M)ETA
*
* @return ActiveRowMeta|mixed|static
* <p>
* We fake the return "static" here because we want auto-completion for the current properties in the IDE.
* <br><br>
* But here the properties contains only the name from the property itself.
* </p>
*
* @psalm-return object{string,string}
*/
final public function m()
{
return (new ActiveRowMeta())->create($this);
}
<?php
final class ActiveRowMeta
{
/**
* @return static
*/
public function create(ActiveRow $obj): self
{
/** @var static[] $STATIC_CACHE */
static $STATIC_CACHE = [];
// DEBUG
// var_dump($STATIC_CACHE);
$cacheKey = \get_class($obj);
if (!empty($STATIC_CACHE[$cacheKey])) {
return $STATIC_CACHE[$cacheKey];
}
foreach ($obj->getObjectVars() as $propertyName => $propertyValue) {
$this->{$propertyName} = $propertyName;
}
$STATIC_CACHE[$cacheKey] = $this;
return $this;
}
}
2. 커스텀 PHPStan 확장
다음 단계에서 정적 코드 분석이 메타데이터의 유형을 알 수 있도록 PHPStan에 대한 DynamicMethodReturnTypeExtension을 추가했습니다. + phpdocs를 통해 IDE에서 자동 완성 기능이 여전히 있습니다.
참고: 여기에서도 메타데이터를 읽기 전용으로 만들었으므로 메타데이터를 오용할 수 없습니다.
<?php
declare(strict_types=1);
namespace meerx\App\scripts\githooks\StandardMeerx\PHPStanHelper;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Type;
final class MeerxMetaDynamicReturnTypeExtension implements \PHPStan\Type\DynamicMethodReturnTypeExtension
{
public function getClass(): string
{
return \ActiveRow::class;
}
public function isMethodSupported(MethodReflection $methodReflection): bool
{
return $methodReflection->getName() === 'm';
}
/**
* @var \PHPStan\Reflection\ReflectionProvider
*/
private $reflectionProvider;
public function __construct(\PHPStan\Reflection\ReflectionProvider $reflectionProvider)
{
$this->reflectionProvider = $reflectionProvider;
}
public function getTypeFromMethodCall(
MethodReflection $methodReflection,
MethodCall $methodCall,
Scope $scope
): Type
{
$exprType = $scope->getType($methodCall->var);
$staticClassName = $exprType->getReferencedClasses()[0];
$classReflection = $this->reflectionProvider->getClass($staticClassName);
return new MeerxMetaType($staticClassName, null, $classReflection);
}
}
<?php
declare(strict_types=1);
namespace meerx\App\scripts\githooks\StandardMeerx\PHPStanHelper;
use PHPStan\Reflection\ClassMemberAccessAnswerer;
use PHPStan\Type\ObjectType;
final class MeerxMetaType extends ObjectType
{
public function getProperty(string $propertyName, ClassMemberAccessAnswerer $scope): \PHPStan\Reflection\PropertyReflection
{
return new MeerxMetaProperty($this->getClassReflection());
}
}
<?php
declare(strict_types=1);
namespace meerx\App\scripts\githooks\StandardMeerx\PHPStanHelper;
use PHPStan\Reflection\ClassReflection;
use PHPStan\TrinaryLogic;
use PHPStan\Type\NeverType;
use PHPStan\Type\StringType;
final class MeerxMetaProperty implements \PHPStan\Reflection\PropertyReflection
{
private ClassReflection $classReflection;
public function __construct(ClassReflection $classReflection)
{
$this->classReflection = $classReflection;
}
public function getReadableType(): \PHPStan\Type\Type
{
return new StringType();
}
public function getWritableType(): \PHPStan\Type\Type
{
return new NeverType();
}
public function isWritable(): bool
{
return false;
}
public function getDeclaringClass(): \PHPStan\Reflection\ClassReflection
{
return $this->classReflection;
}
public function isStatic(): bool
{
return false;
}
public function isPrivate(): bool
{
return false;
}
public function isPublic(): bool
{
return true;
}
public function getDocComment(): ?string
{
return null;
}
public function canChangeTypeAfterAssignment(): bool
{
return false;
}
public function isReadable(): bool
{
return true;
}
public function isDeprecated(): \PHPStan\TrinaryLogic
{
return TrinaryLogic::createFromBoolean(false);
}
public function getDeprecatedDescription(): ?string
{
return null;
}
public function isInternal(): \PHPStan\TrinaryLogic
{
return TrinaryLogic::createFromBoolean(false);
}
}
요약
사용자 지정 코드와 이를 개선할 수 있는 방법에 대해 생각하고 이미 사용한 도구를 사용하고 확장하여 코드를 이해할 수 있습니다. 때로는 쉽고 일부modern PHPDocs를 추가할 수 있거나 토끼 구멍으로 내려가 일부 사용자 정의 항목을 구현해야 하지만 마침내 소프트웨어, 팀 및 고객에게 도움이 될 것입니다.
Reference
이 문제에 관하여(PHP: 사용자 지정 도구 확장을 사용한 코드 품질), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/suckup_de/php-code-quality-with-custom-tooling-extensions-27cc텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)