Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit84ec688

Browse files
authored
Merge pull requestHaehnchen#1701 from Haehnchen/feature/php8-attribute-doctrine
Add PHP8 attributes support for Doctrine metadata
2 parents9b09e22 +69884b0 commit84ec688

File tree

8 files changed

+382
-1
lines changed

8 files changed

+382
-1
lines changed

‎src/main/java/fr/adrienbrault/idea/symfony2plugin/doctrine/DoctrineUtil.java‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@
1414
importcom.jetbrains.php.lang.psi.PhpFile;
1515
importcom.jetbrains.php.lang.psi.elements.PhpClass;
1616
importcom.jetbrains.php.lang.psi.elements.PhpPsiElement;
17+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionArgument;
18+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionClassConstantArgument;
1719
importde.espend.idea.php.annotation.util.AnnotationUtil;
1820
importfr.adrienbrault.idea.symfony2plugin.stubs.indexes.visitor.AnnotationElementWalkingVisitor;
21+
importfr.adrienbrault.idea.symfony2plugin.stubs.indexes.visitor.AttributeElementWalkingVisitor;
22+
importfr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
1923
importfr.adrienbrault.idea.symfony2plugin.util.PsiElementUtils;
2024
importfr.adrienbrault.idea.symfony2plugin.util.yaml.YamlHelper;
2125
importorg.apache.commons.lang.ArrayUtils;
@@ -113,6 +117,8 @@ private static Collection<Pair<String, String>> getClassRepositoryPair(@NotNull
113117
publicstaticCollection<Pair<String,String>>getClassRepositoryPair(@NotNullPsiElementphpFile) {
114118
finalCollection<Pair<String,String>>pairs =newArrayList<>();
115119

120+
// Annotations:
121+
// @ORM\Entity("repositoryClass": YYY)
116122
phpFile.acceptChildren(newAnnotationElementWalkingVisitor(phpDocTag -> {
117123
PhpDocCommentphpDocComment =PsiTreeUtil.getParentOfType(phpDocTag,PhpDocComment.class);
118124
if (phpDocComment ==null) {
@@ -134,6 +140,27 @@ public static Collection<Pair<String, String>> getClassRepositoryPair(@NotNull P
134140
returnfalse;
135141
},MODEL_CLASS_ANNOTATION));
136142

143+
// Attributes:
144+
// #[Entity(repositoryClass: UserRepository::class)]
145+
phpFile.acceptChildren(newAttributeElementWalkingVisitor(pair -> {
146+
StringrepositoryClass =null;
147+
148+
PhpExpectedFunctionArgumentargument =PhpElementsUtil.findAttributeArgumentByName("repositoryClass",pair.getFirst());
149+
if (argumentinstanceofPhpExpectedFunctionClassConstantArgument) {
150+
StringrepositoryClassRaw = ((PhpExpectedFunctionClassConstantArgument)argument).getClassFqn();
151+
if (StringUtils.isNotBlank(repositoryClassRaw)) {
152+
repositoryClass =repositoryClassRaw;
153+
}
154+
}
155+
156+
pairs.add(Pair.create(
157+
StringUtils.stripStart(pair.getSecond().getFQN(),"\\"),
158+
repositoryClass !=null ?StringUtils.stripStart(repositoryClass,"\\") :null
159+
));
160+
161+
returnfalse;
162+
},MODEL_CLASS_ANNOTATION));
163+
137164
returnpairs;
138165
}
139166

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
packagefr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver;
2+
3+
importcom.intellij.psi.PsiFile;
4+
importcom.jetbrains.php.lang.psi.PhpFile;
5+
importcom.jetbrains.php.lang.psi.elements.Field;
6+
importcom.jetbrains.php.lang.psi.elements.PhpAttribute;
7+
importcom.jetbrains.php.lang.psi.elements.PhpClass;
8+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionArgument;
9+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionClassConstantArgument;
10+
importfr.adrienbrault.idea.symfony2plugin.doctrine.dict.DoctrineModelField;
11+
importfr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineMetadataModel;
12+
importfr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
13+
importorg.apache.commons.lang.StringUtils;
14+
importorg.jetbrains.annotations.NotNull;
15+
16+
importjava.util.ArrayList;
17+
importjava.util.Collection;
18+
importjava.util.HashMap;
19+
importjava.util.Map;
20+
21+
/**
22+
*
23+
* example:
24+
* - "#[Column(type: "decimal", precision: 2, scale: 1)]"
25+
*
26+
* @link https://www.doctrine-project.org/projects/doctrine-orm/en/2.9/reference/attributes-reference.html#attrref_table
27+
* @author Daniel Espendiller <daniel@espendiller.net>
28+
*/
29+
publicclassDoctrinePhpAttributeMappingDriverimplementsDoctrineMappingDriverInterface {
30+
@Override
31+
publicDoctrineMetadataModelgetMetadata(@NotNullDoctrineMappingDriverArgumentsarguments) {
32+
PsiFilepsiFile =arguments.getPsiFile();
33+
if(!(psiFileinstanceofPhpFile)) {
34+
returnnull;
35+
}
36+
37+
Collection<DoctrineModelField>fields =newArrayList<>();
38+
DoctrineMetadataModelmodel =newDoctrineMetadataModel(fields);
39+
40+
for (PhpClassphpClass :PhpElementsUtil.getClassesInterface(arguments.getProject(),arguments.getClassName())) {
41+
for (PhpAttributeattribute :phpClass.getAttributes()) {
42+
Stringfqn =attribute.getFQN();
43+
if (fqn ==null) {
44+
continue;
45+
}
46+
47+
if (!PhpElementsUtil.isEqualClassName(fqn,"\\Doctrine\\ORM\\Mapping\\Table")) {
48+
continue;
49+
}
50+
51+
Stringname =PhpElementsUtil.findAttributeArgumentByNameAsString("name",attribute);
52+
if (name !=null) {
53+
model.setTable(name);
54+
}
55+
}
56+
57+
Map<String,Map<String,String>>maps =newHashMap<>();
58+
for(Fieldfield:phpClass.getFields()) {
59+
if (field.isConstant()) {
60+
continue;
61+
}
62+
63+
DoctrineModelFielddoctrineModelField =newDoctrineModelField(field.getName());
64+
doctrineModelField.addTarget(field);
65+
66+
booleanisField =false;
67+
for (PhpAttributeattribute :field.getAttributes()) {
68+
Stringfqn =attribute.getFQN();
69+
if (fqn ==null) {
70+
continue;
71+
}
72+
73+
if (PhpElementsUtil.isEqualClassName(fqn,"\\Doctrine\\ORM\\Mapping\\Column")) {
74+
isField =true;
75+
76+
Stringname =PhpElementsUtil.findAttributeArgumentByNameAsString("name",attribute);
77+
if (name !=null) {
78+
doctrineModelField.setColumn(name);
79+
}
80+
81+
Stringtype =PhpElementsUtil.findAttributeArgumentByNameAsString("type",attribute);
82+
if (type !=null) {
83+
doctrineModelField.setTypeName(type);
84+
}
85+
}
86+
87+
if (PhpElementsUtil.isEqualClassName(fqn,"\\Doctrine\\ORM\\Mapping\\OneToOne","\\Doctrine\\ORM\\Mapping\\ManyToOne","\\Doctrine\\ORM\\Mapping\\OneToMany","\\Doctrine\\ORM\\Mapping\\ManyToMany")) {
88+
isField =true;
89+
90+
Stringsubstring =fqn.substring(fqn.lastIndexOf("\\") +1);
91+
doctrineModelField.setRelationType(substring);
92+
93+
PhpExpectedFunctionArgumentargument =PhpElementsUtil.findAttributeArgumentByName("targetEntity",attribute);
94+
if (argumentinstanceofPhpExpectedFunctionClassConstantArgument) {
95+
StringrepositoryClassRaw = ((PhpExpectedFunctionClassConstantArgument)argument).getClassFqn();
96+
if (StringUtils.isNotBlank(repositoryClassRaw)) {
97+
doctrineModelField.setRelation(repositoryClassRaw);
98+
}
99+
}
100+
}
101+
}
102+
103+
if (isField) {
104+
fields.add(doctrineModelField);
105+
}
106+
}
107+
}
108+
109+
returnmodel;
110+
}
111+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ public class DoctrineMetadataUtil {
4040
privatestaticfinalKey<CachedValue<Set<String>>>CLASS_KEYS =newKey<>("CLASS_KEYS");
4141

4242
privatestaticDoctrineMappingDriverInterface[]MAPPING_DRIVERS =newDoctrineMappingDriverInterface[] {
43+
newDoctrinePhpMappingDriver(),
44+
newDoctrinePhpAttributeMappingDriver(),
4345
newDoctrineXmlMappingDriver(),
4446
newDoctrineYamlMappingDriver(),
45-
newDoctrinePhpMappingDriver(),
4647
};
4748

4849
@NotNull
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
packagefr.adrienbrault.idea.symfony2plugin.stubs.indexes.visitor;
2+
3+
importcom.intellij.openapi.util.Pair;
4+
importcom.intellij.psi.PsiElement;
5+
importcom.intellij.psi.PsiRecursiveElementWalkingVisitor;
6+
importcom.intellij.util.Processor;
7+
importcom.jetbrains.php.lang.psi.elements.PhpAttribute;
8+
importcom.jetbrains.php.lang.psi.elements.PhpAttributesList;
9+
importcom.jetbrains.php.lang.psi.elements.PhpClass;
10+
importfr.adrienbrault.idea.symfony2plugin.util.PhpElementsUtil;
11+
importorg.jetbrains.annotations.NotNull;
12+
13+
/**
14+
* Visit class attributes; filtered by instance
15+
*
16+
* @author Daniel Espendiller <daniel@espendiller.net>
17+
*/
18+
publicclassAttributeElementWalkingVisitorextendsPsiRecursiveElementWalkingVisitor {
19+
20+
@NotNull
21+
privatefinalProcessor<Pair<PhpAttribute,PhpClass>>phpDocTagProcessor;
22+
23+
@NotNull
24+
privatefinalString[]annotations;
25+
26+
publicAttributeElementWalkingVisitor(@NotNullProcessor<Pair<PhpAttribute,PhpClass>>phpDocTagProcessor,@NotNullString...annotations) {
27+
this.phpDocTagProcessor =phpDocTagProcessor;
28+
this.annotations =annotations;
29+
}
30+
31+
@Override
32+
publicvoidvisitElement(@NotNullPsiElementelement) {
33+
if ((elementinstanceofPhpAttributesList)) {
34+
visitPhpAttributesList((PhpAttributesList)element);
35+
}
36+
37+
super.visitElement(element);
38+
}
39+
40+
privatevoidvisitPhpAttributesList(@NotNullPhpAttributesListphpAttributesList) {
41+
PsiElementparent =phpAttributesList.getParent();
42+
43+
if (parentinstanceofPhpClass) {
44+
for (PhpAttributeattribute :phpAttributesList.getAttributes()) {
45+
Stringfqn =attribute.getFQN();
46+
if (fqn ==null) {
47+
continue;
48+
}
49+
50+
if (PhpElementsUtil.isEqualClassName(fqn,annotations)) {
51+
this.phpDocTagProcessor.process(Pair.create(attribute, (PhpClass)parent));
52+
}
53+
}
54+
}
55+
}
56+
}

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
importcom.jetbrains.php.lang.psi.elements.impl.ConstantImpl;
2727
importcom.jetbrains.php.lang.psi.elements.impl.PhpDefineImpl;
2828
importcom.jetbrains.php.lang.psi.resolve.types.PhpType;
29+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionArgument;
30+
importcom.jetbrains.php.lang.psi.stubs.indexes.expectedArguments.PhpExpectedFunctionScalarArgument;
2931
importcom.jetbrains.php.phpunit.PhpUnitUtil;
3032
importcom.jetbrains.php.refactoring.PhpAliasImporter;
3133
importfr.adrienbrault.idea.symfony2plugin.dic.MethodReferenceBag;
@@ -785,6 +787,16 @@ public static boolean isEqualClassName(@Nullable PhpClass phpClass, @Nullable St
785787
.equals(StringUtils.stripStart(compareClassName,"\\"));
786788
}
787789

790+
publicstaticbooleanisEqualClassName(@NotNullStringphpClass,@NotNullString ...compareClassNames) {
791+
for (StringcompareClassName :compareClassNames) {
792+
if (Objects.equals(StringUtils.stripStart(phpClass,"\\"),StringUtils.stripStart(compareClassName,"\\"))) {
793+
returntrue;
794+
}
795+
}
796+
797+
returnfalse;
798+
}
799+
788800
@NotNull
789801
publicstaticPsiElement[]getMethodParameterReferences(@NotNullMethodmethod,intparameterIndex) {
790802
// we dont have a parameter on resolved method
@@ -1502,6 +1514,31 @@ public static String getMethodReferenceStringValueParameter(@NotNull MethodRefer
15021514
returnnull;
15031515
}
15041516

1517+
publicstaticPhpExpectedFunctionArgumentfindAttributeArgumentByName(@NotNullStringattributeName,@NotNullPhpAttributephpAttribute) {
1518+
for (PhpAttribute.PhpAttributeArgumentargument :phpAttribute.getArguments()) {
1519+
Stringname =argument.getName();
1520+
if (!attributeName.equals(name)) {
1521+
continue;
1522+
}
1523+
1524+
returnargument.getArgument();
1525+
}
1526+
1527+
returnnull;
1528+
}
1529+
1530+
@Nullable
1531+
publicstaticStringfindAttributeArgumentByNameAsString(@NotNullStringattributeName,@NotNullPhpAttributephpAttribute) {
1532+
PhpExpectedFunctionArgumentattributeArgumentByName =findAttributeArgumentByName(attributeName,phpAttribute);
1533+
if (attributeArgumentByNameinstanceofPhpExpectedFunctionScalarArgument) {
1534+
Stringvalue =PsiElementUtils.trimQuote(attributeArgumentByName.getValue());
1535+
if (StringUtils.isNotBlank(value)) {
1536+
returnvalue;
1537+
}
1538+
}
1539+
1540+
returnnull;
1541+
}
15051542
/**
15061543
* Visit and collect all variables in given scope
15071544
*/

‎src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/DoctrineUtilTest.java‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,43 @@ public void testGetClassRepositoryPairForStringValue() {
3939
assertEquals("MyBundle\\Entity\\Repository\\AddressRepository",next.getSecond());
4040
}
4141

42+
/**
43+
* @see DoctrineUtil#getClassRepositoryPair
44+
*/
45+
publicvoidtestGetClassRepositoryPairForPhp8AttributeStringValue() {
46+
myFixture.configureByText(PhpFileType.INSTANCE,"<?php class Foobar {};");
47+
48+
PsiFilepsiFileFromText =PhpPsiElementFactory.createPsiFileFromText(getProject(),"" +
49+
"<?php\n" +
50+
"namespace Foo;\n" +
51+
"\n" +
52+
"use Foobar;\n" +
53+
"use Doctrine\\ORM\\Mapping\\Entity;\n" +
54+
"\n" +
55+
"#[Entity(repositoryClass: Foobar::class, readOnly: false)]\n" +
56+
"class Apple\n" +
57+
"{\n" +
58+
"}\n" +
59+
"\n" +
60+
"\n" +
61+
"#[Entity()]\n" +
62+
"class Car\n" +
63+
"{\n" +
64+
"}\n" +
65+
"\n"
66+
);
67+
68+
Collection<Pair<String,String>>classRepositoryPair =DoctrineUtil.getClassRepositoryPair(psiFileFromText);
69+
70+
Pair<String,String>apple =classRepositoryPair.stream().filter(stringStringPair ->"Foo\\Apple".equals(stringStringPair.getFirst())).findFirst().get();
71+
assertEquals("Foo\\Apple",apple.getFirst());
72+
assertEquals("Foobar",apple.getSecond());
73+
74+
Pair<String,String>car =classRepositoryPair.stream().filter(stringStringPair ->"Foo\\Car".equals(stringStringPair.getFirst())).findFirst().get();
75+
assertEquals("Foo\\Car",car.getFirst());
76+
assertNull(car.getSecond());
77+
}
78+
4279
/**
4380
* @see DoctrineUtil#getClassRepositoryPair
4481
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
packagefr.adrienbrault.idea.symfony2plugin.tests.doctrine.metadata.driver;
2+
3+
importcom.jetbrains.php.lang.psi.PhpPsiElementFactory;
4+
importfr.adrienbrault.idea.symfony2plugin.doctrine.metadata.dict.DoctrineMetadataModel;
5+
importfr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.DoctrineMappingDriverArguments;
6+
importfr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.DoctrinePhpAttributeMappingDriver;
7+
importfr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.DoctrinePhpMappingDriver;
8+
importfr.adrienbrault.idea.symfony2plugin.tests.SymfonyLightCodeInsightFixtureTestCase;
9+
10+
/**
11+
* @author Daniel Espendiller <daniel@espendiller.net>
12+
*
13+
* @see fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.DoctrinePhpAttributeMappingDriver
14+
*/
15+
publicclassDoctrinePhpAttributeMappingDriverTestextendsSymfonyLightCodeInsightFixtureTestCase {
16+
publicvoidsetUp()throwsException {
17+
super.setUp();
18+
myFixture.configureFromExistingVirtualFile(myFixture.copyFileToProject("attributes.php"));
19+
}
20+
21+
publicStringgetTestDataPath() {
22+
return"src/test/java/fr/adrienbrault/idea/symfony2plugin/tests/doctrine/metadata/driver/fixtures";
23+
}
24+
25+
/**
26+
* @see DoctrinePhpMappingDriver#getMetadata(fr.adrienbrault.idea.symfony2plugin.doctrine.metadata.driver.DoctrineMappingDriverArguments)
27+
*/
28+
publicvoidtestPhpAttributesMetadata() {
29+
DoctrineMetadataModelmetadata =createOrmMetadata();
30+
31+
assertEquals("table_name",metadata.getTable());
32+
33+
assertEquals("string",metadata.getField("email").getTypeName());
34+
assertEquals("string",metadata.getField("emailTrait").getTypeName());
35+
36+
assertEquals("\\ORM\\Foobar\\Egg",metadata.getField("apple").getRelation());
37+
assertEquals("ManyToOne",metadata.getField("apple").getRelationType());
38+
39+
assertEquals("\\ORM\\Foobar\\Egg",metadata.getField("egg").getRelation());
40+
assertEquals("ManyToMany",metadata.getField("egg").getRelationType());
41+
42+
assertEquals("\\ORM\\Foobar\\Egg",metadata.getField("address").getRelation());
43+
assertEquals("OneToOne",metadata.getField("address").getRelationType());
44+
45+
assertEquals("\\ORM\\Foobar\\Egg",metadata.getField("phonenumbers").getRelation());
46+
assertEquals("OneToMany",metadata.getField("phonenumbers").getRelationType());
47+
48+
assertEquals("\\Doctrine\\Orm\\MyTrait\\Egg",metadata.getField("appleTrait").getRelation());
49+
assertEquals("ManyToOne",metadata.getField("appleTrait").getRelationType());
50+
}
51+
52+
privateDoctrineMetadataModelcreateOrmMetadata() {
53+
returnnewDoctrinePhpAttributeMappingDriver().getMetadata(
54+
newDoctrineMappingDriverArguments(getProject(),PhpPsiElementFactory.createPsiFileFromText(getProject(),"<?php $foo = null;"),"\\ORM\\Attributes\\AttributeEntity")
55+
);
56+
}
57+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp