From 88be50493c7a00e4803212cb02400ed59114aa40 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 13 Apr 2013 10:36:49 +0200 Subject: [PATCH 01/13] doctrine getRepository now knows its entities and supports autocomplete and clicking --- META-INF/plugin.xml | 1 + .../SymfonyInterfacesHelper.java | 8 ++ .../DoctrineEntityReferenceContributor.java | 45 +++++++++++ .../symfony2plugin/doctrine/EntityHelper.java | 65 ++++++++++++++++ .../doctrine/EntityReference.java | 78 +++++++++++++++++++ .../dict/DoctrineEntityLookupElement.java | 36 +++++++++ 6 files changed, 233 insertions(+) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 40027f0ca..dd608ba88 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -94,6 +94,7 @@ + diff --git a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java index 92bf9d223..89e518895 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java @@ -26,6 +26,14 @@ public static boolean isTemplatingRenderCall(PsiElement e) { }); } + public static boolean isRepositoryCall(PsiElement e) { + // EntityManager is needed for symfony 2.0? + return isCallTo(e, new String[] { + "\\Doctrine\\ORM\\EntityManager.getRepository", + "\\Doctrine\\Common\\Persistence\\ObjectManager.getRepository" + }); + } + private static boolean isCallTo(PsiElement e, String expectedMethodFQN) { return isCallTo(e, new String[] { expectedMethodFQN }, 1); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java new file mode 100644 index 000000000..a69c56d3c --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java @@ -0,0 +1,45 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine; + +import com.intellij.patterns.PlatformPatterns; +import com.intellij.psi.*; +import com.intellij.util.ProcessingContext; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.ParameterList; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import org.jetbrains.annotations.NotNull; + +/** + * @author Daniel Espendiller + */ +public class DoctrineEntityReferenceContributor extends PsiReferenceContributor { + + @Override + public void registerReferenceProviders(PsiReferenceRegistrar psiReferenceRegistrar) { + psiReferenceRegistrar.registerReferenceProvider( + PlatformPatterns.psiElement(StringLiteralExpression.class), + new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @NotNull ProcessingContext processingContext) { + if (!(psiElement.getContext() instanceof ParameterList)) { + return new PsiReference[0]; + } + ParameterList parameterList = (ParameterList) psiElement.getContext(); + + if (!(parameterList.getContext() instanceof MethodReference)) { + return new PsiReference[0]; + } + MethodReference method = (MethodReference) parameterList.getContext(); + + if (!SymfonyInterfacesHelper.isRepositoryCall(method)) { + return new PsiReference[0]; + } + + return new PsiReference[]{ new EntityReference((StringLiteralExpression) psiElement) }; + } + } + ); + } + +} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java new file mode 100644 index 000000000..388db0ac4 --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java @@ -0,0 +1,65 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine; + +import com.intellij.openapi.project.Project; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.psi.elements.PhpClass; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Daniel Espendiller + */ +public class EntityHelper { + + /** + * + * @param project PHPStorm projects + * @param shortcutName name as MyBundle\Entity\Model or MyBundle:Model + * @return null|PhpClass + */ + public static PhpClass resolveShortcutName(Project project, String shortcutName) { + + PhpIndex phpIndex = PhpIndex.getInstance(project); + Collection phpClasses = phpIndex.getAllSubclasses("\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle"); + + Map bundlesDirectories = new HashMap(); + for (PhpClass phpClass : phpClasses) { + bundlesDirectories.put(phpClass.getName(), phpClass.getNamespaceName()); + } + + String entity_name = null; + + // resolve: + // MyBundle:Model -> MyBundle\Entity\Model + // MyBundle:Folder\Model -> MyBundle\Entity\Folder\Model + if (shortcutName.contains(":")) { + + int firstDirectorySeparatorIndex = shortcutName.indexOf(":"); + + String bundlename = shortcutName.substring(0, firstDirectorySeparatorIndex); + String entityName = shortcutName.substring(firstDirectorySeparatorIndex + 1); + + String namespace = bundlesDirectories.get(bundlename); + + if(namespace == null) { + return null; + } + + entity_name = namespace + "Entity\\" + entityName; + + } else { + entity_name = shortcutName; + } + + // dont we have any unique class getting method here? + Collection entity_classes = phpIndex.getClassesByFQN(entity_name); + if(!entity_classes.isEmpty()){ + return entity_classes.iterator().next(); + } + + return null; + } + +} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java new file mode 100644 index 000000000..09870f95d --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java @@ -0,0 +1,78 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine; + +import com.intellij.psi.*; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.PhpIndex; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.elements.PhpNamespace; +import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineEntityLookupElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Daniel Espendiller + */ +public class EntityReference extends PsiReferenceBase implements PsiReference { + private String entityName; + + public EntityReference(@NotNull StringLiteralExpression element) { + super(element); + + entityName = element.getText().substring( + element.getValueRange().getStartOffset(), + element.getValueRange().getEndOffset() + ); + } + + @Nullable + @Override + public PsiElement resolve() { + + PhpClass entity = EntityHelper.resolveShortcutName(getElement().getProject(), this.entityName); + if(entity != null) { + return new PsiElementResolveResult(entity).getElement(); + } + + return null; + } + + @NotNull + @Override + public Object[] getVariants() { + + PhpIndex phpIndex = PhpIndex.getInstance(getElement().getProject()); + Collection phpClasses = phpIndex.getAllSubclasses("\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle"); + + List results = new ArrayList(); + for (PhpClass phpClass : phpClasses) { + + // search for classes that match the symfony2 namings + String ns = phpClass.getNamespaceName() + "Entity"; + Collection entities = phpIndex.getNamespacesByName(ns); + + // @TODO: it looks like PhpIndex cant search for classes like \ns\Path\*\... + // temporary only use flat entities and dont support "MyBundle:Folder\Entity" + for (PhpNamespace entity_files : entities) { + + // build our symfony2 shortcut + System.out.println(entity_files.getContainingFile().getContainingDirectory()); + String filename = entity_files.getContainingFile().getName(); + String className = filename.substring(0, filename.lastIndexOf('.')); + String repoName = phpClass.getName() + ':' + className; + + for (PhpClass entity_phpclass : phpIndex.getClassesByFQN(ns + "\\" + className)) { + results.add(new DoctrineEntityLookupElement(repoName, entity_phpclass)); + } + + } + + } + + return results.toArray(); + } + +} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java new file mode 100644 index 000000000..9f5e2278e --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java @@ -0,0 +1,36 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine.dict; + +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementPresentation; +import com.jetbrains.php.PhpIcons; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import org.jetbrains.annotations.NotNull; + +/** + * @author Daniel Espendiller + */ +public class DoctrineEntityLookupElement extends LookupElement { + + private String entityName; + private PhpClass className; + + public DoctrineEntityLookupElement(String entityName, PhpClass className) { + this.entityName = entityName; + this.className = className; + } + + @NotNull + @Override + public String getLookupString() { + return entityName; + } + + public void renderElement(LookupElementPresentation presentation) { + presentation.setItemText(getLookupString()); + presentation.setTypeText(className.getPresentableFQN()); + presentation.setTypeGrayed(true); + presentation.setIcon(PhpIcons.CLASS); + } + +} + From dd46f64c33fa4844f06ccafabbe34b0babcd0296 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 13 Apr 2013 11:04:49 +0200 Subject: [PATCH 02/13] dont add Repository and abstract entities to doctrine autocomplete --- .../idea/symfony2plugin/doctrine/EntityReference.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java index 09870f95d..cd0b742e9 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java @@ -64,8 +64,13 @@ public Object[] getVariants() { String className = filename.substring(0, filename.lastIndexOf('.')); String repoName = phpClass.getName() + ':' + className; - for (PhpClass entity_phpclass : phpIndex.getClassesByFQN(ns + "\\" + className)) { - results.add(new DoctrineEntityLookupElement(repoName, entity_phpclass)); + // dont add Repository classes and abstract entities + if(!className.endsWith("Repository")) { + for (PhpClass entityClass : phpIndex.getClassesByFQN(ns + "\\" + className)) { + if(!entityClass.isAbstract()) { + results.add(new DoctrineEntityLookupElement(repoName, entityClass)); + } + } } } From a6fbfe1d26ae7b7ceed350e78f0ac5c68c312cc0 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 13 Apr 2013 14:01:12 +0200 Subject: [PATCH 03/13] repository classes are used as return types for getRepository, if they exists in the same folder as the entity --- META-INF/plugin.xml | 1 + .../ObjectRepositoryTypeProvider.java | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index dd608ba88..971abdf2b 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -92,6 +92,7 @@ + diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java new file mode 100644 index 000000000..26de68d8a --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java @@ -0,0 +1,44 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine; + +import com.intellij.openapi.project.DumbService; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.resolve.types.PhpType; +import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider; +import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import org.jetbrains.annotations.Nullable; + +/** + * @author Daniel Espendiller + */ +public class ObjectRepositoryTypeProvider implements PhpTypeProvider { + + + @Nullable + @Override + public PhpType getType(PsiElement e) { + if (DumbService.getInstance(e.getProject()).isDumb()) { + return null; + } + + if (!SymfonyInterfacesHelper.isRepositoryCall(e)) { + return null; + } + + String repositoryName = SymfonyInterfacesHelper.getFirstArgumentStringValue((MethodReference) e); + if (null == repositoryName) { + return null; + } + + // @TODO: parse xml or yml for repositoryClass? + PhpClass phpClass = EntityHelper.resolveShortcutName(e.getProject(), repositoryName + "Repository"); + + if(phpClass == null) { + return null; + } + + return new PhpType().add(phpClass); + } + +} From 059b0c1647856e356483d2ded0828895d2f0fac0 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 13 Apr 2013 15:33:50 +0200 Subject: [PATCH 04/13] find and findOneBy returns the entity --- META-INF/plugin.xml | 1 + .../SymfonyInterfacesHelper.java | 10 +++- .../ObjectRepositoryResultTypeProvider.java | 52 +++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java diff --git a/META-INF/plugin.xml b/META-INF/plugin.xml index 971abdf2b..4606a3c0d 100644 --- a/META-INF/plugin.xml +++ b/META-INF/plugin.xml @@ -93,6 +93,7 @@ + diff --git a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java index 89e518895..04f84fa6a 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java @@ -30,7 +30,15 @@ public static boolean isRepositoryCall(PsiElement e) { // EntityManager is needed for symfony 2.0? return isCallTo(e, new String[] { "\\Doctrine\\ORM\\EntityManager.getRepository", - "\\Doctrine\\Common\\Persistence\\ObjectManager.getRepository" + "\\Doctrine\\Common\\Persistence\\ObjectManager.getRepository", + "\\Doctrine\\ORM\\EntityRepository.getRepository", + }); + } + + public static boolean isObjectRepositoryCall(PsiElement e) { + return isCallTo(e, new String[] { + "\\Doctrine\\Common\\Persistence\\ObjectRepository.find", + "\\Doctrine\\Common\\Persistence\\ObjectRepository.findOneBy", }); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java new file mode 100644 index 000000000..8fc041c46 --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java @@ -0,0 +1,52 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine; + +import com.intellij.openapi.project.DumbService; +import com.intellij.psi.PsiElement; +import com.jetbrains.php.lang.psi.elements.MethodReference; +import com.jetbrains.php.lang.psi.elements.PhpClass; +import com.jetbrains.php.lang.psi.resolve.types.PhpType; +import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider; +import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import org.jetbrains.annotations.Nullable; + +/** + * @author Daniel Espendiller + */ +public class ObjectRepositoryResultTypeProvider implements PhpTypeProvider { + + @Nullable + @Override + public PhpType getType(PsiElement e) { + if (DumbService.getInstance(e.getProject()).isDumb()) { + return null; + } + + // @TODO: if ObjectRepositoryTypeProvider overwrites the Repository we dont get called + if (!SymfonyInterfacesHelper.isObjectRepositoryCall(e)) { + return null; + } + + MethodReference met = (MethodReference) e; + + // at least one parameter is necessary + PsiElement[] parameters = met.getParameters(); + if(parameters.length == 0) { + return null; + } + + // find the called repository name on method before + if(!(met.getFirstChild() instanceof MethodReference)) { + return null; + } + + String repositoryName = SymfonyInterfacesHelper.getFirstArgumentStringValue((MethodReference) met.getFirstChild()); + PhpClass phpClass = EntityHelper.resolveShortcutName(e.getProject(), repositoryName); + + if(phpClass == null) { + return null; + } + + return new PhpType().add(phpClass); + } + +} From 60ed4397322e27a167fcafcc034a3a01d9242124 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sat, 13 Apr 2013 20:34:33 +0200 Subject: [PATCH 05/13] get available entitynamespaces directly from compiled container xml file and cache data --- .../Symfony2ProjectComponent.java | 36 +++++++++ .../symfony2plugin/doctrine/EntityHelper.java | 26 +++---- .../doctrine/EntityReference.java | 15 ++-- .../doctrine/component/EntityNamesParser.java | 73 +++++++++++++++++++ .../component/EntityNamesParserTest.java | 25 +++++++ .../component/appDevDebugProjectContainer.xml | 43 +++++++++++ 6 files changed, 197 insertions(+), 21 deletions(-) create mode 100644 src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java create mode 100644 tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java create mode 100644 tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java index 70225d1d6..88268b232 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java @@ -6,12 +6,15 @@ import com.intellij.openapi.vfs.VfsUtil; import fr.adrienbrault.idea.symfony2plugin.dic.ServiceMap; import fr.adrienbrault.idea.symfony2plugin.dic.ServiceMapParser; +import fr.adrienbrault.idea.symfony2plugin.doctrine.component.EntityNamesParser; import org.jetbrains.annotations.NotNull; import org.xml.sax.SAXException; import javax.xml.parsers.ParserConfigurationException; import java.io.File; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; /** * @author Adrien Brault @@ -22,6 +25,9 @@ public class Symfony2ProjectComponent implements ProjectComponent { private ServiceMap servicesMap; private Long servicesMapLastModified; + private Long entityNamespacesMapLastModified; + private Map entityNamespaces; + public Symfony2ProjectComponent(Project project) { this.project = project; } @@ -77,4 +83,34 @@ public ServiceMap getServicesMap() { return new ServiceMap(); } + public Map getEntityNamespacesMap() { + String defaultServiceMapFilePath = Settings.getInstance(project).pathToProjectContainer; + if (!FileUtil.isAbsolute(defaultServiceMapFilePath)) { // Project relative path + defaultServiceMapFilePath = project.getBasePath() + "/" + defaultServiceMapFilePath; + } + + File xmlFile = new File(defaultServiceMapFilePath); + if (!xmlFile.exists()) { + return new HashMap(); + } + + Long xmlFileLastModified = xmlFile.lastModified(); + if (xmlFileLastModified.equals(entityNamespacesMapLastModified)) { + return entityNamespaces; + } + + try { + EntityNamesParser entityNamesParser = new EntityNamesParser(); + entityNamespaces = entityNamesParser.parse(xmlFile); + entityNamespacesMapLastModified = xmlFileLastModified; + + return entityNamespaces; + } catch (SAXException ignored) { + } catch (IOException ignored) { + } catch (ParserConfigurationException ignored) { + } + + return new HashMap(); + } + } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java index 388db0ac4..328df60c0 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java @@ -3,9 +3,9 @@ import com.intellij.openapi.project.Project; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.PhpClass; +import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import java.util.Collection; -import java.util.HashMap; import java.util.Map; /** @@ -21,39 +21,37 @@ public class EntityHelper { */ public static PhpClass resolveShortcutName(Project project, String shortcutName) { - PhpIndex phpIndex = PhpIndex.getInstance(project); - Collection phpClasses = phpIndex.getAllSubclasses("\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle"); - - Map bundlesDirectories = new HashMap(); - for (PhpClass phpClass : phpClasses) { - bundlesDirectories.put(phpClass.getName(), phpClass.getNamespaceName()); - } - - String entity_name = null; + String entity_name = shortcutName; // resolve: // MyBundle:Model -> MyBundle\Entity\Model // MyBundle:Folder\Model -> MyBundle\Entity\Folder\Model if (shortcutName.contains(":")) { + Symfony2ProjectComponent symfony2ProjectComponent = project.getComponent(Symfony2ProjectComponent.class); + Map em = symfony2ProjectComponent.getEntityNamespacesMap(); + int firstDirectorySeparatorIndex = shortcutName.indexOf(":"); String bundlename = shortcutName.substring(0, firstDirectorySeparatorIndex); String entityName = shortcutName.substring(firstDirectorySeparatorIndex + 1); - String namespace = bundlesDirectories.get(bundlename); + String namespace = em.get(bundlename); if(namespace == null) { return null; } - entity_name = namespace + "Entity\\" + entityName; + entity_name = namespace + "\\" + entityName; + } - } else { - entity_name = shortcutName; + // only use them on entity namespace + if(!entity_name.contains("\\")) { + return null; } // dont we have any unique class getting method here? + PhpIndex phpIndex = PhpIndex.getInstance(project); Collection entity_classes = phpIndex.getClassesByFQN(entity_name); if(!entity_classes.isEmpty()){ return entity_classes.iterator().next(); diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java index cd0b742e9..a2da481c6 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java @@ -7,6 +7,7 @@ import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.elements.PhpNamespace; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineEntityLookupElement; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -45,28 +46,28 @@ public PsiElement resolve() { public Object[] getVariants() { PhpIndex phpIndex = PhpIndex.getInstance(getElement().getProject()); - Collection phpClasses = phpIndex.getAllSubclasses("\\Symfony\\Component\\HttpKernel\\Bundle\\Bundle"); + + Symfony2ProjectComponent symfony2ProjectComponent = getElement().getProject().getComponent(Symfony2ProjectComponent.class); + Map em = symfony2ProjectComponent.getEntityNamespacesMap(); List results = new ArrayList(); - for (PhpClass phpClass : phpClasses) { + for (String shortcutName : em.keySet()) { // search for classes that match the symfony2 namings - String ns = phpClass.getNamespaceName() + "Entity"; - Collection entities = phpIndex.getNamespacesByName(ns); + Collection entities = phpIndex.getNamespacesByName(em.get(shortcutName)); // @TODO: it looks like PhpIndex cant search for classes like \ns\Path\*\... // temporary only use flat entities and dont support "MyBundle:Folder\Entity" for (PhpNamespace entity_files : entities) { // build our symfony2 shortcut - System.out.println(entity_files.getContainingFile().getContainingDirectory()); String filename = entity_files.getContainingFile().getName(); String className = filename.substring(0, filename.lastIndexOf('.')); - String repoName = phpClass.getName() + ':' + className; + String repoName = shortcutName + ':' + className; // dont add Repository classes and abstract entities if(!className.endsWith("Repository")) { - for (PhpClass entityClass : phpIndex.getClassesByFQN(ns + "\\" + className)) { + for (PhpClass entityClass : phpIndex.getClassesByFQN(em.get(shortcutName) + "\\" + className)) { if(!entityClass.isAbstract()) { results.add(new DoctrineEntityLookupElement(repoName, entityClass)); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java new file mode 100644 index 000000000..baf5e26eb --- /dev/null +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java @@ -0,0 +1,73 @@ +package fr.adrienbrault.idea.symfony2plugin.doctrine.component; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; +import javax.xml.parsers.DocumentBuilder; + +/** + * @author Daniel Espendiller + */ +public class EntityNamesParser { + + private DocumentBuilder documentBuilder; + + public EntityNamesParser() throws ParserConfigurationException { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + documentBuilder = dbFactory.newDocumentBuilder(); + } + + public Map parse(InputStream stream) throws IOException, SAXException { + return parse(documentBuilder.parse(stream)); + } + + public Map parse(File file) throws IOException, SAXException { + return parse(documentBuilder.parse(file)); + } + + public Map parse(Document document) { + Map map = new HashMap(); + + // any way to this better? + NodeList servicesNodes = document.getElementsByTagName("service"); + for (int i = 0; i < servicesNodes.getLength(); i++) { + Element node = (Element) servicesNodes.item(i); + + // doctrine.orm.default_entity_manager + // doctrine.orm.customer_entity_manager + if (node.hasAttribute("class") && node.hasAttribute("id") && node.getAttribute("id").startsWith("doctrine.orm") && node.getAttribute("id").endsWith("_entity_manager")) { + + // + NodeList calls = document.getElementsByTagName("call"); + for (int x = 0; x < calls.getLength(); x++) { + + Element call = (Element) calls.item(x); + if (call.hasAttribute("method") && call.getAttribute("method").equals("setEntityNamespaces")) { + + // HomeBundle\Entity + NodeList arguments = call.getElementsByTagName("argument"); + for (int y = 0; y < arguments.getLength(); y++) { + Element arg = (Element) arguments.item(y); + if (arg.hasAttribute("key")) { + map.put(arg.getAttribute("key"), "\\" + arg.getTextContent()); + } + } + } + + } + } + } + + return map; + } + +} diff --git a/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java b/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java new file mode 100644 index 000000000..f1de38154 --- /dev/null +++ b/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java @@ -0,0 +1,25 @@ +package fr.adrienbrault.idea.symfony2plugin.tests.dic.doctrine.component; + +import fr.adrienbrault.idea.symfony2plugin.doctrine.component.EntityNamesParser; +import org.junit.Assert; +import org.junit.Test; +import java.io.File; +import java.util.Map; + +/** + * @author Daniel Espendiller + */ +public class EntityNamesParserTest extends Assert { + + @Test + public void testParse() throws Exception { + + File testFile = new File(this.getClass().getResource("appDevDebugProjectContainer.xml").getFile()); + Map map = new EntityNamesParser().parse(testFile); + + assertEquals("\\My\\NiceBundle\\Entity", map.get("MyNiceBundle")); + assertEquals("\\Your\\TestBundle\\Entity", map.get("YourTestBundle")); + } + +} + diff --git a/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml b/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml new file mode 100644 index 000000000..2ee73be4c --- /dev/null +++ b/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml @@ -0,0 +1,43 @@ + + + + + /EntityManager_516982d26ada1.php + + + + + + + + My\NiceBundle\Entity + + + + + + + + + + + EntityManager_516982d26ada1.php + + + + + + + + Your\TestBundle\Entity + + + + + + + + + + + From b42231a820ae7abca3f84a92aca4f91a46ee5b23 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sun, 14 Apr 2013 18:26:01 +0200 Subject: [PATCH 06/13] use new Symfony2InterfacesUtil, which can check for interface, too. now Repository classes are working fine --- .../Symfony2InterfacesUtil.java | 17 ++++++++++ .../Symfony2ProjectComponent.java | 32 +++++++++++++++++++ .../DoctrineEntityReferenceContributor.java | 6 ++-- .../symfony2plugin/doctrine/EntityHelper.java | 4 +++ .../ObjectRepositoryResultTypeProvider.java | 9 +++--- .../ObjectRepositoryTypeProvider.java | 7 ++-- 6 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java index f3f3ae1d8..e8d387592 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java @@ -40,6 +40,23 @@ public boolean isUrlGeneratorGenerateCall(PsiElement e) { }); } + public boolean isRepositoryCall(PsiElement e) { + // EntityManager is needed for symfony 2.0? + return isCallTo(e, new Method[] { + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectManager", "getRepository"), + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ManagerRegistry", "getRepository"), + getClassMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\AbstractManagerRegistry", "getRepository"), + getClassMethod(e.getProject(), "\\Doctrine\\ORM\\EntityRepository", "getRepository"), + }); + } + + public boolean isObjectRepositoryCall(PsiElement e) { + return isCallTo(e, new Method[] { + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "find"), + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findOneBy"), + }); + } + protected boolean isCallTo(PsiElement e, Method expectedMethod) { return isCallTo(e, new Method[] { expectedMethod }); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java index 1f6014dfa..d08bd268c 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2ProjectComponent.java @@ -7,6 +7,7 @@ import com.intellij.openapi.vfs.VirtualFile; import fr.adrienbrault.idea.symfony2plugin.dic.ServiceMap; import fr.adrienbrault.idea.symfony2plugin.dic.ServiceMapParser; +import fr.adrienbrault.idea.symfony2plugin.doctrine.component.EntityNamesParser; import fr.adrienbrault.idea.symfony2plugin.routing.Route; import org.jetbrains.annotations.NotNull; import org.xml.sax.SAXException; @@ -32,6 +33,9 @@ public class Symfony2ProjectComponent implements ProjectComponent { private Map routes; private Long routesLastModified; + private Long entityNamespacesMapLastModified; + private Map entityNamespaces; + public Symfony2ProjectComponent(Project project) { this.project = project; } @@ -138,4 +142,32 @@ private String getPath(Project project, String path) { return path; } + public Map getEntityNamespacesMap() { + + String defaultServiceMapFilePath = getPath(project, Settings.getInstance(project).pathToProjectContainer); + + File xmlFile = new File(defaultServiceMapFilePath); + if (!xmlFile.exists()) { + return new HashMap(); + } + + Long xmlFileLastModified = xmlFile.lastModified(); + if (xmlFileLastModified.equals(entityNamespacesMapLastModified)) { + return entityNamespaces; + } + + try { + EntityNamesParser entityNamesParser = new EntityNamesParser(); + entityNamespaces = entityNamesParser.parse(xmlFile); + entityNamespacesMapLastModified = xmlFileLastModified; + + return entityNamespaces; + } catch (SAXException ignored) { + } catch (IOException ignored) { + } catch (ParserConfigurationException ignored) { + } + + return new HashMap(); + } + } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java index a69c56d3c..337df0105 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java @@ -6,7 +6,7 @@ import com.jetbrains.php.lang.psi.elements.MethodReference; import com.jetbrains.php.lang.psi.elements.ParameterList; import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; -import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; import org.jetbrains.annotations.NotNull; /** @@ -31,8 +31,8 @@ public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @No return new PsiReference[0]; } MethodReference method = (MethodReference) parameterList.getContext(); - - if (!SymfonyInterfacesHelper.isRepositoryCall(method)) { + Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); + if (!interfacesUtil.isRepositoryCall(method)) { return new PsiReference[0]; } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java index 328df60c0..a7e25925a 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityHelper.java @@ -21,6 +21,10 @@ public class EntityHelper { */ public static PhpClass resolveShortcutName(Project project, String shortcutName) { + if(shortcutName == null) { + return null; + } + String entity_name = shortcutName; // resolve: diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java index 8fc041c46..b695bd754 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java @@ -6,7 +6,7 @@ import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider; -import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; import org.jetbrains.annotations.Nullable; /** @@ -21,8 +21,8 @@ public PhpType getType(PsiElement e) { return null; } - // @TODO: if ObjectRepositoryTypeProvider overwrites the Repository we dont get called - if (!SymfonyInterfacesHelper.isObjectRepositoryCall(e)) { + Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); + if (!interfacesUtil.isObjectRepositoryCall(e)) { return null; } @@ -34,12 +34,13 @@ public PhpType getType(PsiElement e) { return null; } + // @TODO: find the previously defined type instead of try it on the parameter, we now can rely on it! // find the called repository name on method before if(!(met.getFirstChild() instanceof MethodReference)) { return null; } - String repositoryName = SymfonyInterfacesHelper.getFirstArgumentStringValue((MethodReference) met.getFirstChild()); + String repositoryName = Symfony2InterfacesUtil.getFirstArgumentStringValue((MethodReference) met.getFirstChild()); PhpClass phpClass = EntityHelper.resolveShortcutName(e.getProject(), repositoryName); if(phpClass == null) { diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java index 26de68d8a..634364477 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java @@ -6,7 +6,7 @@ import com.jetbrains.php.lang.psi.elements.PhpClass; import com.jetbrains.php.lang.psi.resolve.types.PhpType; import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider; -import fr.adrienbrault.idea.symfony2plugin.SymfonyInterfacesHelper; +import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; import org.jetbrains.annotations.Nullable; /** @@ -22,11 +22,12 @@ public PhpType getType(PsiElement e) { return null; } - if (!SymfonyInterfacesHelper.isRepositoryCall(e)) { + Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); + if (!interfacesUtil.isRepositoryCall(e)) { return null; } - String repositoryName = SymfonyInterfacesHelper.getFirstArgumentStringValue((MethodReference) e); + String repositoryName = Symfony2InterfacesUtil.getFirstArgumentStringValue((MethodReference) e); if (null == repositoryName) { return null; } From a9f63cc8b81a34ea6c89bf92dfb956946d664914 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sun, 14 Apr 2013 19:28:53 +0200 Subject: [PATCH 07/13] adding support for doctrine findAll and findBy --- .../symfony2plugin/Symfony2InterfacesUtil.java | 2 ++ .../ObjectRepositoryResultTypeProvider.java | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java index e8d387592..a5f426d83 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java @@ -54,6 +54,8 @@ public boolean isObjectRepositoryCall(PsiElement e) { return isCallTo(e, new Method[] { getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "find"), getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findOneBy"), + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findAll"), + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectRepository", "findBy"), }); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java index b695bd754..6a69b499d 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryResultTypeProvider.java @@ -27,11 +27,14 @@ public PhpType getType(PsiElement e) { } MethodReference met = (MethodReference) e; - - // at least one parameter is necessary - PsiElement[] parameters = met.getParameters(); - if(parameters.length == 0) { - return null; + String methodName = met.getName(); + + // at least one parameter is necessary on some finds + if(null != methodName && !methodName.equals("findAll")) { + PsiElement[] parameters = met.getParameters(); + if(parameters.length == 0) { + return null; + } } // @TODO: find the previously defined type instead of try it on the parameter, we now can rely on it! @@ -47,6 +50,11 @@ public PhpType getType(PsiElement e) { return null; } + + if(null != methodName && (methodName.equals("findAll") || methodName.equals("findBy"))) { + return new PhpType().add(phpClass.getFQN() + "[]"); + } + return new PhpType().add(phpClass); } From 8bb6f315a99144a6245dc0951937b1b2e1391322 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Mon, 15 Apr 2013 19:11:20 +0200 Subject: [PATCH 08/13] cleaning up Repository calls to use only the interface, revert adding SymfonyInterfacesHelper --- .../Symfony2InterfacesUtil.java | 6 +- .../SymfonyInterfacesHelper.java | 121 ------------------ .../DoctrineEntityReferenceContributor.java | 2 +- .../ObjectRepositoryTypeProvider.java | 2 +- 4 files changed, 3 insertions(+), 128 deletions(-) delete mode 100644 src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java index a5f426d83..3853d0ec2 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java @@ -40,13 +40,9 @@ public boolean isUrlGeneratorGenerateCall(PsiElement e) { }); } - public boolean isRepositoryCall(PsiElement e) { - // EntityManager is needed for symfony 2.0? + public boolean isGetRepositoryCall(PsiElement e) { return isCallTo(e, new Method[] { - getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectManager", "getRepository"), getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ManagerRegistry", "getRepository"), - getClassMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\AbstractManagerRegistry", "getRepository"), - getClassMethod(e.getProject(), "\\Doctrine\\ORM\\EntityRepository", "getRepository"), }); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java b/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java deleted file mode 100644 index 04f84fa6a..000000000 --- a/src/fr/adrienbrault/idea/symfony2plugin/SymfonyInterfacesHelper.java +++ /dev/null @@ -1,121 +0,0 @@ -package fr.adrienbrault.idea.symfony2plugin; - -import com.intellij.psi.PsiElement; -import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpInstruction; -import com.jetbrains.php.codeInsight.controlFlow.instructions.PhpReturnInstruction; -import com.jetbrains.php.lang.psi.elements.Method; -import com.jetbrains.php.lang.psi.elements.MethodReference; -import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; - -/** - * @author Adrien Brault - */ -public class SymfonyInterfacesHelper { - - public static boolean isContainerGetCall(PsiElement e) { - return isCallTo(e, "\\Symfony\\Component\\DependencyInjection\\ContainerInterface.get"); - } - - public static boolean isTemplatingRenderCall(PsiElement e) { - return isCallTo(e, new String[] { - "\\Symfony\\Component\\Templating\\EngineInterface.render", - "\\Symfony\\Bridge\\Twig\\TwigEngine.render", - "\\Symfony\\Bundle\\TwigBundle\\TwigEngine.render", - "\\Symfony\\Bundle\\TwigBundle\\TwigEngine.renderResponse", - "\\Symfony\\Bundle\\TwigBundle\\EngineInterface.renderResponse" - }); - } - - public static boolean isRepositoryCall(PsiElement e) { - // EntityManager is needed for symfony 2.0? - return isCallTo(e, new String[] { - "\\Doctrine\\ORM\\EntityManager.getRepository", - "\\Doctrine\\Common\\Persistence\\ObjectManager.getRepository", - "\\Doctrine\\ORM\\EntityRepository.getRepository", - }); - } - - public static boolean isObjectRepositoryCall(PsiElement e) { - return isCallTo(e, new String[] { - "\\Doctrine\\Common\\Persistence\\ObjectRepository.find", - "\\Doctrine\\Common\\Persistence\\ObjectRepository.findOneBy", - }); - } - - private static boolean isCallTo(PsiElement e, String expectedMethodFQN) { - return isCallTo(e, new String[] { expectedMethodFQN }, 1); - } - - private static boolean isCallTo(PsiElement e, String[] expectedMethodFQNs) { - return isCallTo(e, expectedMethodFQNs, 1); - } - - private static boolean isCallTo(PsiElement e, String[] expectedMethodFQNs, int deepness) { - if (!(e instanceof MethodReference)) { - return false; - } - - MethodReference methodRef = (MethodReference) e; - if (null == e.getReference()) { - return false; - } - - PsiElement resolvedReference = methodRef.getReference().resolve(); - if (!(resolvedReference instanceof Method)) { - return false; - } - - Method method = (Method) resolvedReference; - String methodFQN = method.getFQN(); // Something like "\Symfony\Bundle\FrameworkBundle\Controller\Controller.get" - if (null == methodFQN) { - return false; - } - - for (int i = 0; i < expectedMethodFQNs.length; i++) { - String expectedMethodFQN = expectedMethodFQNs[i]; - - if (methodFQN.equals(expectedMethodFQN)) { - return true; - } - } - - if (deepness > 3) { - return false; - } - - // Try to see if this method return expression is a method call to a ContainerInterface::get ... recursive! - - PhpInstruction[] instructions = method.getControlFlow().getInstructions(); - for (int i = 0; i < instructions.length; i++) { - PhpInstruction instruction = instructions[i]; - - if (instruction instanceof PhpReturnInstruction) { - PhpReturnInstruction returnInstruction = (PhpReturnInstruction) instruction; - - PsiElement returnInstructionElement = returnInstruction.getArgument(); - if (null != returnInstructionElement && - null != returnInstructionElement.getReference() && - returnInstructionElement.getReference().resolve() != resolvedReference) { // Avoid stackoverflow with method calling itself - - return isCallTo(returnInstructionElement, expectedMethodFQNs, deepness + 1); - } - } - } - - return false; - } - - public static String getFirstArgumentStringValue(MethodReference e) { - String stringValue = null; - - PsiElement[] parameters = e.getParameters(); - if (parameters.length > 0 && parameters[0] instanceof StringLiteralExpression) { - StringLiteralExpression stringLiteralExpression = (StringLiteralExpression)parameters[0]; - stringValue = stringLiteralExpression.getText(); // quoted string - stringValue = stringValue.substring(stringLiteralExpression.getValueRange().getStartOffset(), stringLiteralExpression.getValueRange().getEndOffset()); - } - - return stringValue; - } - -} diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java index 337df0105..f1c7f958c 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineEntityReferenceContributor.java @@ -32,7 +32,7 @@ public PsiReference[] getReferencesByElement(@NotNull PsiElement psiElement, @No } MethodReference method = (MethodReference) parameterList.getContext(); Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); - if (!interfacesUtil.isRepositoryCall(method)) { + if (!interfacesUtil.isGetRepositoryCall(method)) { return new PsiReference[0]; } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java index 634364477..057a47754 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/ObjectRepositoryTypeProvider.java @@ -23,7 +23,7 @@ public PhpType getType(PsiElement e) { } Symfony2InterfacesUtil interfacesUtil = new Symfony2InterfacesUtil(); - if (!interfacesUtil.isRepositoryCall(e)) { + if (!interfacesUtil.isGetRepositoryCall(e)) { return null; } From 11d3af26614fd643841a333f4568a0b8bf8b269d Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Tue, 16 Apr 2013 16:44:47 +0200 Subject: [PATCH 09/13] allow entities equals Repository --- .../idea/symfony2plugin/doctrine/EntityReference.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java index a2da481c6..38ed214bb 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java @@ -66,7 +66,7 @@ public Object[] getVariants() { String repoName = shortcutName + ':' + className; // dont add Repository classes and abstract entities - if(!className.endsWith("Repository")) { + if(!className.endsWith("Repository") && !className.equals("Repository")) { for (PhpClass entityClass : phpIndex.getClassesByFQN(em.get(shortcutName) + "\\" + className)) { if(!entityClass.isAbstract()) { results.add(new DoctrineEntityLookupElement(repoName, entityClass)); From ca2b58f44eaff88f0ded83b9b639fc93db9a9a90 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Tue, 16 Apr 2013 16:54:40 +0200 Subject: [PATCH 10/13] renaming of doctrine EntityNamesParserTest to reflection file structure --- .../{dic => }/doctrine/component/EntityNamesParserTest.java | 2 +- .../doctrine/component/appDevDebugProjectContainer.xml | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/fr/adrienbrault/idea/symfony2plugin/tests/{dic => }/doctrine/component/EntityNamesParserTest.java (90%) rename tests/fr/adrienbrault/idea/symfony2plugin/tests/{dic => }/doctrine/component/appDevDebugProjectContainer.xml (100%) diff --git a/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java b/tests/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/component/EntityNamesParserTest.java similarity index 90% rename from tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java rename to tests/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/component/EntityNamesParserTest.java index f1de38154..e2efcfd1a 100644 --- a/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/EntityNamesParserTest.java +++ b/tests/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/component/EntityNamesParserTest.java @@ -1,4 +1,4 @@ -package fr.adrienbrault.idea.symfony2plugin.tests.dic.doctrine.component; +package fr.adrienbrault.idea.symfony2plugin.tests.doctrine.component; import fr.adrienbrault.idea.symfony2plugin.doctrine.component.EntityNamesParser; import org.junit.Assert; diff --git a/tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml b/tests/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/component/appDevDebugProjectContainer.xml similarity index 100% rename from tests/fr/adrienbrault/idea/symfony2plugin/tests/dic/doctrine/component/appDevDebugProjectContainer.xml rename to tests/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/component/appDevDebugProjectContainer.xml From f00493e836e571b17d7af59a52270395c6d78c77 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Sun, 21 Apr 2013 11:13:05 +0200 Subject: [PATCH 11/13] using xpath to find all setEntityNamespaces calls and allow ObjectManager::getRepository calls --- .../Symfony2InterfacesUtil.java | 1 + .../doctrine/component/EntityNamesParser.java | 40 +++++++------------ 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java index 3853d0ec2..682b88492 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java @@ -43,6 +43,7 @@ public boolean isUrlGeneratorGenerateCall(PsiElement e) { public boolean isGetRepositoryCall(PsiElement e) { return isCallTo(e, new Method[] { getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ManagerRegistry", "getRepository"), + getInterfaceMethod(e.getProject(), "\\Doctrine\\Common\\Persistence\\ObjectManager", "getRepository"), }); } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java index baf5e26eb..db4854a1c 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/component/EntityNamesParser.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilder; +import javax.xml.xpath.*; /** * @author Daniel Espendiller @@ -35,36 +36,23 @@ public Map parse(File file) throws IOException, SAXException { } public Map parse(Document document) { - Map map = new HashMap(); - - // any way to this better? - NodeList servicesNodes = document.getElementsByTagName("service"); - for (int i = 0; i < servicesNodes.getLength(); i++) { - Element node = (Element) servicesNodes.item(i); - - // doctrine.orm.default_entity_manager - // doctrine.orm.customer_entity_manager - if (node.hasAttribute("class") && node.hasAttribute("id") && node.getAttribute("id").startsWith("doctrine.orm") && node.getAttribute("id").endsWith("_entity_manager")) { - // - NodeList calls = document.getElementsByTagName("call"); - for (int x = 0; x < calls.getLength(); x++) { + Map map = new HashMap(); - Element call = (Element) calls.item(x); - if (call.hasAttribute("method") && call.getAttribute("method").equals("setEntityNamespaces")) { + Object result = null; + try { + XPath xpath = XPathFactory.newInstance().newXPath(); + XPathExpression xPathExpr = xpath.compile("/container/services/service[@id[starts-with(.,'doctrine.orm.')]]//call[@method='setEntityNamespaces']//argument[@key]"); + result = xPathExpr.evaluate(document, XPathConstants.NODESET); + } catch (XPathExpressionException e) { + return map; + } - // HomeBundle\Entity - NodeList arguments = call.getElementsByTagName("argument"); - for (int y = 0; y < arguments.getLength(); y++) { - Element arg = (Element) arguments.item(y); - if (arg.hasAttribute("key")) { - map.put(arg.getAttribute("key"), "\\" + arg.getTextContent()); - } - } - } + NodeList nodes = (NodeList) result; - } - } + for (int i = 0; i < nodes.getLength(); i++) { + Element node = (Element) nodes.item(i); + map.put(node.getAttribute("key"), "\\" + node.getTextContent()); } return map; From dec9b34f170adb2a305310bdd24f238ec4eaabe4 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Tue, 30 Apr 2013 14:58:34 +0200 Subject: [PATCH 12/13] using isInstanceOf to check of Repository classes --- .../Symfony2InterfacesUtil.java | 2 +- .../doctrine/EntityReference.java | 53 ++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java index a418319e1..3c8b2b73e 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/Symfony2InterfacesUtil.java @@ -172,7 +172,7 @@ protected boolean isImplementationOfInterface(PhpClass phpClass, PhpClass phpInt return isImplementationOfInterface(phpClass.getSuperClass(), phpInterface); } - protected boolean isInstanceOf(PhpClass subjectClass, PhpClass expectedClass) { + public boolean isInstanceOf(PhpClass subjectClass, PhpClass expectedClass) { if (subjectClass == expectedClass) { return true; } diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java index 38ed214bb..e2d9ea81d 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/EntityReference.java @@ -4,9 +4,8 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.psi.PsiElement; import com.jetbrains.php.PhpIndex; -import com.jetbrains.php.lang.psi.elements.PhpClass; -import com.jetbrains.php.lang.psi.elements.PhpNamespace; -import com.jetbrains.php.lang.psi.elements.StringLiteralExpression; +import com.jetbrains.php.lang.psi.elements.*; +import fr.adrienbrault.idea.symfony2plugin.Symfony2InterfacesUtil; import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent; import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineEntityLookupElement; import org.jetbrains.annotations.NotNull; @@ -48,13 +47,20 @@ public Object[] getVariants() { PhpIndex phpIndex = PhpIndex.getInstance(getElement().getProject()); Symfony2ProjectComponent symfony2ProjectComponent = getElement().getProject().getComponent(Symfony2ProjectComponent.class); - Map em = symfony2ProjectComponent.getEntityNamespacesMap(); + Map entityNamespaces = symfony2ProjectComponent.getEntityNamespacesMap(); List results = new ArrayList(); - for (String shortcutName : em.keySet()) { + + // find Repository interface to filter RepositoryClasses out + PhpClass repositoryClass = getRepositoryClass(phpIndex); + if(null == repositoryClass) { + return results.toArray(); + } + + for (Map.Entry entry : entityNamespaces.entrySet()) { // search for classes that match the symfony2 namings - Collection entities = phpIndex.getNamespacesByName(em.get(shortcutName)); + Collection entities = phpIndex.getNamespacesByName(entry.getValue()); // @TODO: it looks like PhpIndex cant search for classes like \ns\Path\*\... // temporary only use flat entities and dont support "MyBundle:Folder\Entity" @@ -63,22 +69,41 @@ public Object[] getVariants() { // build our symfony2 shortcut String filename = entity_files.getContainingFile().getName(); String className = filename.substring(0, filename.lastIndexOf('.')); - String repoName = shortcutName + ':' + className; + String repoName = entry.getKey() + ':' + className; // dont add Repository classes and abstract entities - if(!className.endsWith("Repository") && !className.equals("Repository")) { - for (PhpClass entityClass : phpIndex.getClassesByFQN(em.get(shortcutName) + "\\" + className)) { - if(!entityClass.isAbstract()) { - results.add(new DoctrineEntityLookupElement(repoName, entityClass)); - } - } + PhpClass entityClass = getClass(phpIndex, entityNamespaces.get(entry.getKey()) + "\\" + className); + if(null != entityClass && isEntity(entityClass, repositoryClass)) { + results.add(new DoctrineEntityLookupElement(repoName, entityClass)); } - } + } } return results.toArray(); } + @Nullable + protected PhpClass getRepositoryClass(PhpIndex phpIndex) { + Collection classes = phpIndex.getInterfacesByFQN("\\Doctrine\\Common\\Persistence\\ObjectRepository"); + return classes.isEmpty() ? null : classes.iterator().next(); + } + + @Nullable + protected PhpClass getClass(PhpIndex phpIndex, String className) { + Collection classes = phpIndex.getClassesByFQN(className); + return classes.isEmpty() ? null : classes.iterator().next(); + } + + protected boolean isEntity(PhpClass entityClass, PhpClass repositoryClass) { + + if(entityClass.isAbstract()) { + return false; + } + + Symfony2InterfacesUtil symfony2Util = new Symfony2InterfacesUtil(); + return !symfony2Util.isInstanceOf(entityClass, repositoryClass); + } + } From 846f5f20ed3fd758c4c8573f488668fbbf7ffee9 Mon Sep 17 00:00:00 2001 From: Daniel Espendiller Date: Tue, 30 Apr 2013 15:11:08 +0200 Subject: [PATCH 13/13] using doctrine icons for entity lookups --- .../doctrine/dict/DoctrineEntityLookupElement.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java index 9f5e2278e..ae5cdc380 100644 --- a/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java +++ b/src/fr/adrienbrault/idea/symfony2plugin/doctrine/dict/DoctrineEntityLookupElement.java @@ -2,8 +2,8 @@ import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementPresentation; -import com.jetbrains.php.PhpIcons; import com.jetbrains.php.lang.psi.elements.PhpClass; +import fr.adrienbrault.idea.symfony2plugin.Symfony2Icons; import org.jetbrains.annotations.NotNull; /** @@ -29,7 +29,7 @@ public void renderElement(LookupElementPresentation presentation) { presentation.setItemText(getLookupString()); presentation.setTypeText(className.getPresentableFQN()); presentation.setTypeGrayed(true); - presentation.setIcon(PhpIcons.CLASS); + presentation.setIcon(Symfony2Icons.DOCTRINE); } }