package fr.adrienbrault.idea.symfony2plugin.doctrine;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.php.PhpIndex;
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag;
import com.jetbrains.php.lang.psi.elements.*;
import com.jetbrains.php.lang.psi.elements.impl.PhpNamedElementImpl;
import fr.adrienbrault.idea.symfony2plugin.doctrine.component.DocumentNamespacesParser;
import fr.adrienbrault.idea.symfony2plugin.doctrine.component.EntityNamesServiceParser;
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelField;
import fr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineTypes;
import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineMetadataModel;
import fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.util.DoctrineMetadataUtil;
import fr.adrienbrault.idea.symfony2plugin.extension.DoctrineModelProvider;
import fr.adrienbrault.idea.symfony2plugin.extension.DoctrineModelProviderParameter;
import fr.adrienbrault.idea.symfony2plugin.util.*;
import fr.adrienbrault.idea.symfony2plugin.util.dict.DoctrineModel;
import fr.adrienbrault.idea.symfony2plugin.util.dict.SymfonyBundle;
import fr.adrienbrault.idea.symfony2plugin.util.service.ServiceXmlParserFactory;
import fr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.yaml.psi.*;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Daniel Espendiller <daniel@espendiller.net>
*/
public class EntityHelper {
public static final ExtensionPointName<DoctrineModelProvider> MODEL_POINT_NAME = new ExtensionPointName<>("fr.adrienbrault.idea.symfony2plugin.extension.DoctrineModelProvider");
final public static String[] ANNOTATION_FIELDS = new String[] {
"\\Doctrine\\ORM\\Mapping\\Column",
"\\Doctrine\\ORM\\Mapping\\OneToOne",
"\\Doctrine\\ORM\\Mapping\\ManyToOne",
"\\Doctrine\\ORM\\Mapping\\OneToMany",
"\\Doctrine\\ORM\\Mapping\\ManyToMany",
};
final public static Set<String> RELATIONS = new HashSet<>(Arrays.asList("manytoone", "manytomany", "onetoone", "onetomany"));
/**
* Resolve shortcut and namespaces classes for current phpclass and attached modelname
*/
@Nullable
public static PhpClass getAnnotationRepositoryClass(@NotNull PhpClass phpClass, @NotNull String modelName) {
// \ns\Class fine we dont need to resolve classname we are in global context
if(modelName.startsWith("\\")) {
return PhpElementsUtil.getClassInterface(phpClass.getProject(), modelName);
}
// repositoryClass="Classname" pre-append namespace here
PhpNamedElementImpl phpNamedElementImpl = PsiTreeUtil.getParentOfType(phpClass, PhpNamedElementImpl.class);
if(phpNamedElementImpl != null) {
String className = phpNamedElementImpl.getFQN() + "\\" + modelName;
PhpClass namespaceClass = PhpElementsUtil.getClassInterface(phpClass.getProject(), className);
if(namespaceClass != null) {
return namespaceClass;
}
}
// repositoryClass="Classname\Test" trailing backslash can be stripped
return PhpElementsUtil.getClassInterface(phpClass.getProject(), modelName);
}
/**
* Search for a repository class of a model
*
* @param project Current project
* @param shortcutName "\Class\Name" or "FooBundle:Name"
*/
@Nullable
public static PhpClass getEntityRepositoryClass(@NotNull Project project, @NotNull String shortcutName) {
PhpClass phpClass = resolveShortcutName(project, shortcutName);
if(phpClass == null) {
return null;
}
String presentableFQN = phpClass.getPresentableFQN();
PhpClass classRepository = DoctrineMetadataUtil.getClassRepository(project, presentableFQN);
if(classRepository != null) {
return classRepository;
}
// @TODO: deprecated code
// search on annotations
PhpDocComment docAnnotation = phpClass.getDocComment();
if(docAnnotation != null) {
// search for repositoryClass="Foo\Bar\RegisterRepository"
// @MongoDB\Document; @ORM\Entity
String docAnnotationText = docAnnotation.getText();
Matcher matcher = Pattern.compile("repositoryClass[\\s]*=[\\s]*[\"|'](.*)[\"|']").matcher(docAnnotationText);
if (matcher.find()) {
return getAnnotationRepositoryClass(phpClass, matcher.group(1));
}
}
SymfonyBundle symfonyBundle = new SymfonyBundleUtil(PhpIndex.getInstance(project)).getContainingBundle(phpClass);
if(symfonyBundle != null) {
PhpClass repositoryClass = getEntityRepositoryClass(project, symfonyBundle, presentableFQN);
if(repositoryClass != null) {
return repositoryClass;
}
}
// old __CLASS__ Repository type
// @TODO remove this fallback when we implemented all cases
return resolveShortcutName(project, shortcutName + "Repository");
}
public static List<DoctrineModelField> getModelFieldsSet(YAMLKeyValue yamlKeyValue) {
List<DoctrineModelField> fields = new ArrayList<>();
for(Map.Entry<String, YAMLKeyValue> entry: getYamlModelFieldKeyValues(yamlKeyValue).entrySet()) {
List<DoctrineModelField> fieldSet = getYamlDoctrineFields(entry.getKey(), entry.getValue());
if(fieldSet != null) {
fields.addAll(fieldSet);
}
}
return fields;
}
@Nullable
public static List<DoctrineModelField> getYamlDoctrineFields(String keyName, @Nullable YAMLKeyValue yamlKeyValue) {
if(yamlKeyValue == null) {
return null;
}
PsiElement yamlCompoundValue = yamlKeyValue.getValue();
if(yamlCompoundValue == null) {
return null;
}
List<DoctrineModelField> modelFields = new ArrayList<>();
for(YAMLKeyValue yamlKey: PsiTreeUtil.getChildrenOfTypeAsList(yamlCompoundValue, YAMLKeyValue.class)) {
String fieldName = YamlHelper.getYamlKeyName(yamlKey);
if(fieldName != null) {
DoctrineModelField modelField = new DoctrineModelField(fieldName);
modelField.addTarget(yamlKey);
attachYamlFieldTypeName(keyName, modelField, yamlKey);
modelFields.add(modelField);
}
}
return modelFields;
}
public static void attachYamlFieldTypeName(String keyName, DoctrineModelField doctrineModelField, YAMLKeyValue yamlKeyValue) {
if("fields".equals(keyName) || "id".equals(keyName)) {
YAMLKeyValue yamlType = YamlHelper.getYamlKeyValue(yamlKeyValue, "type");
if(yamlType != null) {
doctrineModelField.setTypeName(yamlType.getValueText());
}
YAMLKeyValue yamlColumn = YamlHelper.getYamlKeyValue(yamlKeyValue, "column");
if(yamlColumn != null) {
doctrineModelField.setColumn(yamlColumn.getValueText());
}
return;
}
if(RELATIONS.contains(keyName.toLowerCase())) {
YAMLKeyValue targetEntity = YamlHelper.getYamlKeyValue(yamlKeyValue, "targetEntity");
if(targetEntity != null) {
doctrineModelField.setRelationType(keyName);
doctrineModelField.setRelation(getOrmClass(yamlKeyValue.getContainingFile(), targetEntity.getValueText()));
}
}
}
@NotNull
public static String getOrmClass(@NotNull PsiFile psiFile, @NotNull String className) {
// force global namespace not need to search for class
if(className.startsWith("\\")) {
return className;
}
String entityName = null;
// espend\Doctrine\ModelBundle\Entity\Bike:
// ...
// targetEntity: Foo
if(psiFile instanceof YAMLFile) {
YAMLDocument yamlDocument = PsiTreeUtil.getChildOfType(psiFile, YAMLDocument.class);
if(yamlDocument != null) {
YAMLKeyValue entityKeyValue = PsiTreeUtil.getChildOfType(yamlDocument, YAMLKeyValue.class);
if(entityKeyValue != null) {
entityName = entityKeyValue.getKeyText();
}
}
} else if(psiFile instanceof XmlFile) {
XmlTag rootTag = ((XmlFile) psiFile).getRootTag();
if(rootTag != null) {
XmlTag entity = rootTag.findFirstSubTag("entity");
if(entity != null) {
String name = entity.getAttributeValue("name");
if(org.apache.commons.lang.StringUtils.isBlank(name)) {
entityName = name;
}
}
}
}
if(entityName == null) {
return className;
}
// trim class name
int lastBackSlash = entityName.lastIndexOf("\\");
if(lastBackSlash > 0) {
String fqnClass = entityName.substring(0, lastBackSlash + 1) + className;
if(PhpElementsUtil.getClass(psiFile.getProject(), fqnClass) != null) {
return fqnClass;
}
}
return className;
}
@NotNull
public static Map<String, YAMLKeyValue> getYamlModelFieldKeyValues(YAMLKeyValue yamlKeyValue) {
Map<String, YAMLKeyValue> keyValueCollection = new HashMap<>();
for(String fieldMap: new String[] { "id", "fields", "manyToOne", "oneToOne", "manyToMany", "oneToMany"}) {
YAMLKeyValue targetYamlKeyValue = YamlHelper.getYamlKeyValue(yamlKeyValue, fieldMap, true);
if(targetYamlKeyValue != null) {
keyValueCollection.put(fieldMap, targetYamlKeyValue);
}
}
return keyValueCollection;
}
@NotNull
public static PsiElement[] getModelFieldTargets(@NotNull PhpClass phpClass,@NotNull String fieldName) {
Collection<PsiElement> psiElements = new ArrayList<>();
DoctrineMetadataModel modelFields = DoctrineMetadataUtil.getModelFields(phpClass.getProject(), phpClass.getPresentableFQN());
if(modelFields != null) {
for (DoctrineModelField field : modelFields.getFields()) {
if(field.getName().equals(fieldName) && field.getTargets().size() > 0) {
return field.getTargets().toArray(new PsiElement[psiElements.size()]);
}
}
}
// @TODO: deprecated
PsiFile psiFile = EntityHelper.getModelConfigFile(phpClass);
if(psiFile instanceof YAMLFile) {
// @TODO: migrate to getEntityFields()
YAMLValue topLevelValue = ((YAMLFile) psiFile).getDocuments().get(0).getTopLevelValue();
if(topLevelValue instanceof YAMLMapping) {
Collection<YAMLKeyValue> keyValues = ((YAMLMapping) topLevelValue).getKeyValues();
if(keyValues.size() > 0) {
for(YAMLKeyValue yamlKeyValue: EntityHelper.getYamlModelFieldKeyValues(keyValues.iterator().next()).values()) {
ContainerUtil.addIfNotNull(psiElements, YamlHelper.getYamlKeyValue(yamlKeyValue, "name"));
}
}
}
}
if(psiFile instanceof XmlFile) {
for (DoctrineModelField field : getEntityFields((XmlFile) psiFile)) {
if(field.getName().equals(fieldName)) {
psiElements.addAll(field.getTargets());
}
}
}
// provide fallback on annotations
// @TODO: better detect annotation switch; yaml and annotation are valid; need deps on annotation plugin
PhpDocComment docComment = phpClass.getDocComment();
if(docComment != null) {
if(docComment.getText().contains("Entity") || docComment.getText().contains("@ORM") || docComment.getText().contains("repositoryClass")) {
for(Field field: phpClass.getFields()) {
if(!field.isConstant() && fieldName.equals(field.getName())) {
psiElements.add(field);
}
}
}
}
String methodName = "get" + StringUtils.camelize(fieldName.toLowerCase(), false);
Method method = phpClass.findMethodByName(methodName);
if(method != null) {
psiElements.add(method);
}
return psiElements.toArray(new PsiElement[psiElements.size()]);
}
@Nullable
private static PsiFile getEntityMetadataFile(@NotNull Project project, @NotNull SymfonyBundle symfonyBundleUtil, @NotNull String className, @NotNull String modelShortcut) {
for(String s: new String[] {"yml", "xml"}) {
String entityFile = "Resources/config/doctrine/" + className + String.format(".%s.%s", modelShortcut, s);
VirtualFile virtualFile = symfonyBundleUtil.getRelative(entityFile);
if(virtualFile != null) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
if(psiFile != null) {
return psiFile;
}
}
}
return null;
}
@Nullable
public static PsiFile getModelConfigFile(@NotNull PhpClass phpClass) {
// new code
String presentableFQN = phpClass.getPresentableFQN();
Collection<VirtualFile> metadataFiles = DoctrineMetadataUtil.findMetadataFiles(phpClass.getProject(), presentableFQN);
if(metadataFiles.size() > 0) {
PsiFile file = PsiManager.getInstance(phpClass.getProject()).findFile(metadataFiles.iterator().next());
if(file != null) {
return file;
}
}
// @TODO: deprecated code
SymfonyBundle symfonyBundle = new SymfonyBundleUtil(phpClass.getProject()).getContainingBundle(phpClass);
if(symfonyBundle != null) {
for(String modelShortcut: new String[] {"orm", "mongodb", "couchdb"}) {
String className = phpClass.getName();
int n = presentableFQN.indexOf("\\Entity\\");
if(n > 0) {
className = presentableFQN.substring(n + 8).replace("\\", ".");
}
PsiFile entityMetadataFile = getEntityMetadataFile(phpClass.getProject(), symfonyBundle, className, modelShortcut);
if(entityMetadataFile != null) {
return entityMetadataFile;
}
}
}
return null;
}
@NotNull
public static Collection<DoctrineModelField> getModelFields(@NotNull PhpClass phpClass) {
// new code
String presentableFQN = phpClass.getPresentableFQN();
DoctrineMetadataModel fields = DoctrineMetadataUtil.getModelFields(phpClass.getProject(), presentableFQN);
if(fields != null) {
return fields.getFields();
}
// @TODO: old deprecated code
PsiFile psiFile = getModelConfigFile(phpClass);
if(psiFile == null) {
Collections.emptyList();
}
if(psiFile instanceof YAMLFile) {
List<DoctrineModelField> modelFields = new ArrayList<>();
PsiElement yamlDocument = psiFile.getFirstChild();
if(yamlDocument instanceof YAMLDocument) {
PsiElement arrayKeyValue = yamlDocument.getFirstChild();
if(arrayKeyValue instanceof YAMLKeyValue) {
// first line is class name; check of we are right
String className = YamlHelper.getYamlKeyName(((YAMLKeyValue) arrayKeyValue));
if(PhpElementsUtil.isEqualClassName(phpClass, className)) {
modelFields.addAll(getModelFieldsSet((YAMLKeyValue) arrayKeyValue));
}
}
}
return modelFields;
}
if(psiFile instanceof XmlFile) {
return getEntityFields((XmlFile) psiFile);
}
// provide fallback on annotations
List<DoctrineModelField> modelFields = new ArrayList<>();
PhpDocComment docComment = phpClass.getDocComment();
if(docComment != null) {
if(AnnotationBackportUtil.hasReference(docComment, "\\Doctrine\\ORM\\Mapping\\Entity")) {
for(Field field: phpClass.getFields()) {
if(!field.isConstant()) {
if(AnnotationBackportUtil.hasReference(field.getDocComment(), ANNOTATION_FIELDS)) {
DoctrineModelField modelField = new DoctrineModelField(field.getName());
attachAnnotationInformation(field, modelField.addTarget(field));
modelFields.add(modelField);
}
}
}
}
}
return modelFields;
}
@NotNull
public static List<DoctrineModelField> getEntityFields(@NotNull XmlFile psiFile) {
List<DoctrineModelField> modelFields = new ArrayList<>();
XmlTag rootTag = psiFile.getRootTag();
if(rootTag == null) {
return Collections.emptyList();
}
final XmlTag entity = rootTag.findFirstSubTag("entity");
if(entity == null) {
return Collections.emptyList();
}
for (XmlTag xmlTag : new ArrayList<XmlTag>() {{
addAll(Arrays.asList(entity.findSubTags("field")));
addAll(Arrays.asList(entity.findSubTags("id")));
}}) {
String name = xmlTag.getAttributeValue("name");
if(org.apache.commons.lang.StringUtils.isBlank(name)) {
continue;
}
DoctrineModelField field = new DoctrineModelField(name);
field.addTarget(xmlTag);
String column = xmlTag.getAttributeValue("column");
if(org.apache.commons.lang.StringUtils.isNotBlank(name)) {
field.setColumn(column);
}
String type = xmlTag.getAttributeValue("type");
if(org.apache.commons.lang.StringUtils.isNotBlank(type)) {
field.setTypeName(type);
}
modelFields.add(field);
}
for(String s: new String[] {"one-to-one", "one-to-many", "many-to-many", "many-to-one"}) {
for (XmlTag xmlTag : entity.findSubTags(s)) {
String targetEntity = xmlTag.getAttributeValue("target-entity");
if(targetEntity == null) {
continue;
}
String field = xmlTag.getAttributeValue("field");
if(field == null) {
continue;
}
DoctrineModelField entityField = new DoctrineModelField(field);
entityField.addTarget(xmlTag);
// find namespace
entityField.setRelation(getOrmClass(psiFile, targetEntity));
entityField.setRelationType(StringUtils.camelize(s.replace("-", "_")));
modelFields.add(entityField);
}
}
return modelFields;
}
@Nullable
private static PhpClass getEntityRepositoryClass(Project project, SymfonyBundle symfonyBundle, String classFqnName) {
// some default bundle search path
// Bundle/Resources/config/doctrine/Product.orm.yml
// Bundle/Resources/config/doctrine/Product.mongodb.yml
List<String[]> managerConfigs = new ArrayList<>();
managerConfigs.add(new String[] { "Entity", "orm"});
managerConfigs.add(new String[] { "Document", "mongodb"});
for(String[] managerConfig: managerConfigs) {
String entityName = classFqnName.substring(symfonyBundle.getNamespaceName().length() - 1);
if(entityName.startsWith(managerConfig[0] + "\\")) {
entityName = entityName.substring((managerConfig[0] + "\\").length());
}
// entities in sub folder: 'Foo\Bar' -> 'Foo.Bar.orm.yml'
String entityFile = "Resources/config/doctrine/" + entityName.replace("\\", ".") + String.format(".%s.yml", managerConfig[1]);
VirtualFile virtualFile = symfonyBundle.getRelative(entityFile);
if(virtualFile != null) {
PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
if(psiFile != null) {
// search for "repositoryClass: Foo\Bar\RegisterRepository" also provide quoted values
Matcher matcher = Pattern.compile("[\\s]*repositoryClass:[\\s]*[\"|']*(.*)[\"|']*").matcher(psiFile.getText());
if (matcher.find()) {
return PhpElementsUtil.getClass(PhpIndex.getInstance(project), matcher.group(1));
}
// we found entity config so no other check needed
return null;
}
}
}
return null;
}
@Nullable
public static PhpClass resolveShortcutName(@NotNull Project project, @NotNull String shortcutName) {
return resolveShortcutName(project, shortcutName, DoctrineTypes.Manager.ORM, DoctrineTypes.Manager.MONGO_DB, DoctrineTypes.Manager.COUCH_DB);
}
/**
*
* @param project PHPStorm projects
* @param shortcutName name as MyBundle\Entity\Model or MyBundle:Model
* @return null|PhpClass
*/
@Nullable
public static PhpClass resolveShortcutName(@NotNull Project project, @Nullable String shortcutName, DoctrineTypes.Manager... managers) {
if(shortcutName == null) {
return null;
}
// we dont need to resolve bundle name, use class name
if (!shortcutName.contains(":")) {
return PhpElementsUtil.getClassInterface(project, shortcutName);
}
// resolve:
// MyBundle:Model -> MyBundle\Entity\Model
// MyBundle:Folder\Model -> MyBundle\Entity\Folder\Model
List<DoctrineTypes.Manager> managerList = Arrays.asList(managers);
// collect entitymanager namespaces on bundle or container file
Map<String, String> em = new HashMap<>();
if(managerList.contains(DoctrineTypes.Manager.ORM)) {
Map<String, String> entityNameMap = ServiceXmlParserFactory.getInstance(project, EntityNamesServiceParser.class).getEntityNameMap();
em.putAll(entityNameMap);
em.putAll(EntityHelper.getWeakBundleNamespaces(project, entityNameMap, "Entity"));
}
Map<String, String> odm = new HashMap<>();
if(managerList.contains(DoctrineTypes.Manager.MONGO_DB) || managerList.contains(DoctrineTypes.Manager.COUCH_DB)) {
Map<String, String> documentMap = ServiceXmlParserFactory.getInstance(project, DocumentNamespacesParser.class).getNamespaceMap();
odm.putAll(documentMap);
odm.putAll(EntityHelper.getWeakBundleNamespaces(project, documentMap, "Document"));
}
// split bundle and model name
int firstDirectorySeparatorIndex = shortcutName.indexOf(":");
String bundlename = shortcutName.substring(0, firstDirectorySeparatorIndex);
String entityName = shortcutName.substring(firstDirectorySeparatorIndex + 1);
// conditional find namespace on manager paths
for(Map<String, String> map: Arrays.asList(em, odm)) {
String namespace = map.get(bundlename);
if(namespace == null) {
continue;
}
PhpClass classInterface = PhpElementsUtil.getClassInterface(project, namespace + "\\" + entityName);
if(classInterface != null) {
return classInterface;
}
}
return null;
}
@Nullable
public static DoctrineTypes.Manager getManager(MethodReference methodReference) {
PhpPsiElement phpTypedElement = methodReference.getFirstPsiChild();
if(!(phpTypedElement instanceof PhpTypedElement)) {
return null;
}
for(String typeString: PhpIndex.getInstance(methodReference.getProject()).completeType(methodReference.getProject(), ((PhpTypedElement) phpTypedElement).getType(), new HashSet<>()).getTypes()) {
for(Map.Entry<DoctrineTypes.Manager, String> entry: DoctrineTypes.getManagerInstanceMap().entrySet()) {
if(PhpElementsUtil.isInstanceOf(methodReference.getProject(), typeString, entry.getValue())) {
return entry.getKey();
}
}
}
return null;
}
public static PsiElement[] getModelPsiTargets(Project project, @NotNull String entityName) {
List<PsiElement> results = new ArrayList<>();
PhpClass phpClass = EntityHelper.getEntityRepositoryClass(project, entityName);
if(phpClass != null) {
results.add(phpClass);
}
// search any php model file
PhpClass entity = EntityHelper.resolveShortcutName(project, entityName);
if(entity != null) {
results.add(entity);
// find model config eg ClassName.orm.yml
PsiFile psiFile = EntityHelper.getModelConfigFile(entity);
if(psiFile != null) {
results.add(psiFile);
}
}
return results.toArray(new PsiElement[results.size()]);
}
public static void attachAnnotationInformation(Field field, DoctrineModelField doctrineModelField) {
// we already have that without regular expression
// @TODO: de.espend.idea.php.annotation.util.AnnotationUtil.getPhpDocCommentAnnotationContainer()
// fully require plugin now?
// get some more presentable completion information
// dont resolve docblocks; just extract them from doc comment
PhpDocComment docBlock = field.getDocComment();
if(docBlock == null) {
return;
}
String text = docBlock.getText();
// column type
Matcher matcher = Pattern.compile("type[\\s]*=[\\s]*[\"|']([\\w_\\\\]+)[\"|']").matcher(text);
if (matcher.find()) {
doctrineModelField.setTypeName(matcher.group(1));
}
// relation type
matcher = Pattern.compile("((Many|One)To(Many|One))\\(").matcher(text);
if (matcher.find()) {
doctrineModelField.setRelationType(matcher.group(1));
// targetEntity name
matcher = Pattern.compile("targetEntity[\\s]*=[\\s]*[\"|']([\\w_\\\\]+)[\"|']").matcher(text);
if (matcher.find()) {
doctrineModelField.setRelation(matcher.group(1));
} else {
// @TODO: external split
// FLOW shortcut:
// @var "\DateTime" is targetEntity
PhpDocParamTag varTag = docBlock.getVarTag();
if(varTag != null) {
String type = varTag.getType().toString();
if(org.apache.commons.lang.StringUtils.isNotBlank(type)) {
doctrineModelField.setRelation(type);
}
}
}
}
matcher = Pattern.compile("Column\\(").matcher(text);
if (matcher.find()) {
matcher = Pattern.compile("name\\s*=\\s*\"(\\w+)\"").matcher(text);
if(matcher.find()) {
doctrineModelField.setColumn(matcher.group(1));
}
}
}
/**
* One PhpClass can have multiple targets and names @TODO: refactor
*/
public static Collection<DoctrineModel> getModelClasses(final Project project) {
HashMap<String, String> shortcutNames = new HashMap<String, String>() {{
putAll(ServiceXmlParserFactory.getInstance(project, EntityNamesServiceParser.class).getEntityNameMap());
putAll(ServiceXmlParserFactory.getInstance(project, DocumentNamespacesParser.class).getNamespaceMap());
}};
for (SymfonyBundle symfonyBundle : new SymfonyBundleUtil(project).getBundles()) {
for(String s : new String[] {"Entity", "Document", "CouchDocument"}) {
String namespace = symfonyBundle.getNamespaceName() + s;
if(symfonyBundle.getRelative(s) != null || PhpIndex.getInstance(project).getNamespacesByName(namespace).size() > 0) {
shortcutNames.put(symfonyBundle.getName(), namespace);
}
}
}
// class fqn fallback
Collection<DoctrineModel> doctrineModels = getModelClasses(project, shortcutNames);
for (PhpClass phpClass : DoctrineMetadataUtil.getModels(project)) {
if(containsDoctrineModelClass(doctrineModels, phpClass)) {
continue;
}
doctrineModels.add(new DoctrineModel(phpClass));
}
DoctrineModelProviderParameter containerLoaderExtensionParameter = new DoctrineModelProviderParameter(project, new ArrayList<>());
for(DoctrineModelProvider provider : EntityHelper.MODEL_POINT_NAME.getExtensions()) {
for(DoctrineModelProviderParameter.DoctrineModel doctrineModel: provider.collectModels(containerLoaderExtensionParameter)) {
doctrineModels.add(new DoctrineModel(doctrineModel.getPhpClass(), doctrineModel.getName()));
}
}
return doctrineModels;
}
private static boolean containsDoctrineModelClass(@NotNull Collection<DoctrineModel> models, @NotNull PhpClass phpClass) {
for (DoctrineModel doctrineModel : models) {
if(PhpElementsUtil.isEqualClassName(doctrineModel.getPhpClass(), phpClass)) {
return true;
}
}
return false;
}
public static Collection<DoctrineModel> getModelClasses(Project project, Map<String, String> shortcutNames) {
PhpClass repositoryInterface = PhpElementsUtil.getInterface(PhpIndex.getInstance(project), DoctrineTypes.REPOSITORY_INTERFACE);
Collection<DoctrineModel> models = new ArrayList<>();
for (Map.Entry<String, String> entry : shortcutNames.entrySet()) {
for(PhpClass phpClass: PhpIndexUtil.getPhpClassInsideNamespace(project, entry.getValue())) {
if(repositoryInterface != null && !isEntity(phpClass, repositoryInterface)) {
continue;
}
models.add(new DoctrineModel(phpClass, entry.getKey(), entry.getValue()));
}
}
return models;
}
public static boolean isEntity(PhpClass entityClass, PhpClass repositoryClass) {
if(entityClass.isAbstract() || entityClass.isInterface()) {
return false;
}
return !PhpElementsUtil.isInstanceOf(entityClass, repositoryClass);
}
public static Map<String, String> getWeakBundleNamespaces(Project project, Map<String, String> entityNameMap, String subFolder) {
Map<String, String> missingMap = new HashMap<>();
Collection<SymfonyBundle> symfonyBundles = new SymfonyBundleUtil(project).getBundles();
for(SymfonyBundle symfonyBundle: symfonyBundles) {
if(symfonyBundle.isTestBundle()) {
continue;
}
// namespace already known
String bundleName = symfonyBundle.getName();
if(entityNameMap.containsKey(bundleName)) {
continue;
}
// find namepsace on file or class index
String namespace = symfonyBundle.getNamespaceName() + subFolder;
if(symfonyBundle.getRelative(subFolder) != null || PhpIndex.getInstance(project).getNamespacesByName(namespace).size() > 0) {
missingMap.put(bundleName, namespace);
}
}
return missingMap;
}
}