Skip to content

Commit aec2b74

Browse files
authored
Merge pull request #168 from cedricziel/extbase-model-property-foo
Detect ObjectStorage contents and add it to the field types
2 parents 00e18c2 + c6f9445 commit aec2b74

File tree

7 files changed

+245
-2
lines changed

7 files changed

+245
-2
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ buildscript {
99
}
1010

1111
plugins {
12-
id "org.jetbrains.intellij" version "0.3.1"
12+
id "org.jetbrains.intellij" version "0.3.2"
1313
id "de.undercouch.download" version "3.2.0"
1414
id 'com.palantir.git-version' version "0.9.1"
1515
}
@@ -41,7 +41,7 @@ version gitVersion()
4141
apply plugin: 'de.undercouch.download'
4242
task downloadPsiViewerPlugin() {
4343
download {
44-
src 'https://plugins.jetbrains.com/plugin/download?updateId=31087'
44+
src 'https://plugins.jetbrains.com/plugin/download?updateId=46431'
4545
dest new File("${buildDir}/tmp/plugins/", 'PsiViewer.jar')
4646
onlyIfNewer true
4747
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package com.cedricziel.idea.typo3.extbase.persistence;
2+
3+
import com.intellij.openapi.project.DumbService;
4+
import com.intellij.openapi.project.Project;
5+
import com.intellij.psi.PsiElement;
6+
import com.jetbrains.php.PhpClassHierarchyUtils;
7+
import com.jetbrains.php.PhpIndex;
8+
import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment;
9+
import com.jetbrains.php.lang.documentation.phpdoc.psi.tags.PhpDocParamTag;
10+
import com.jetbrains.php.lang.psi.elements.*;
11+
import com.jetbrains.php.lang.psi.resolve.types.PhpType;
12+
import com.jetbrains.php.lang.psi.resolve.types.PhpTypeProvider3;
13+
import org.jetbrains.annotations.Nullable;
14+
15+
import java.util.Collection;
16+
import java.util.Set;
17+
import java.util.regex.Matcher;
18+
import java.util.regex.Pattern;
19+
20+
public class ExtbaseModelCollectionReturnTypeProvider implements PhpTypeProvider3 {
21+
22+
public static final String TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY = "TYPO3\\CMS\\Extbase\\DomainObject\\AbstractEntity";
23+
24+
@Override
25+
public char getKey() {
26+
return '\u0278';
27+
}
28+
29+
@Nullable
30+
@Override
31+
public PhpType getType(PsiElement psiElement) {
32+
if (DumbService.getInstance(psiElement.getProject()).isDumb()) {
33+
return null;
34+
}
35+
36+
if (!(psiElement instanceof Field) && !isGetter(psiElement)) {
37+
return null;
38+
}
39+
40+
if (!isEntityClass(psiElement)) {
41+
return null;
42+
}
43+
44+
return extractReturnType(psiElement);
45+
}
46+
47+
private PhpType extractReturnType(PsiElement psiElement) {
48+
Field field;
49+
if (psiElement instanceof MethodReference) {
50+
field = extractFieldFromGetter((MethodReference) psiElement);
51+
} else if (psiElement instanceof Method) {
52+
field = extractFieldFromGetter((Method) psiElement);
53+
} else {
54+
field = ((Field) psiElement);
55+
}
56+
57+
if (field == null) {
58+
return null;
59+
}
60+
61+
PhpDocComment docComment = field.getDocComment();
62+
if (docComment == null) {
63+
return null;
64+
}
65+
66+
PhpDocParamTag varTag = docComment.getVarTag();
67+
if (varTag == null) {
68+
return null;
69+
}
70+
71+
String text = varTag.getText();
72+
if (!text.contains("ObjectStorage<")) {
73+
return null;
74+
}
75+
76+
PhpType phpType = new PhpType();
77+
78+
String pattern = "<(.*?)>";
79+
Pattern compiled = Pattern.compile(pattern);
80+
Matcher matcher = compiled.matcher(text);
81+
while (matcher.find()) {
82+
phpType.add(matcher.group(1) + "[]");
83+
}
84+
85+
return phpType;
86+
}
87+
88+
private Field extractFieldFromGetter(MethodReference methodReference) {
89+
String name = methodReference.getName();
90+
if (name == null) {
91+
return null;
92+
}
93+
94+
String substring = name.substring(2);
95+
char[] cArr = substring.toCharArray();
96+
cArr[0] = Character.toLowerCase(cArr[0]);
97+
98+
String propertyName = new String(cArr);
99+
100+
PsiElement method = methodReference.resolve();
101+
if (!(method instanceof Method)) {
102+
return null;
103+
}
104+
105+
PhpClass containingClass = ((Method) method).getContainingClass();
106+
107+
if (containingClass == null) {
108+
return null;
109+
}
110+
111+
return containingClass.findFieldByName(propertyName, true);
112+
}
113+
114+
private Field extractFieldFromGetter(Method method) {
115+
String substring = method.getName().substring(3);
116+
char[] cArr = substring.toCharArray();
117+
cArr[0] = Character.toLowerCase(cArr[0]);
118+
119+
String propertyName = new String(cArr);
120+
121+
PhpClass containingClass = method.getContainingClass();
122+
123+
if (containingClass == null) {
124+
return null;
125+
}
126+
127+
return containingClass.findFieldByName(propertyName, false);
128+
}
129+
130+
private boolean isEntityClass(PsiElement psiElement) {
131+
PhpClass containingClass = ((PhpClassMember) psiElement).getContainingClass();
132+
if (containingClass == null) {
133+
return false;
134+
}
135+
136+
Collection<PhpClass> classesByFQN = PhpIndex.getInstance(psiElement.getProject()).getClassesByFQN(TYPO3_CMS_EXTBASE_DOMAIN_OBJECT_ABSTRACT_ENTITY);
137+
if (classesByFQN.isEmpty()) {
138+
return false;
139+
}
140+
141+
PhpClass abstractEntityClass = classesByFQN.iterator().next();
142+
return PhpClassHierarchyUtils.isSuperClass(abstractEntityClass, containingClass, true);
143+
}
144+
145+
private boolean isGetter(PsiElement psiElement) {
146+
return (psiElement instanceof Method) && ((Method) psiElement).getName().startsWith("get");
147+
}
148+
149+
@Override
150+
public Collection<? extends PhpNamedElement> getBySignature(String expression, Set<String> visited, int depth, Project project) {
151+
return PhpIndex.getInstance(project).getBySignature(expression);
152+
}
153+
}

src/main/resources/META-INF/plugin.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ It is a great inspiration for possible solutions and parts of the code.</p>
143143

144144
<!-- extbase persistence -->
145145
<referenceResolver implementation="com.cedricziel.idea.typo3.extbase.persistence.ExtbasePersistenceReferenceResolver"/>
146+
147+
<typeProvider3 implementation="com.cedricziel.idea.typo3.extbase.persistence.ExtbaseModelCollectionReturnTypeProvider"/>
146148
</extensions>
147149

148150
<extensions defaultExtensionNs="com.intellij">
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package com.cedricziel.idea.typo3.extbase.persistence;
2+
3+
import com.intellij.psi.PsiElement;
4+
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
5+
import com.jetbrains.php.lang.psi.elements.Field;
6+
import com.jetbrains.php.lang.psi.elements.Method;
7+
8+
import java.util.Set;
9+
10+
public class ExtbaseModelCollectionReturnTypeProviderTest extends LightCodeInsightFixtureTestCase {
11+
@Override
12+
protected String getTestDataPath() {
13+
return "testData/com/cedricziel/idea/typo3/extbase/persistence";
14+
}
15+
16+
public void testResolvesObjectStoragePropertiesToObjectTypes() {
17+
myFixture.copyFileToProject("PersistenceMocks.php");
18+
myFixture.configureByFile("FieldTypeProvider.php");
19+
20+
PsiElement elementAtCaret = myFixture.getElementAtCaret();
21+
22+
assertInstanceOf(elementAtCaret, Field.class);
23+
24+
Set<String> types = ((Field) elementAtCaret).getInferredType().getTypes();
25+
assertTrue(types.contains("\\My\\Extension\\Domain\\Model\\Book[]"));
26+
}
27+
28+
public void testResolvesObjectStoragePropertiesToObjectTypesOnGetters() {
29+
myFixture.copyFileToProject("PersistenceMocks.php");
30+
myFixture.configureByFile("MethodTypeProvider.php");
31+
32+
PsiElement elementAtCaret = myFixture.getElementAtCaret();
33+
34+
assertInstanceOf(elementAtCaret, Method.class);
35+
36+
Set<String> types = ((Method) elementAtCaret).getInferredType().getTypes();
37+
assertTrue(types.contains("\\My\\Extension\\Domain\\Model\\Book[]"));
38+
}
39+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace MyExt {
4+
5+
class FieldTypeProvider extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
6+
{
7+
/**
8+
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\My\Extension\Domain\Model\Book>
9+
*/
10+
protected $book;
11+
12+
public function getBook()
13+
{
14+
foreach ($this-><caret>book as $b) {
15+
$b->getAuthor();
16+
}
17+
}
18+
}
19+
20+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace MyExt {
4+
5+
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
6+
7+
class MethodTypeProvider extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
8+
{
9+
/**
10+
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\My\Extension\Domain\Model\Book>
11+
*/
12+
protected $book;
13+
14+
public function getBook() : ObjectStorage
15+
{
16+
return $this->book;
17+
}
18+
19+
public function foo()
20+
{
21+
return $this-><caret>getBook();
22+
}
23+
}
24+
25+
}

testData/com/cedricziel/idea/typo3/extbase/persistence/PersistenceMocks.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ class QueryResultInterface {}
66
class ObjectStorage {}
77
}
88

9+
namespace TYPO3\CMS\Extbase\DomainObject {
10+
class AbstractEntity {}
11+
}
12+
913
namespace My\Extension\Domain\Model {
1014
class Book extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity {
1115
/**

0 commit comments

Comments
 (0)