Skip to content

Commit df9848a

Browse files
committed
fix #2669 ContainerBag get method autocomplete and warning
1 parent 1e53707 commit df9848a

12 files changed

Lines changed: 161 additions & 21 deletions

File tree

src/main/java/fr/adrienbrault/idea/symfony2plugin/codeInspection/service/ServiceDeprecatedClassesInspection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ private void visitPhpElement(@NotNull StringLiteralExpression psiElement, @NotNu
189189
}
190190

191191
MethodReference methodReference = PsiElementUtils.getMethodReferenceWithFirstStringParameter(psiElement);
192-
if (methodReference == null || !PhpElementsUtil.isMethodReferenceInstanceOf(methodReference, ServiceContainerUtil.SERVICE_GET_SIGNATURES)) {
192+
if (methodReference == null || !ServiceContainerUtil.isServiceGetMethod(methodReference)) {
193193
return;
194194
}
195195

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/SymfonyPhpReferenceContributor.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
88
import fr.adrienbrault.idea.symfony2plugin.dic.ConstraintPropertyReference;
99
import fr.adrienbrault.idea.symfony2plugin.dic.ServiceReference;
10+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
1011
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityHelper;
1112
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityReference;
1213
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineTypes;
@@ -39,9 +40,6 @@ public class SymfonyPhpReferenceContributor extends PsiReferenceContributor {
3940
new MethodMatcher.CallToSignature("\\Symfony\\Component\\DependencyInjection\\ContainerInterface", "has"),
4041
new MethodMatcher.CallToSignature("\\Psr\\Container\\ContainerInterface", "get"),
4142
new MethodMatcher.CallToSignature("\\Psr\\Container\\ContainerInterface", "has"),
42-
43-
new MethodMatcher.CallToSignature("Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface", "get"),
44-
new MethodMatcher.CallToSignature("Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface", "has"),
4543
};
4644

4745
public static final MethodMatcher.CallToSignature[] REPOSITORY_SIGNATURES = new MethodMatcher.CallToSignature[] {
@@ -117,7 +115,8 @@ public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistr
117115
@Override
118116
public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
119117

120-
if (MethodMatcher.getMatchedSignatureWithDepth(psiElement, CONTAINER_SIGNATURES) == null) {
118+
MethodMatcher.MethodMatchParameter methodMatchParameter = MethodMatcher.getMatchedSignatureWithDepth(psiElement, CONTAINER_SIGNATURES);
119+
if (methodMatchParameter == null || ServiceContainerUtil.isContainerBagParameterAccess(methodMatchParameter.getMethodReference())) {
121120
return new PsiReference[0];
122121
}
123122

src/main/java/fr/adrienbrault/idea/symfony2plugin/config/php/PhpConfigReferenceContributor.java

Lines changed: 35 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
import fr.adrienbrault.idea.symfony2plugin.dic.AbstractServiceReference;
1515
import fr.adrienbrault.idea.symfony2plugin.dic.ServiceReference;
1616
import fr.adrienbrault.idea.symfony2plugin.dic.TagReference;
17+
import fr.adrienbrault.idea.symfony2plugin.dic.container.util.ServiceContainerUtil;
1718
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
19+
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
1820
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1921
import fr.adrienbrault.idea.symfony2plugin.util.PhpStringLiteralExpressionReference;
2022
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
@@ -29,22 +31,45 @@
2931
* @author Daniel Espendiller <daniel@espendiller.net>
3032
*/
3133
public class PhpConfigReferenceContributor extends PsiReferenceContributor {
34+
private static final MethodMatcher.CallToSignature[] SERVICE_HAS_SIGNATURES = new MethodMatcher.CallToSignature[] {
35+
new MethodMatcher.CallToSignature("\\Symfony\\Component\\DependencyInjection\\ContainerInterface", "has"),
36+
new MethodMatcher.CallToSignature("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "has"),
37+
new MethodMatcher.CallToSignature("\\Psr\\Container\\ContainerInterface", "has"),
38+
39+
// Symfony 3.3 / 3.4
40+
new MethodMatcher.CallToSignature("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerTrait", "has"),
41+
42+
// Symfony 4
43+
new MethodMatcher.CallToSignature("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController", "has"),
44+
};
3245

3346
@Override
3447
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar psiReferenceRegistrar) {
35-
psiReferenceRegistrar.registerReferenceProvider(PhpElementsUtil.getMethodWithFirstStringOrNamedArgumentPattern(), new PhpStringLiteralExpressionReference(ServiceReference.class)
36-
.addCall("\\Symfony\\Component\\DependencyInjection\\ContainerInterface", "has")
37-
.addCall("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller", "has")
38-
.addCall("\\Psr\\Container\\ContainerInterface", "has")
48+
psiReferenceRegistrar.registerReferenceProvider(PhpElementsUtil.getMethodWithFirstStringOrNamedArgumentPattern(), new PsiReferenceProvider() {
49+
@NotNull
50+
@Override
51+
public PsiReference @NotNull [] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
52+
if (!Symfony2ProjectComponent.isEnabled(psiElement) || !(psiElement instanceof StringLiteralExpression stringLiteralExpression) || !(psiElement.getContext() instanceof ParameterList parameterList)) {
53+
return new PsiReference[0];
54+
}
3955

40-
// Symfony 3.3 / 3.4
41-
.addCall("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerTrait", "has")
56+
PsiElement method = parameterList.getContext();
57+
if (!(method instanceof MethodReference methodReference) || PsiElementUtils.getParameterIndexValue(psiElement) != 0) {
58+
return new PsiReference[0];
59+
}
4260

43-
// Symfony 4
44-
.addCall("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController", "has")
61+
if (ServiceContainerUtil.isContainerBagParameterAccess(methodReference) || !PhpElementsUtil.isMethodReferenceInstanceOf(methodReference, SERVICE_HAS_SIGNATURES)) {
62+
return new PsiReference[0];
63+
}
4564

46-
.addCall("\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface", "has")
47-
);
65+
return new PsiReference[]{ new ServiceReference(stringLiteralExpression) };
66+
}
67+
68+
@Override
69+
public boolean acceptsTarget(@NotNull PsiElement target) {
70+
return Symfony2ProjectComponent.isEnabled(target);
71+
}
72+
});
4873

4974
psiReferenceRegistrar.registerReferenceProvider(PhpElementsUtil.getMethodWithFirstStringOrNamedArgumentPattern(), new PhpStringLiteralExpressionReference(ServiceIndexedReference.class)
5075
.addCall("\\Symfony\\Component\\DependencyInjection\\ContainerBuilder", "hasDefinition")

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/SymfonyContainerTypeProvider.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ public PhpType getType(PsiElement e) {
5050
return null;
5151
}
5252

53+
if (!ServiceContainerUtil.isServiceGetMethod(methodReference)) {
54+
return null;
55+
}
56+
5357
String signature = PhpTypeProviderUtil.getReferenceSignatureByFirstParameter(methodReference, TRIM_KEY);
5458
return signature == null ? null : new PhpType().add("#" + this.getKey() + signature);
5559
}
@@ -78,7 +82,7 @@ public Collection<? extends PhpNamedElement> getBySignature(String expression, S
7882
}
7983

8084
// finally search the classes
81-
if (PhpElementsUtil.isMethodInstanceOf((Method) phpNamedElement, ServiceContainerUtil.SERVICE_GET_SIGNATURES)) {
85+
if (ServiceContainerUtil.isServiceGetMethod((Method) phpNamedElement)) {
8286
ContainerService containerService = ContainerCollectionResolver.getService(project, parameter);
8387

8488
if (containerService != null) {

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/container/util/ServiceContainerUtil.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
*/
6262
public class ServiceContainerUtil {
6363
private static final Key<CachedValue<Map<String, Collection<PhpClassResourceCandidate>>>> PHP_CLASS_FQN_RESOURCE_CACHE = new Key<>("SYMFONY_PHP_CLASS_FQN_RESOURCE_CACHE");
64+
public static final String CONTAINER_BAG_INTERFACE = "\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface";
6465
public static final MethodMatcher.CallToSignature[] SERVICE_GET_SIGNATURES = new MethodMatcher.CallToSignature[] {
6566
new MethodMatcher.CallToSignature("\\Symfony\\Component\\DependencyInjection\\ContainerInterface", "get"),
6667
new MethodMatcher.CallToSignature("\\Psr\\Container\\ContainerInterface", "get"),
@@ -71,8 +72,6 @@ public class ServiceContainerUtil {
7172

7273
// Symfony 4
7374
new MethodMatcher.CallToSignature("\\Symfony\\Bundle\\FrameworkBundle\\Controller\\AbstractController", "get"),
74-
75-
new MethodMatcher.CallToSignature("\\Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface", "get"),
7675
};
7776

7877
private static final Key<CachedValue<Collection<String>>> SYMFONY_CONTAINER_FILES = new Key<>("SYMFONY_CONTAINER_FILES");
@@ -94,6 +93,29 @@ public class ServiceContainerUtil {
9493
public static final String CONTAINER_CONFIGURATOR = "\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\ContainerConfigurator";
9594
public static final String CONTAINER_CONFIG_APP = "\\Symfony\\Component\\DependencyInjection\\Loader\\Configurator\\App";
9695

96+
public static boolean isServiceGetMethod(@NotNull MethodReference methodReference) {
97+
if (isContainerBagParameterAccess(methodReference)) {
98+
return false;
99+
}
100+
101+
return PhpElementsUtil.isMethodReferenceInstanceOf(methodReference, SERVICE_GET_SIGNATURES);
102+
}
103+
104+
public static boolean isServiceGetMethod(@NotNull Method method) {
105+
return PhpElementsUtil.isMethodInstanceOf(method, SERVICE_GET_SIGNATURES);
106+
}
107+
108+
public static boolean isContainerBagParameterAccess(@NotNull MethodReference methodReference) {
109+
String methodName = methodReference.getName();
110+
if (!"get".equals(methodName) && !"has".equals(methodName)) {
111+
return false;
112+
}
113+
114+
PhpExpression receiver = methodReference.getClassReference();
115+
return receiver != null && receiver.getType().getTypes().stream()
116+
.anyMatch(fqn -> PhpElementsUtil.isInstanceOf(methodReference.getProject(), fqn, CONTAINER_BAG_INTERFACE));
117+
}
118+
97119
@NotNull
98120
public static Collection<ServiceSerializable> getServicesInFile(@NotNull PsiFile psiFile) {
99121
final Collection<ServiceSerializable> services = new ArrayList<>();

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/inspection/MissingServiceInspection.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public void visitElement(@NotNull PsiElement element) {
5151
if(element.getLanguage() == PhpLanguage.INSTANCE && element instanceof StringLiteralExpression) {
5252
// PHP
5353
MethodReference methodReference = PsiElementUtils.getMethodReferenceWithFirstStringParameter((StringLiteralExpression) element);
54-
if (methodReference != null && PhpElementsUtil.isMethodReferenceInstanceOf(methodReference, ServiceContainerUtil.SERVICE_GET_SIGNATURES)) {
54+
if (methodReference != null && ServiceContainerUtil.isServiceGetMethod(methodReference)) {
5555
String serviceName = PhpElementsUtil.getFirstArgumentStringValue(methodReference);
5656
if (StringUtils.isNotBlank(serviceName) && !hasService(serviceName)) {
5757
holder.registerProblem(element, INSPECTION_MESSAGE, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);

src/main/java/fr/adrienbrault/idea/symfony2plugin/dic/registrar/DicGotoCompletionRegistrar.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver;
2121
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
2222
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
23+
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
2324
import fr.adrienbrault.idea.symfony2plugin.util.completion.TagNameCompletionProvider;
2425
import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil;
2526
import org.apache.commons.lang3.StringUtils;
@@ -56,6 +57,13 @@ public void register(@NotNull GotoCompletionRegistrarParameter registrar) {
5657
.match();
5758

5859
if(methodMatchParameter == null) {
60+
MethodReference methodReference = PsiElementUtils.getMethodReferenceWithFirstStringParameter((StringLiteralExpression) context);
61+
if (methodReference == null || !ServiceContainerUtil.isContainerBagParameterAccess(methodReference)) {
62+
return null;
63+
}
64+
}
65+
66+
if(methodMatchParameter == null && PsiElementUtils.getParameterIndexValue(context) != 0) {
5967
return null;
6068
}
6169

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/inspection/MissingServiceInspectionTest.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,23 @@ public void testThatPhpServiceInterfaceForGetMethodIsInspected() {
3232
);
3333
}
3434

35+
public void testThatContainerBagGetMethodIsNotInspectedAsService() {
36+
assertLocalInspectionNotContains("test.php", "<?php\n" +
37+
"use Symfony\\Component\\DependencyInjection\\ParameterBag\\ContainerBagInterface;\n" +
38+
"\n" +
39+
"class TestService\n" +
40+
"{\n" +
41+
" public function __construct(private ContainerBagInterface $containerBag) {}\n" +
42+
"\n" +
43+
" public function testFoo()\n" +
44+
" {\n" +
45+
" $this->containerBag->get('app.to<caret>ken');\n" +
46+
" }\n" +
47+
"}",
48+
MissingServiceInspection.INSPECTION_MESSAGE
49+
);
50+
}
51+
3552
public void testThatPhpAttributesForServiceAutowireIsInspected() {
3653
assertLocalInspectionContains("test.php", "<?php\n" +
3754
"use Symfony\\Component\\DependencyInjection\\Attribute\\Autowire;\n" +

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/inspection/fixtures/classes.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@ public function get();
88
};
99
}
1010

11+
namespace Psr\Container
12+
{
13+
interface ContainerInterface
14+
{
15+
public function get(string $id);
16+
public function has(string $id);
17+
}
18+
}
19+
20+
namespace Symfony\Component\DependencyInjection\ParameterBag
21+
{
22+
interface ContainerBagInterface extends \Psr\Container\ContainerInterface
23+
{
24+
public function all();
25+
}
26+
}
27+
1128
namespace Foobar
1229
{
1330
class Car
@@ -27,4 +44,4 @@ public function __construct($foobar)
2744
{
2845
class Autowire {}
2946
class AsDecorator {}
30-
}
47+
}

src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/dic/registrar/DicGotoCompletionRegistrarTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,30 @@ public void testParameterContributor() {
3636
}
3737
}
3838

39+
public void testParameterContributorForParameterBagInterfaces() {
40+
for (String className : new String[]{"ParameterBagInterface", "ContainerBagInterface"}) {
41+
for (String methodName : new String[]{"get", "has"}) {
42+
String php = "<?php\n" +
43+
"use Symfony\\Component\\DependencyInjection\\ParameterBag\\" + className + ";\n" +
44+
"\n" +
45+
"class TestService\n" +
46+
"{\n" +
47+
" public function __construct(private " + className + " $bag) {}\n" +
48+
"\n" +
49+
" public function testFoo()\n" +
50+
" {\n" +
51+
" $this->bag->" + methodName + "('<caret>');\n" +
52+
" }\n" +
53+
"}\n";
54+
55+
assertCompletionContains(PhpFileType.INSTANCE, php, "app.token");
56+
assertCompletionNotContains(PhpFileType.INSTANCE, php, "foo_bar_service");
57+
58+
assertNavigationMatch(PhpFileType.INSTANCE, php.replace("<caret>", "app.to<caret>ken"), PlatformPatterns.psiElement());
59+
}
60+
}
61+
}
62+
3963
public void testParameterContributorProxied() {
4064
for (String s : new String[]{"foo", "bar"}) {
4165
assertCompletionContains(PhpFileType.INSTANCE, "<?php\n" +

0 commit comments

Comments
 (0)