diff --git a/src/Common/Bridge/BridgeFacadeMethodsRule.php b/src/Common/Bridge/BridgeFacadeMethodsRule.php new file mode 100644 index 0000000..1e35be8 --- /dev/null +++ b/src/Common/Bridge/BridgeFacadeMethodsRule.php @@ -0,0 +1,192 @@ +getName()) === 0 || + preg_match('#.*\\\\Dependency\\\\Facade.*#', $node->getNamespaceName()) === 0 || + !$node instanceof ClassNode + ) { + return; + } + $bridgeName = $node->getFullQualifiedName(); + + foreach ($node->getMethods() as $method) { + $interfaceMethodName = sprintf('%s::%s', $bridgeName, $method->getName()); + $interfaceMethodReflection = new ReflectionMethod($interfaceMethodName); + $methodReturnType = $interfaceMethodReflection->getReturnType(); + + if (preg_match('/^(get|read|find)/', $method->getName())) { + $this->verifyGetMethod($method, $methodReturnType); + + continue; + } + + if (preg_match('/^(delete|remove)/', $method->getName())) { + $this->verifyDeleteMethod($method, $methodReturnType); + + continue; + } + + if (preg_match('/^(create|add)/', $method->getName())) { + $this->verifyCreateMethod($method, $methodReturnType); + + continue; + } + + if (preg_match('/^(update|change)/', $method->getName())) { + $this->verifyUpdateMethod($method, $methodReturnType); + + continue; + } + + if (strpos($method->getName(), 'save') === 0) { + $this->addViolation($method, [sprintf( + 'Method `%s()` must have `public function [update|create]Collection(CollectionRequestTransfer): CollectionResponseTransfer` signature.', + $method->getName(), + )]); + + continue; + } + } + } + + /** + * @param \PHPMD\Node\MethodNode $method + * @param \ReflectionType|null $methodReturnType + * + * @return void + */ + protected function verifyGetMethod(MethodNode $method, ?ReflectionType $methodReturnType): void + { + $parameters = $method->getNode()->getParameters(); + + if ( + $method->getParameterCount() !== 1 || + !$parameters[0]->getClass() || + !$methodReturnType || + preg_match('/^get(?\w+)Collection$/', $method->getName(), $methodNameMatches) === 0 || + preg_match('/^(?\w+)CriteriaTransfer$/', $parameters[0]->getClass()->getName(), $methodParameterMatches) === 0 || + preg_match('/^Generated\\\\Shared\\\\Transfer\\\\(?\w+)CollectionTransfer$/', $methodReturnType->getName(), $methodReturnTypeMatches) === 0 || + count(array_unique([$methodNameMatches['domainEntity'], $methodParameterMatches['domainEntity'], $methodReturnTypeMatches['domainEntity']])) !== 1 + ) { + $this->addViolation($method, [sprintf( + 'Method `%s()` must have `public function getCollection(CriteriaTransfer): CollectionTransfer` signature.', + $method->getName(), + )]); + } + } + + /** + * @param \PHPMD\Node\MethodNode $method + * @param \ReflectionType|null $methodReturnType + * + * @return void + */ + protected function verifyUpdateMethod(MethodNode $method, ?ReflectionType $methodReturnType): void + { + $parameters = $method->getNode()->getParameters(); + + if ( + $method->getParameterCount() !== 1 || + !$parameters[0]->getClass() || + !$methodReturnType || + preg_match('/^update(?\w+)Collection$/', $method->getName(), $methodNameMatches) === 0 || + preg_match('/^(?\w+)CollectionRequestTransfer$/', $parameters[0]->getClass()->getName(), $methodParameterMatches) === 0 || + preg_match('/^Generated\\\\Shared\\\\Transfer\\\\(?\w+)CollectionResponseTransfer$/', $methodReturnType->getName(), $methodReturnTypeMatches) === 0 || + count(array_unique([$methodNameMatches['domainEntity'], $methodParameterMatches['domainEntity'], $methodReturnTypeMatches['domainEntity']])) !== 1 + ) { + $this->addViolation($method, [sprintf( + 'Method `%s()` must have `public function updateCollection(CollectionRequestTransfer): CollectionResponseTransfer` signature.', + $method->getName(), + )]); + } + } + + /** + * @param \PHPMD\Node\MethodNode $method + * @param \ReflectionType|null $methodReturnType + * + * @return void + */ + protected function verifyDeleteMethod(MethodNode $method, ?ReflectionType $methodReturnType): void + { + $parameters = $method->getNode()->getParameters(); + + if ( + $method->getParameterCount() !== 1 || + !$parameters[0]->getClass() || + !$methodReturnType || + preg_match('/^delete(?\w+)Collection$/', $method->getName(), $methodNameMatches) === 0 || + preg_match('/^(?\w+)CollectionDeleteCriteriaTransfer$/', $parameters[0]->getClass()->getName(), $methodParameterMatches) === 0 || + preg_match('/^Generated\\\\Shared\\\\Transfer\\\\(?\w+)CollectionResponseTransfer$/', $methodReturnType->getName(), $methodReturnTypeMatches) === 0 || + count(array_unique([$methodNameMatches['domainEntity'], $methodParameterMatches['domainEntity'], $methodReturnTypeMatches['domainEntity']])) !== 1 + ) { + $this->addViolation($method, [sprintf( + 'Method `%s()` must have `public function deleteCollection(CollectionDeleteCriteriaTransfer): CollectionResponseTransfer` signature.', + $method->getName(), + )]); + } + } + + /** + * @param \PHPMD\Node\MethodNode $method + * @param \ReflectionType|null $methodReturnType + * + * @return void + */ + protected function verifyCreateMethod(MethodNode $method, ?ReflectionType $methodReturnType): void + { + $parameters = $method->getNode()->getParameters(); + + if ( + $method->getParameterCount() !== 1 || + !$parameters[0]->getClass() || + !$methodReturnType || + preg_match('/^create(?\w+)Collection$/', $method->getName(), $methodNameMatches) === 0 || + preg_match('/^(?\w+)CollectionRequestTransfer$/', $parameters[0]->getClass()->getName(), $methodParameterMatches) === 0 || + preg_match('/^Generated\\\\Shared\\\\Transfer\\\\(?\w+)CollectionResponseTransfer$/', $methodReturnType->getName(), $methodReturnTypeMatches) === 0 || + count(array_unique([$methodNameMatches['domainEntity'], $methodParameterMatches['domainEntity'], $methodReturnTypeMatches['domainEntity']])) !== 1 + ) { + $this->addViolation($method, [sprintf( + 'Method `%s()` must have `public function createCollection(CollectionRequestTransfer): CollectionResponseTransfer` signature.', + $method->getName(), + )]); + } + } +} diff --git a/src/Common/ruleset.xml b/src/Common/ruleset.xml index 36813ec..c9eae0a 100644 --- a/src/Common/ruleset.xml +++ b/src/Common/ruleset.xml @@ -173,6 +173,13 @@ 2 + + + 2 + setReport($this->getReportMock(17)); + $bridgeMethodsRule->apply($this->getClassNode()); + } + + /** + * @return void + */ + public function testRuleAppliesWhenFacadeBridgeMethodsAreCorrect(): void + { + $bridgeMethodsRule = new BridgeFacadeMethodsRule(); + $bridgeMethodsRule->setReport($this->getReportMock(0)); + $bridgeMethodsRule->apply($this->getClassNode()); + } +} diff --git a/tests/_bootstrap.php b/tests/_bootstrap.php index c29b6d7..e7c6535 100644 --- a/tests/_bootstrap.php +++ b/tests/_bootstrap.php @@ -21,3 +21,5 @@ require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassDoesNotExtendOrImplementExternal.php'; require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassImplementsExternalAndHasReturnType.php'; require_once '_data/Common/Method/ExternalMethodExtensionReturnTypeRuleTest/testRuleDoesNotApplyWhenClassExtendsExternalAndHasReturnType.php'; +require_once '_data/Common/Bridge/BridgeFacadeMethodsTest/testRuleAppliesWhenFacadeBridgeMethodsAreCorrect.php'; +require_once '_data/Common/Bridge/BridgeFacadeMethodsTest/testRuleDoesNotApplyWhenBridgeFacadeMethodsAreNotCorrect.php'; diff --git a/tests/_data/Common/Bridge/BridgeFacadeMethodsTest/testRuleAppliesWhenFacadeBridgeMethodsAreCorrect.php b/tests/_data/Common/Bridge/BridgeFacadeMethodsTest/testRuleAppliesWhenFacadeBridgeMethodsAreCorrect.php new file mode 100644 index 0000000..5bfa329 --- /dev/null +++ b/tests/_data/Common/Bridge/BridgeFacadeMethodsTest/testRuleAppliesWhenFacadeBridgeMethodsAreCorrect.php @@ -0,0 +1,92 @@ +