Skip to content

Commit 9791a8b

Browse files
committed
refactoring of ObjectManager::findBy* and support more possible repository usages #925 #898
1 parent 97de010 commit 9791a8b

13 files changed

+365
-128
lines changed

META-INF/plugin.xml

+1
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,7 @@
662662
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.doctrine.querybuilder.dbal.DoctrineDbalQbGotoCompletionRegistrar"/>
663663
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.type.DoctrineTypeGotoCompletionRegistrar"/>
664664
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.DoctrineYamlGotoCompletionRegistrar"/>
665+
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.ObjectRepositoryFindGotoCompletionRegistrar"/>
665666
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.completion.xml.XmlGotoCompletionRegistrar"/>
666667
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.dic.registrar.DicGotoCompletionRegistrar"/>
667668
<GotoCompletionRegistrar implementation="fr.adrienbrault.idea.symfony2plugin.completion.yaml.YamlGotoCompletionRegistrar"/>

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

-36
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,12 @@
44
import com.intellij.psi.*;
55
import com.intellij.util.ProcessingContext;
66
import com.jetbrains.php.lang.PhpLanguage;
7-
import com.jetbrains.php.lang.parser.PhpElementTypes;
87
import com.jetbrains.php.lang.psi.elements.*;
98
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
109
import fr.adrienbrault.idea.symfony2plugin.dic.ConstraintPropertyReference;
1110
import fr.adrienbrault.idea.symfony2plugin.dic.ServiceReference;
1211
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityHelper;
1312
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityReference;
14-
import fr.adrienbrault.idea.symfony2plugin.doctrine.ModelFieldReference;
1513
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineTypes;
1614
import fr.adrienbrault.idea.symfony2plugin.templating.TemplateReference;
1715
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
@@ -20,8 +18,6 @@
2018
import fr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
2119
import org.jetbrains.annotations.NotNull;
2220

23-
import java.util.Collection;
24-
2521
/**
2622
* @author Daniel Espendiller <daniel@espendiller.net>
2723
*/
@@ -151,38 +147,6 @@ public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @No
151147
}
152148
);
153149

