package fr.adrienbrault.idea.symfony2plugin.util.controller; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.openapi.project.Project; import com.jetbrains.php.PhpIndex; import com.jetbrains.php.lang.psi.elements.Method; import com.jetbrains.php.lang.psi.elements.PhpClass; import fr.adrienbrault.idea.symfony2plugin.routing.Route; import fr.adrienbrault.idea.symfony2plugin.routing.RouteHelper; import fr.adrienbrault.idea.symfony2plugin.routing.dic.ServiceRouteContainer; import fr.adrienbrault.idea.symfony2plugin.stubs.ContainerCollectionResolver; import fr.adrienbrault.idea.symfony2plugin.util.PhpIndexUtil; import fr.adrienbrault.idea.symfony2plugin.util.SymfonyBundleUtil; import fr.adrienbrault.idea.symfony2plugin.util.dict.ServiceUtil; import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * @author Daniel Espendiller <daniel@espendiller.net> */ public class ControllerIndex { private Project project; private PhpIndex phpIndex; private ContainerCollectionResolver.LazyServiceCollector lazyServiceCollector; public ControllerIndex(Project project) { this.project = project; this.phpIndex = PhpIndex.getInstance(project); } public List<ControllerAction> getActions() { List<ControllerAction> actions = new ArrayList<>(); SymfonyBundleUtil symfonyBundleUtil = new SymfonyBundleUtil(phpIndex); for (SymfonyBundle symfonyBundle : symfonyBundleUtil.getBundles()) { actions.addAll(this.getActionMethods(symfonyBundle)); } return actions; } @Nullable public ControllerAction getControllerActionOnService(String shortcutName) { // only foo_bar:Method is valid if(!RouteHelper.isServiceController(shortcutName)) { return null; } String serviceId = shortcutName.substring(0, shortcutName.lastIndexOf(":")); String methodName = shortcutName.substring(shortcutName.lastIndexOf(":") + 1); PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(this.project, serviceId, getLazyServiceCollector(this.project)); if(phpClass == null) { return null; } Method method = phpClass.findMethodByName(methodName); if(method == null) { return null; } return new ControllerAction(serviceId, method); } @Nullable public ControllerAction getControllerAction(String shortcutName) { for(ControllerAction controllerAction: this.getActions()) { if(controllerAction.getShortcutName().equals(shortcutName)) { return controllerAction; } } return null; } private List<ControllerAction> getActionMethods(SymfonyBundle symfonyBundle) { String namespaceName = symfonyBundle.getNamespaceName(); if(!namespaceName.startsWith("\\")) { namespaceName = "\\" + namespaceName; } if(!namespaceName.endsWith("\\")) { namespaceName += "\\"; } namespaceName += "Controller"; List<ControllerAction> actions = new ArrayList<>(); for (PhpClass phpClass : PhpIndexUtil.getPhpClassInsideNamespace(this.project, namespaceName)) { if(!phpClass.getName().endsWith("Controller")) { continue; } String presentableFQN = phpClass.getPresentableFQN(); if(!presentableFQN.startsWith("\\")) { presentableFQN = "\\" + presentableFQN; } presentableFQN = presentableFQN.substring(0, presentableFQN.length() - "Controller".length()); if(presentableFQN.length() == 0) { continue; } String ns = presentableFQN.substring(namespaceName.length() + 1); for(Method method : phpClass.getMethods()) { String methodName = method.getName(); if(methodName.endsWith("Action") && method.getAccess().isPublic()) { String shortcutName = symfonyBundle.getName() + ":" + ns.replace("\\", "/") + ':' + methodName.substring(0, methodName.length() - 6); actions.add(new ControllerAction(shortcutName, method)); } } } return actions; } @NotNull public List<ControllerAction> getServiceActionMethods(@NotNull Project project) { Map<String,Route> routes = RouteHelper.getAllRoutes(project); if(routes.size() == 0) { return Collections.emptyList(); } // there is now way to find service controllers directly, // so we search for predefined service controller and use the public methods ContainerCollectionResolver.LazyServiceCollector collector = new ContainerCollectionResolver.LazyServiceCollector(project); List<ControllerAction> actions = new ArrayList<>(); for (String serviceName : ServiceRouteContainer.build(routes).getServiceNames()) { PhpClass phpClass = ServiceUtil.getResolvedClassDefinition(project, serviceName, collector); if(phpClass == null) { continue; } // find public method of the service class which are possible Actions for(Method method : phpClass.getMethods()) { if(method.getAccess().isPublic() && !method.getName().startsWith("__") && !method.getName().startsWith("set")) { actions.add(new ControllerAction(serviceName + ":" + method.getName(), method)); } } } return actions; } @Nullable public Method resolveShortcutName(String controllerName) { ControllerIndex controllerIndex = new ControllerIndex(project); ControllerAction controllerAction = controllerIndex.getControllerAction(controllerName); if(controllerAction != null) { return controllerAction.getMethod(); } controllerAction = controllerIndex.getControllerActionOnService(controllerName); if(controllerAction != null) { return controllerAction.getMethod(); } return null; } private ContainerCollectionResolver.LazyServiceCollector getLazyServiceCollector(Project project) { return this.lazyServiceCollector == null ? this.lazyServiceCollector = new ContainerCollectionResolver.LazyServiceCollector(project) : this.lazyServiceCollector; } static public List<LookupElement> getControllerLookupElements(Project project) { List<LookupElement> lookupElements = new ArrayList<>(); ControllerIndex controllerIndex = new ControllerIndex(project); for(ControllerAction controllerAction: controllerIndex.getActions()) { lookupElements.add(new ControllerActionLookupElement(controllerAction)); } for(ControllerAction controllerAction: controllerIndex.getServiceActionMethods(project)) { lookupElements.add(new ControllerActionLookupElement(controllerAction)); } return lookupElements; } @Nullable static public Method getControllerMethod(Project project, String controllerName) { return new ControllerIndex(project).resolveShortcutName(controllerName); } }