154-
psiReferenceRegistrar.registerReferenceProvider(
155-
// @TODO: implement global pattern for array parameters
156-
PlatformPatterns.psiElement(StringLiteralExpression.class).withParent(
157-
PlatformPatterns.or(
158-
PlatformPatterns.psiElement(PhpElementTypes.ARRAY_VALUE),
159-
PlatformPatterns.psiElement(PhpElementTypes.ARRAY_KEY)
160-
)
161-
).inside(PlatformPatterns.psiElement(ParameterList.class)),
162-
new PsiReferenceProvider() {
163-
@NotNull
164-
@Override
165-
public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) {
166-
167-
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.ArrayParameterMatcher(psiElement, 0)
168-
.withSignature("\\Doctrine\\Common\\Persistence\\ObjectRepository", "findOneBy")
169-
.withSignature("\\Doctrine\\Common\\Persistence\\ObjectRepository", "findBy")
170-
.match();
171-
172-
if(methodMatchParameter == null) {
173-
return new PsiReference[0];
174-
}
175-
176-
Collection<PhpClass> phpClasses = PhpElementsUtil.getClassFromPhpTypeSetArrayClean(psiElement.getProject(), methodMatchParameter.getMethodReference().getType().getTypes());
177-
if(phpClasses.size() == 0) {
178-
return new PsiReference[0];
179-
}
180-
181-
return new PsiReference[]{ new ModelFieldReference((StringLiteralExpression) psiElement, phpClasses)};
182-
}
183-
}
184-
);
185-
186150
psiReferenceRegistrar.registerReferenceProvider(
187151
PlatformPatterns.psiElement(StringLiteralExpression.class),
188152
new PsiReferenceProvider() {

src/fr/adrienbrault/idea/symfony2plugin/doctrine/ModelFieldReference.java

-60
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package fr.adrienbrault.idea.symfony2plugin.doctrine.metadata;
2+
3+
import com.intellij.codeInsight.lookup.LookupElement;
4+
import com.intellij.psi.PsiElement;
5+
import com.jetbrains.php.lang.psi.elements.MethodReference;
6+
import com.jetbrains.php.lang.psi.elements.PhpClass;
7+
import com.jetbrains.php.lang.psi.elements.PhpExpression;
8+
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
9+
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
10+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionProvider;
11+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrar;
12+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.GotoCompletionRegistrarParameter;
13+
import fr.adrienbrault.idea.symfony2plugin.codeInsight.utils.GotoCompletionUtil;
14+
import fr.adrienbrault.idea.symfony2plugin.doctrine.EntityHelper;
15+
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelField;
16+
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelFieldLookupElement;
17+
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelInterface;
18+
import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.util.DoctrineMetadataUtil;
19+
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher;
20+
import fr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
21+
import org.jetbrains.annotations.NotNull;
22+
23+
import java.util.*;
24+
import java.util.function.Function;
25+
import java.util.stream.Collectors;
26+
27+
/**
28+
* @author Daniel Espendiller <daniel@espendiller.net>
29+
*/
30+
public class ObjectRepositoryFindGotoCompletionRegistrar implements GotoCompletionRegistrar {
31+
public void register(GotoCompletionRegistrarParameter registrar) {
32+
33+
// "@var $om \Doctrine\Common\Persistence\ObjectManager"
34+
// "$om->getRepository('Foo\Bar')->" + s + "(['foo' => 'foo', '<caret>' => 'foo'])"
35+
registrar.register(PhpElementsUtil.getParameterListArrayValuePattern(), psiElement -> {
36+
PsiElement context = psiElement.getContext();
37+
if (!(context instanceof StringLiteralExpression)) {
38+
return null;
39+
}
40+
41+
MethodMatcher.MethodMatchParameter methodMatchParameter = new MethodMatcher.ArrayParameterMatcher(context, 0)
42+
.withSignature("\\Doctrine\\Common\\Persistence\\ObjectRepository", "findOneBy")
43+
.withSignature("\\Doctrine\\Common\\Persistence\\ObjectRepository", "findBy")
44+
.match();
45+
46+
if(methodMatchParameter != null) {
47+
MethodReference methodReference = methodMatchParameter.getMethodReference();
48+
49+
// extract from type provide on completion:
50+
// $foo->getRepository('MODEL')->findBy()
51+
Collection<PhpClass> phpClasses = PhpElementsUtil.getClassFromPhpTypeSetArrayClean(psiElement.getProject(), methodReference.getType().getTypes());
52+
53+
// resolve every direct repository instance $this->findBy()
54+
// or direct repository instance $repository->findBy()
55+
if(phpClasses.size() == 0) {
56+
PhpExpression classReference = methodReference.getClassReference();
57+
if(classReference != null) {
58+
PhpType type = classReference.getType();
59+
for (String s : type.getTypes()) {
60+
// dont visit type providers
61+
if(PhpType.isUnresolved(s)) {
62+
continue;
63+
}
64+
65+
for (DoctrineModelInterface doctrineModel : DoctrineMetadataUtil.findMetadataModelForRepositoryClass(psiElement.getProject(), s)) {
66+
phpClasses.addAll(PhpElementsUtil.getClassesInterface(psiElement.getProject(), doctrineModel.getClassName()));
67+
}
68+
}
69+
}
70+
}
71+
72+
if(phpClasses.size() == 0) {
73+
return null;
74+
}
75+
76+
return new MyArrayFieldMetadataGotoCompletionRegistrar(psiElement, phpClasses);
77+
}
78+
79+
return null;
80+
});
81+
}
82+
83+
private static class MyArrayFieldMetadataGotoCompletionRegistrar extends GotoCompletionProvider {
84+
@NotNull
85+
private final Collection<PhpClass> phpClasses;
86+
87+
MyArrayFieldMetadataGotoCompletionRegistrar(@NotNull PsiElement element, @NotNull Collection<PhpClass> phpClasses) {
88+
super(element);
89+
this.phpClasses = phpClasses;
90+
}
91+
92+
@NotNull
93+
@Override
94+
public Collection<LookupElement> getLookupElements() {
95+
List<LookupElement> results = new ArrayList<>();
96+
97+
phpClasses.forEach(phpClass ->
98+
results.addAll(EntityHelper.getModelFields(phpClass).stream()
99+
.map((Function<DoctrineModelField, LookupElement>) DoctrineModelFieldLookupElement::new)
100+
.collect(Collectors.toList())
101+
)
102+
);
103+
104+
return results;
105+
}
106+
107+
@NotNull
108+
@Override
109+
public Collection<PsiElement> getPsiTargets(PsiElement element) {
110+
String content = GotoCompletionUtil.getTextValueForElement(element);
111+
if(content == null) {
112+
return Collections.emptyList();
113+
}
114+
115+
Collection<PsiElement> results = new ArrayList<>();
116+
117+
phpClasses.forEach(phpClass ->
118+
results.addAll(Arrays.asList(EntityHelper.getModelFieldTargets(phpClass, content)))
119+
);
120+
121+
return results;
122+
}
123+
}
124+
}

src/fr/adrienbrault/idea/symfony2plugin/doctrine/metadata/util/DoctrineMetadataUtil.java

+26
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ public static Collection<VirtualFile> findMetadataForRepositoryClass(final @NotN
136136
project.putUserData(DOCTRINE_REPOSITORY_CACHE, cache);
137137
}
138138

139+
repositoryClass = StringUtils.stripStart(repositoryClass,"\\");
139140
if(!cache.getValue().containsKey(repositoryClass)) {
140141
return Collections.emptyList();
141142
}
@@ -151,6 +152,31 @@ public static Collection<VirtualFile> findMetadataForRepositoryClass(final @NotN
151152
return virtualFiles;
152153
}
153154

155+
/**
156+
* Find metadata model in which the given repository class is used
157+
* eg "@ORM\Entity(repositoryClass="FOOBAR")", xml or yaml
158+
*/
159+
@NotNull
160+
public static Collection<DoctrineModelInterface> findMetadataModelForRepositoryClass(final @NotNull Project project, @NotNull String repositoryClass) {
161+
repositoryClass = StringUtils.stripStart(repositoryClass,"\\");
162+
163+
Collection<DoctrineModelInterface> models = new ArrayList<>();
164+
165+
for (String key : FileIndexCaches.getIndexKeysCache(project, CLASS_KEYS, DoctrineMetadataFileStubIndex.KEY)) {
166+
for (DoctrineModelInterface repositoryDefinition : FileBasedIndex.getInstance().getValues(DoctrineMetadataFileStubIndex.KEY, key, GlobalSearchScope.allScope(project))) {
167+
String myRepositoryClass = repositoryDefinition.getRepositoryClass();
168+
if(StringUtils.isBlank(myRepositoryClass) ||
169+
!repositoryClass.equalsIgnoreCase(StringUtils.stripStart(myRepositoryClass, "\\"))) {
170+
continue;
171+
}
172+
173+
models.add(repositoryDefinition);
174+
}
175+
}
176+
177+
return models;
178+
}
179+
154180
@NotNull
155181
public static Collection<Pair<String, PsiElement>> getTables(@NotNull Project project) {
156182

src/fr/adrienbrault/idea/symfony2plugin/util/EventDispatcherTypeProvider.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,7 @@ public char getKey() {
3030
@Nullable
3131
@Override
3232
public PhpType getType(PsiElement e) {
33-
34-
if (DumbService.getInstance(e.getProject()).isDumb() || !Settings.getInstance(e.getProject()).pluginEnabled || !Settings.getInstance(e.getProject()).symfonyContainerTypeProvider) {
33+
if (!Settings.getInstance(e.getProject()).pluginEnabled || !Settings.getInstance(e.getProject()).symfonyContainerTypeProvider) {
3534
return null;
3635
}
3736

src/fr/adrienbrault/idea/symfony2plugin/util/PhpElementsUtil.java

+26
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,32 @@ public static Set<Variable> getVariablesInScope(@NotNull PsiElement psiElement,
13891389
return MyVariableRecursiveElementVisitor.visit(psiElement, name);
13901390
}
13911391

1392+
/**
1393+
* Provide array key pattern. we need incomplete array key support, too.
1394+
*
1395+
* foo(['<caret>'])
1396+
* foo(['<caret>' => 'foobar'])
1397+
*/
1398+
@NotNull
1399+
public static PsiElementPattern.Capture<PsiElement> getParameterListArrayValuePattern() {
1400+
return PlatformPatterns.psiElement()
1401+
.withParent(PlatformPatterns.psiElement(StringLiteralExpression.class).withParent(
1402+
PlatformPatterns.or(
1403+
PlatformPatterns.psiElement().withElementType(PhpElementTypes.ARRAY_VALUE)
1404+
.withParent(PlatformPatterns.psiElement(ArrayCreationExpression.class)
1405+
.withParent(ParameterList.class)
1406+
),
1407+
1408+
PlatformPatterns.psiElement().withElementType(PhpElementTypes.ARRAY_KEY)
1409+
.withParent(PlatformPatterns.psiElement(ArrayHashElement.class)
1410+
.withParent(PlatformPatterns.psiElement(ArrayCreationExpression.class)
1411+
.withParent(ParameterList.class)
1412+
)
1413+
)
1414+
))
1415+
);
1416+
}
1417+
13921418
/**
13931419
* Visit and collect all variables in given scope
13941420
*/

tests/fr/adrienbrault/idea/symfony2plugin/tests/config/ServiceLineMarkerProviderTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class ServiceLineMarkerProviderTest extends SymfonyLightCodeInsightFixtur
2828

2929
public void setUp() throws Exception {
3030
super.setUp();
31-
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("SymfonyPhpReferenceContributor.php"));
31+
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("ServiceLineMarkerProvider.php"));
3232
}
3333

3434
public String getTestDataPath() {

tests/fr/adrienbrault/idea/symfony2plugin/tests/config/SymfonyPhpReferenceContributorTest.java

+1-29
Original file line numberDiff line numberDiff line change
@@ -14,42 +14,14 @@ public class SymfonyPhpReferenceContributorTest extends SymfonyLightCodeInsightF
1414

1515
public void setUp() throws Exception {
1616
super.setUp();
17-
myFixture.copyFileToProject("SymfonyPhpReferenceContributor.php");
1817
myFixture.copyFileToProject("services.xml");
18+
myFixture.copyFileToProject("ServiceLineMarkerProvider.php");
1919
}
2020

2121
public String getTestDataPath() {
2222
return new File(this.getClass().getResource("fixtures").getFile()).getAbsolutePath();
2323
}
2424

25-
/**
26-
* @see fr.adrienbrault.idea.symfony2plugin.doctrine.ModelFieldReference
27-
*/
28-
public void testModelFieldReference() {
29-
for (String s : new String[]{"findBy", "findOneBy"}) {
30-
assertCompletionContains(PhpFileType.INSTANCE, "<?php" +
31-
"/** @var $em \\Doctrine\\Common\\Persistence\\ObjectManager */\n" +
32-
"$em->getRepository('Foo\\Bar')->" + s + "(['<caret>'])",
33-
"phonenumbers", "email"
34-
);
35-
36-
assertCompletionContains(PhpFileType.INSTANCE, "<?php" +
37-
"/** @var $em \\Doctrine\\Common\\Persistence\\ObjectManager */\n" +
38-
"$em->getRepository('Foo\\Bar')->" + s + "(['foo', '<caret>' => 'foo'])",
39-
"phonenumbers", "email"
40-
);
41-
42-
assertCompletionContains(PhpFileType.INSTANCE, "<?php" +
43-
"/** @var $em \\Doctrine\\Common\\Persistence\\ObjectManager */\n" +
44-
"$em->getRepository('Foo\\Bar')->" + s + "(['foo' => 'foo', '<caret>' => 'foo'])",
45-
"phonenumbers", "email"
46-
);
47-
48-
// migrate: @TODO: fr.adrienbrault.idea.symfony2plugin.doctrine.ModelFieldReference.multiResolve()
49-
// add navigation testing
50-
}
51-
}
52-
5325
public void testThatPrivateServiceAreNotInCompletionListForContainerGet() {
5426
assertCompletionContains(PhpFileType.INSTANCE, "<?php" +
5527
"/** @var $c \\Symfony\\Component\\DependencyInjection\\ContainerInterface */\n" +

0 commit comments

Comments
 (0)