Skip to main content

Notice: this Wiki will be going read only early in 2024 and edits will no longer be possible. Please see: https://gitlab.eclipse.org/eclipsefdn/helpdesk/-/wikis/Wiki-shutdown-plan for the plan.

Jump to: navigation, search

Difference between revisions of "Extending PDT"

(Contributing to the structured model)
m
 
(21 intermediate revisions by 4 users not shown)
Line 1: Line 1:
== Purpose ==
+
== Purpose ==
There are different purposes for extending PDT. One of them is adding support for specific PHP framework to the IDE features like: Code Assist, Navigation (CTRL + click), Presentation (Outline, PHP Explorer). In this document we'll describe how to achieve these goals using PDT extension points.  
+
 
If you have any questions regarding this document, please ask them on [mailto:pdt-dev@eclipse.org PDT-Dev] mailing list.
+
There are different purposes for extending PDT. One of them is adding support for specific PHP framework to the IDE features like: Code Assist, Navigation (CTRL + click), Presentation (Outline, PHP Explorer). In this document we'll describe how to achieve these goals using PDT extension points. If you have any questions regarding this document, please ask them on [mailto:pdt-dev@eclipse.org PDT-Dev] mailing list.  
 +
 
 +
== Extending  ==
 +
 
 +
=== Code Assist  ===
  
== Extending ==
 
=== Code Assist ===
 
 
==== Type inference hinting  ====
 
==== Type inference hinting  ====
Suppose your framework uses the following language structure for object instantiation:
 
  
  $myObject = ClassRegistry::init('MyClass');
+
Suppose your framework uses the following language structure for object instantiation:  
  
In this case PDT type inference engine is unable to detect the type of $myObject variable, so we'll have to add a specific rule that helps him. The following extension point allows to provide additional rules to the PHP type inference engine:
+
<source lang="php">
 +
$myObject = ClassRegistry::init('MyClass');
 +
</source>
 +
 
 +
In this case PDT type inference engine is unable to detect the type of $myObject variable, so we'll have to add a specific rule that helps him. The following extension point allows to provide additional rules to the PHP type inference engine:  
  
 
   org.eclipse.php.core.goalEvaluatorFactories
 
   org.eclipse.php.core.goalEvaluatorFactories
  
For our example what we need to contribute is:
+
For our example what we need to contribute is:  
  
  &lt;extension point="org.eclipse.php.core.goalEvaluatorFactories"&gt;
+
<source lang="xml">
    &lt;factory
+
<extension point="org.eclipse.php.core.goalEvaluatorFactories">
          class="com.xyz.php.fmwrk.XYZGoalEvaluatorFactory"
+
  <factory
          priority="100"&gt;
+
      class="com.xyz.php.fmwrk.XYZGoalEvaluatorFactory"
    &lt;/factory&gt;
+
      priority="100">
  &lt;/extension&gt;
+
  </factory>
 +
</extension>
 +
</source>
  
Please note the priority is set to 100 in order to override the default PHP goal evaluator (its priority is 10).
+
Please note the priority is set to 100 in order to override the default PHP goal evaluator (its priority is 10).  
  
<pre>
+
<source lang="java">
 
public class XYZGoalEvaluatorFactory implements IGoalEvaluatorFactory {
 
public class XYZGoalEvaluatorFactory implements IGoalEvaluatorFactory {
  
Line 72: Line 79:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
 +
<source lang="java">
  
<pre>
 
 
public class DummyGoalEvaluator extends GoalEvaluator {
 
public class DummyGoalEvaluator extends GoalEvaluator {
 
   private String className;
 
   private String className;
Line 96: Line 103:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>  
 +
That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.
  
That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.
+
==== Code assist strategies  ====
  
==== Code assist strategies ====
+
Some PHP frameworks provide class fields or methods that are not declared explicitly in a code. For instance, Zend Framework view helpers can be accessed from the view class using:  
Some PHP frameworks provide class fields or methods that are not declared explicitly in a code. For instance, Zend Framework view helpers can be accessed from the view class using:
+
  
   $this->helperName()
+
   $this-&gt;helperName()
  
Behind the scenes, view loads the Zend_View_Helper_HelperName class (note the naming convention), creates an object instance of it, and calls its helperName() method.
+
Behind the scenes, view loads the Zend_View_Helper_HelperName class (note the naming convention), creates an object instance of it, and calls its helperName() method.  
  
In order to provide Code Assist for "helperName" after "$this->" we'll need to extend the following extensions:
+
In order to provide Code Assist for "helperName" after "$this-&gt;" we'll need to extend the following extensions:  
  
 
   org.eclipse.php.core.completionContextResolvers
 
   org.eclipse.php.core.completionContextResolvers
  org.eclipse.php.core.completionStrategyFactories
+
org.eclipse.php.core.completionStrategyFactories
  
First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.
+
First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.  
 +
<source lang="java">
  
<pre>
 
 
public class XYZCompletionContext extends ClassMemberContext {
 
public class XYZCompletionContext extends ClassMemberContext {
  
Line 150: Line 157:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
 +
 +
Register new context in the completion engine:
 +
<source lang="java">
  
Register new context in the completion engine:
 
 
<pre>
 
 
public class XYZContextResolver extends CompletionContextResolver
 
public class XYZContextResolver extends CompletionContextResolver
 
     implements ICompletionContextResolver {
 
     implements ICompletionContextResolver {
Line 162: Line 169:
 
     }
 
     }
 
}
 
}
</pre>
+
</source>
 +
 +
Add the completion strategy that does the actual job - adds "helperName" method to the completion list:
  
Add the completion strategy that does the actual job - adds "helperName" method to the completion list:
+
<source lang="java">
  
<pre>
 
 
public class XYZCompletionStrategy
 
public class XYZCompletionStrategy
 
     extends ClassMembersStrategy implements ICompletionStrategy {
 
     extends ClassMembersStrategy implements ICompletionStrategy {
Line 193: Line 201:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>  
  
Create an association between the completion context and completion strategy:
+
Create an association between the completion context and completion strategy:  
 +
 
 +
<source lang="java">
  
<pre>
 
 
public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
 
public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
  
Line 211: Line 220:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
 +
 +
And, finally, plugin.xml contributions:
 +
 
 +
<source lang="xml">
 +
 
 +
<extension point="org.eclipse.php.core.completionStrategyFactories">
 +
    <factory class="com.xyz.php.framework.XYZCompletionStrategyFactory"/>
 +
</extension>
 +
 +
<extension point="org.eclipse.php.core.completionContextResolvers">
 +
    <resolver class="com.xyz.php.framework.XYZContextResolver"/>
 +
</extension>
 +
 
 +
</source>
 +
 
 +
 
 +
=== CTRL + click  ===
 +
 
 +
==== Contributing to index  ====
 +
 
 +
When you click while holding CTRL pressed on some PHP element in an editor, or when you ask for Code Assist by pressing CTRL+Space selection engine (or completion engine in case of Code Assist) tries to resolve PHP elements by accessing index. Index contains all the PHP element declarations and references that present in a workspace. The following example indexes non-existing select(), delete() and insert() methods from [[Extending PDT#Snippet1|code snippet #1]].
 +
 
 +
Extension point that we gonna use is:
 +
 
 +
  org.eclipse.php.core.phpIndexingVisitors
 +
 
 +
Implement the indexing visitor:
 +
 
 +
<source lang="java">
 +
 
 +
public class XYZIndexingVisitorExtension
 +
    extends PhpIndexingVisitorExtension {
 +
 
 +
  private ClassDeclaration currentClass;
 +
  private MethodDeclaration currentMethod;
 +
  private Set<Scalar> deferredMethods = new HashSet<Scalar>();
 +
 
 +
  public boolean visit(TypeDeclaration s) throws Exception {
 +
    if (s instanceof ClassDeclaration) {
 +
      currentClass = (ClassDeclaration) s;
 +
    }
 +
    return true;
 +
  }
 +
 
 +
  public boolean endvisit(TypeDeclaration s) throws Exception {
 +
    for (Scalar method : deferredMethods) {
 +
      int start = method.sourceStart();
 +
      int length = method.sourceEnd() - method.sourceStart();
 +
      String name = method.getValue().replaceAll("['\"]", "");
 +
 
 +
      modifyDeclaration(method, new DeclarationInfo(IModelElement.METHOD,
 +
          Modifiers.AccPublic, start, length, start, length, name,
 +
          null, null, currentClass.getName()));
 +
    }
 +
    currentClass = null;
 +
    deferredMethods.clear();
 +
    return true;
 +
  }
 +
 
 +
  public boolean visit(MethodDeclaration s) throws Exception {
 +
    if (currentClass != null && "__call".equals(s.getName())) {
 +
      currentMethod = s;
 +
    }
 +
    return true;
 +
  }
 +
 
 +
  public boolean endvisit(MethodDeclaration s) throws Exception {
 +
    currentMethod = null;
 +
    return true;
 +
  }
 +
 
 +
  public boolean visit(Statement s) throws Exception {
 +
    if (s instanceof IfStatement && currentClass != null
 +
        && currentMethod != null) {
 +
 
 +
      Expression condition = ((IfStatement) s).getCondition();
 +
      if (condition instanceof InfixExpression) {
 +
 
 +
        InfixExpression infixExp = (InfixExpression) condition;
 +
        if (infixExp.getOperatorType() == InfixExpression.OP_IS_EQUAL) {
 +
 
 +
          Expression rightExp = infixExp.getRight();
 +
          if (rightExp instanceof Scalar) {
 +
 
 +
            Scalar scalar = (Scalar) rightExp;
 +
            if (scalar.getScalarType() == Scalar.TYPE_STRING) {
 +
              deferredMethods.add(scalar);
 +
            }
 +
          }
 +
        }
 +
      }
 +
    }
 +
    return true;
 +
  }
 +
}
 +
</source>
 +
 
 +
Note: this visitor is very similar to the structured model contribution [[Extending PDT#SModelVisitor|visitor]].
 +
 
 +
Finally, register this class in plugin.xml:
 +
 
 +
  &lt;extension point="org.eclipse.php.core.phpIndexingVisitors"&gt;
 +
  &lt;visitor class="com.xyz.php.framework.XYZIndexingVisitorExtension" /&gt;
 +
&lt;/extension&gt;
 +
 
 +
=== Outline and PHP Explorer  ===
 +
 
  
And, finally, plugin.xml contributions:
 
  
  <extension point="org.eclipse.php.core.completionStrategyFactories">
+
Adding additional nodes to the PHP explorer can be achieved in 2 different ways.
      <factory class="com.xyz.php.framework.XYZCompletionStrategyFactory"/>
+
  </extension>
+
 
+
  <extension point="org.eclipse.php.core.completionContextResolvers">
+
      <resolver class="com.xyz.php.framework.XYZContextResolver"/>
+
  </extension>
+
  
=== CTRL + click ===
+
==== Contributing to the structured model  ====
  
==== Contributing to index ====
+
Sometimes you need to present some elements that don't really exist. For example, you may want to present fields and methods that are processed using __get() or __call() magic methods:
When you click while holding CTRL pressed on some PHP element in an editor, or when you ask for Code Assist by pressing CTRL+Space the selection engine (or completion engine in case of Code Assist) tries to resolve PHP elements by accessing index. Index contains all the PHP element declarations and references that present in a workspace. The following example indexes non-existing select(), delete() and insert() methods from [[Extending_PDT_2.2#Snippet1|snippet #1]].
+
  
=== Outline and PHP Explorer ===
+
<span id="Snippet1">Code snippet #1</span>
==== Contributing to the structured model ====
+
Sometimes you need to present some elements that don't really exist. For example, you may want to present fields and methods that are processed using __get() or __call() magic methods:
+
  
<span id="Snippet1">Code snippet #1</span>
+
<source lang="java">/**
<pre>
+
/**
+
 
  * Please don't judge me for this code, it's for the sake of example only
 
  * Please don't judge me for this code, it's for the sake of example only
 
  * (even though I've seen PHP code like this in real PHP applications :-])
 
  * (even though I've seen PHP code like this in real PHP applications :-])
Line 253: Line 357:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
 
+
Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:
+
Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:  
  
 
   org.eclipse.php.core.phpSourceElementRequestors
 
   org.eclipse.php.core.phpSourceElementRequestors
  
plugin.xml contribution:
+
plugin.xml contribution:  
  
   <extension point="org.eclipse.php.core.phpSourceElementRequestors">
+
   &lt;extension point="org.eclipse.php.core.phpSourceElementRequestors"&gt;
      <requestor
+
    &lt;requestor
            class="com.xyz.php.framework.XYZSourceElementRequestor">
+
          class="com.xyz.php.framework.XYZSourceElementRequestor"&gt;
      </requestor>
+
    &lt;/requestor&gt;
  </extension>
+
  &lt;/extension&gt;
  
 +
<span id="SModelVisitor">
 
Source element requester extension implementation:
 
Source element requester extension implementation:
 +
</span>
 +
 +
<source lang="java">
  
<pre>
 
 
public class XYZSourceElementRequestor extends
 
public class XYZSourceElementRequestor extends
 
     PHPSourceElementRequestorExtension {
 
     PHPSourceElementRequestorExtension {
Line 339: Line 446:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
 +
 +
Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:
  
Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:
+
[[Image:Requestor1.png]]
  
[[Image:Requestor1.png]]
 
  
=== Language library contributions ===
+
==== Contributing a PHPTreeContentProvider ====
  
PHP language library contains builtin classes, functions and constants - you can see it under PHP project as a "PHP Language Library" node (see image above). This model is used in Code Assist and PHP Function View. By default, it contains only predefined set of PHP extensions. There's a way to contribute more PHP language fragments to this library. The key extension point is:
+
 
 +
Since PDT 3.1.1. additional nodes can be added to the PHP explorer using the '''org.eclipse.php.ui.phpTreeContentProviders''' extension point. Let's say you want
 +
 
 +
to contribute a custom buildpath container using the phpTreeContentProviders extension point:
 +
 
 +
<source lang="xml">
 +
 
 +
<extension point="org.eclipse.php.ui.phpTreeContentProviders">
 +
    <provider labelProvider="com.dubture.composer.core.ui.explorer.PackageTreeLabelProvider" contentProvider="com.dubture.composer.core.ui.explorer.PackageTreeContentProvider"/>
 +
</extension>
 +
 
 +
</source>
 +
 
 +
All you need to do is to create the labelProvider and contentProvider elements, by implementing an org.eclipse.jface.viewers.ILabelProvider and an org.eclipse.jface.viewers.ITreeContentProvider:
 +
 
 +
 
 +
<source lang="java">
 +
 
 +
public class PackageTreeLabelProvider extends LabelProvider
 +
{
 +
 
 +
    @Override
 +
    public String getText(Object element)
 +
    {
 +
        if (element instanceof PackagePath) {
 +
            PackagePath path = (PackagePath) element;
 +
            return path.getPackageName();
 +
        }
 +
       
 +
        return null;
 +
    }
 +
 
 +
    @Override
 +
    public Image getImage(Object element)
 +
    {
 +
        if (element instanceof PackagePath) {
 +
            return PHPPluginImages
 +
                    .get(PHPPluginImages.IMG_OBJS_LIBRARY);
 +
        }
 +
       
 +
        return null;
 +
    }
 +
}
 +
 
 +
</source>
 +
 
 +
 
 +
<source lang="java">
 +
 
 +
public class PackageTreeContentProvider extends ScriptExplorerContentProvider
 +
{
 +
    public PackageTreeContentProvider()
 +
    {
 +
        super(true);
 +
    }
 +
 
 +
 
 +
    @Override
 +
    public Object[] getChildren(Object parentElement)
 +
    {
 +
        if (parentElement instanceof PackagePath) {
 +
           
 +
            PackagePath pPath = (PackagePath) parentElement;
 +
            IScriptProject scriptProject = pPath.getProject();
 +
            IBuildpathEntry entry = pPath.getEntry();
 +
           
 +
            try {
 +
               
 +
                IProjectFragment[] allProjectFragments;
 +
                allProjectFragments = scriptProject.getAllProjectFragments();
 +
                for (IProjectFragment fragment : allProjectFragments) {
 +
                    if (fragment instanceof ExternalProjectFragment) {
 +
                        ExternalProjectFragment external = (ExternalProjectFragment) fragment;
 +
                        if (external.getBuildpathEntry().equals(entry)) {
 +
                            return super.getChildren(external);
 +
                        }
 +
                    }
 +
                }
 +
            } catch (ModelException e) {
 +
                Logger.logException(e);
 +
            }
 +
        } else if (parentElement instanceof ComposerBuildpathContainer) {
 +
            ComposerBuildpathContainer container = (ComposerBuildpathContainer) parentElement;
 +
           
 +
            IAdaptable[] children = container.getChildren();
 +
           
 +
            if (children == null || children.length == 0) {
 +
                return NO_CHILDREN;
 +
            }
 +
           
 +
            return children;
 +
        } else if (parentElement instanceof IScriptProject) {
 +
            try {
 +
                IProject project = ((IScriptProject)parentElement).getProject();
 +
                if (project.hasNature(ComposerNature.NATURE_ID)) {
 +
                    return new Object[]{new ComposerBuildpathContainer((IScriptProject) parentElement)};
 +
                }
 +
            } catch (Exception e) {
 +
                Logger.logException(e);
 +
            }
 +
        }
 +
        return null;
 +
    }
 +
}
 +
 
 +
</source>
 +
 
 +
 
 +
[[Image:PhpTreeContentProvider.png]]
 +
 
 +
The example has been taken from the [https://github.com/pulse00/Composer-Eclipse-Plugin Composer Eclipse Plugin]
 +
 
 +
 
 +
=== Language library contributions  ===
 +
 
 +
PHP language library contains builtin classes, functions and constants - you can see it under PHP project as a "PHP Language Library" node (see image above). This model is used in Code Assist and PHP Function View. By default, it contains only predefined set of PHP extensions. There's a way to contribute more PHP language fragments to this library. The key extension point is:  
  
 
   org.eclipse.php.core.languageModelProviders
 
   org.eclipse.php.core.languageModelProviders
  
Let's say we need to add [http://pecl.php.net/package/newt PHP-newt] extension functions to the Code Assist. First, create the language model provider class:
+
Let's say we need to add [http://pecl.php.net/package/newt PHP-newt] extension functions to the Code Assist. First, create the language model provider class:  
 +
 
 +
<source lang="java">
  
<pre>
 
 
public class NewtLanguageModelProvider  
 
public class NewtLanguageModelProvider  
 
     implements ILanguageModelProvider {
 
     implements ILanguageModelProvider {
Line 365: Line 589:
 
   }
 
   }
 
}
 
}
</pre>
+
</source>
Please not that this interface has been changed according to [https://bugs.eclipse.org/bugs/show_bug.cgi?id=291729 bug 291729]
+
 +
Please not that this interface has been changed since PDT 2.1 according to [https://bugs.eclipse.org/bugs/show_bug.cgi?id=291729 bug 291729]  
 +
 
 +
Register this class in plugin.xml:
 +
 
 +
  &lt;extension point="org.eclipse.php.core.languageModelProviders"&gt;
 +
  &lt;provider class="com.xyz.php.framework.NewtLanguageModelProvider" /&gt;
 +
&lt;/extension&gt;
 +
 
 +
Create resources/newt folder in your plug-in directory, and add this folder to the build.properties, then place PHP stub files under this folder.
 +
 
 +
=== Error reporting / validation  ===
  
Register this class in plugin.xml:
+
Sometimes there's a need to add custom PHP source code validator that reports additional errors or warnings to user. Please learn how to do that from [https://github.com/eclipse/pdt/tree/master/examples/org.eclipse.php.examples.xss this plugin]. This example adds validator that checks PHP code for places possibly vulnerable for XSS attack, and reports warnings to developer.
  
  <extension point="org.eclipse.php.core.languageModelProviders">
 
    <provider class="com.xyz.php.framework.NewtLanguageModelProvider" />
 
  </extension>
 
  
Create resources/newt folder in your plug-in directory, and add this folder to the build.properties, then place PHP stub files under this folder.
+
==== Providing quickfixes for your validation problems ====
  
=== Error reporting ===
+
Showing validation errors is fine, but helping out with a quickfix is even better:
  
Sometimes there's a need to add custom PHP source code validator that reports additional errors or warnings to user. Please learn how to do that from [http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.pdt/examples/org.eclipse.php.examples.xss/?root=Tools_Project this plugin]. This example adds validator that checks PHP code for places possibly vulnerable for XSS attack, and reports warnings to developer.
+
You can use the '''org.eclipse.php.ui.quickFixProcessors''' extension point to do this. See the [https://github.com/pdt-eg/Core-Plugin/tree/master/org.pdtextensions.core.ui PDT Extensions] plugin for
 +
an example implementation.

Latest revision as of 15:43, 2 May 2014

Purpose

There are different purposes for extending PDT. One of them is adding support for specific PHP framework to the IDE features like: Code Assist, Navigation (CTRL + click), Presentation (Outline, PHP Explorer). In this document we'll describe how to achieve these goals using PDT extension points. If you have any questions regarding this document, please ask them on PDT-Dev mailing list.

Extending

Code Assist

Type inference hinting

Suppose your framework uses the following language structure for object instantiation:

$myObject = ClassRegistry::init('MyClass');

In this case PDT type inference engine is unable to detect the type of $myObject variable, so we'll have to add a specific rule that helps him. The following extension point allows to provide additional rules to the PHP type inference engine:

 org.eclipse.php.core.goalEvaluatorFactories

For our example what we need to contribute is:

<extension point="org.eclipse.php.core.goalEvaluatorFactories">
  <factory
      class="com.xyz.php.fmwrk.XYZGoalEvaluatorFactory"
      priority="100">
  </factory>
</extension>

Please note the priority is set to 100 in order to override the default PHP goal evaluator (its priority is 10).

public class XYZGoalEvaluatorFactory implements IGoalEvaluatorFactory {
 
  public GoalEvaluator createEvaluator(IGoal goal) {
    Class<?> goalClass = goal.getClass();
 
    // We're overriding only the expression type goal:
    if (goalClass == ExpressionTypeGoal.class) {
      ASTNode expression = ((ExpressionTypeGoal) goal).getExpression();
 
      // Check the expression AST node type
      if (expression instanceof StaticMethodInvocation) {
        StaticMethodInvocation inv = (StaticMethodInvocation) expression;
        ASTNode reciever = inv.getReceiver();
 
        // Check that the class name is 'CallRegistry':
        if (reciever instanceof SimpleReference
            && "ClassRegistry".equals(((SimpleReference) reciever)
                .getName())) {
 
          // Check that the method name is 'init'
          if ("init".equals(inv.getCallName().getName())) {
 
            // Take the first call argument:
            List arguments = inv.getArgs().getChilds();
            if (arguments.size() == 1) {
              Object first = arguments.get(0);
 
              if (first instanceof Scalar
                 && ((Scalar) first).getScalarType() == Scalar.TYPE_STRING) {
 
                String className = ((Scalar) first).getValue();
 
                // Return the evaluated type through dummy
                // evaluator
                return new DummyGoalEvaluator(goal, className);
              }
            }
          }
        }
      }
    }
 
    // Give the control to the default PHP goal evaluator
    return null;
  }
}
public class DummyGoalEvaluator extends GoalEvaluator {
  private String className;
 
  public DummyGoalEvaluator(IGoal goal, String className) {
    super(goal);
    this.className = className;
  }
 
  public Object produceResult() {
    return new PHPClassType(className);
  }
 
  public IGoal[] init() {
    return null;
  }
 
  public IGoal[] subGoalDone(IGoal subgoal, Object result,
      GoalState state) {
    return null;
  }
}

That's all. In case if 'MyClass' exists Code Assist and Navigation will work out of the box since they are both based on the type inference engine.

Code assist strategies

Some PHP frameworks provide class fields or methods that are not declared explicitly in a code. For instance, Zend Framework view helpers can be accessed from the view class using:

 $this->helperName()

Behind the scenes, view loads the Zend_View_Helper_HelperName class (note the naming convention), creates an object instance of it, and calls its helperName() method.

In order to provide Code Assist for "helperName" after "$this->" we'll need to extend the following extensions:

 org.eclipse.php.core.completionContextResolvers
org.eclipse.php.core.completionStrategyFactories

First of all, we need to define the completion context - a class that verifies that the cursor is positioned after the object call operator and that the object type is a View.

public class XYZCompletionContext extends ClassMemberContext {
 
  public boolean isValid(ISourceModule sourceModule, int offset,
      CompletionRequestor requestor) {
 
    // Call to super to verify that cursor is in the class member call
    // context
    if (super.isValid(sourceModule, offset, requestor)) {
 
      // This context only supports "->" trigger type (not the "::")
      if (getTriggerType() == Trigger.OBJECT) {
 
        IType[] recieverClass = getLhsTypes();
        // recieverClass contains types for the expression from the left
        // side of "->"
        for (IType c : recieverClass) {
          if (!isViewer(c)) {
            return false;
          }
        }
        return true;
      }
    }
 
    return false;
  }
 
  /**
   * Check that the type of the class is Viewer
   */
  private boolean isViewer(IType type) {
    // XXX: add more sophisticated check
    return "Viewer".equalsIgnoreCase(type.getElementName());
  }
}

Register new context in the completion engine:

public class XYZContextResolver extends CompletionContextResolver
    implements ICompletionContextResolver {
 
    public ICompletionContext[] createContexts() {
        return new ICompletionContext[] { new XYZCompletionContext(); };
    }
}

Add the completion strategy that does the actual job - adds "helperName" method to the completion list:

public class XYZCompletionStrategy
    extends ClassMembersStrategy implements ICompletionStrategy {
 
  public XYZCompletionStrategy(ICompletionContext context) {
    super(context);
  }
 
  public void apply(ICompletionReporter reporter) throws Exception {
    XYZCompletionContext context = (XYZCompletionContext) getContext();
    IType type = context.getLhsTypes()[0];
    for (String helperName : getHelperNames()) {
      // Create fake model element for the helper method
      FakeMethod fakeHelperMethod = new FakeMethod((ModelElement) type,
          helperName);
 
      // Report the method to the completion proposals list
      reporter.reportMethod(fakeHelperMethod, "()",
          getReplacementRange(context));
    }
  }
 
  private String[] getHelperNames() {
    // XXX: calculate existing helper names from code
    return new String[] { "helperName" };
  }
}

Create an association between the completion context and completion strategy:

 
 
public class XYZCompletionStrategyFactory implements ICompletionStrategyFactory {
 
  public ICompletionStrategy[] create(ICompletionContext[] contexts) {
    List<ICompletionStrategy> result = new LinkedList<ICompletionStrategy>();
    for (ICompletionContext context : contexts) {
      if (context.getClass() == XYZCompletionContext.class) {
        result.add(new XYZCompletionStrategy(context));
      }
    }
    return (ICompletionStrategy[]) result
        .toArray(new ICompletionStrategy[result.size()]);
  }
}

And, finally, plugin.xml contributions:

<extension point="org.eclipse.php.core.completionStrategyFactories">
    <factory class="com.xyz.php.framework.XYZCompletionStrategyFactory"/>
</extension>
 
<extension point="org.eclipse.php.core.completionContextResolvers">
    <resolver class="com.xyz.php.framework.XYZContextResolver"/>
</extension>


CTRL + click

Contributing to index

When you click while holding CTRL pressed on some PHP element in an editor, or when you ask for Code Assist by pressing CTRL+Space selection engine (or completion engine in case of Code Assist) tries to resolve PHP elements by accessing index. Index contains all the PHP element declarations and references that present in a workspace. The following example indexes non-existing select(), delete() and insert() methods from code snippet #1.

Extension point that we gonna use is:

 org.eclipse.php.core.phpIndexingVisitors

Implement the indexing visitor:

public class XYZIndexingVisitorExtension 
    extends PhpIndexingVisitorExtension {
 
  private ClassDeclaration currentClass;
  private MethodDeclaration currentMethod;
  private Set<Scalar> deferredMethods = new HashSet<Scalar>();
 
  public boolean visit(TypeDeclaration s) throws Exception {
    if (s instanceof ClassDeclaration) {
      currentClass = (ClassDeclaration) s;
    }
    return true;
  }
 
  public boolean endvisit(TypeDeclaration s) throws Exception {
    for (Scalar method : deferredMethods) {
      int start = method.sourceStart();
      int length = method.sourceEnd() - method.sourceStart();
      String name = method.getValue().replaceAll("['\"]", "");
 
      modifyDeclaration(method, new DeclarationInfo(IModelElement.METHOD,
          Modifiers.AccPublic, start, length, start, length, name,
          null, null, currentClass.getName()));
    }
    currentClass = null;
    deferredMethods.clear();
    return true;
  }
 
  public boolean visit(MethodDeclaration s) throws Exception {
    if (currentClass != null && "__call".equals(s.getName())) {
      currentMethod = s;
    }
    return true;
  }
 
  public boolean endvisit(MethodDeclaration s) throws Exception {
    currentMethod = null;
    return true;
  }
 
  public boolean visit(Statement s) throws Exception {
    if (s instanceof IfStatement && currentClass != null
        && currentMethod != null) {
 
      Expression condition = ((IfStatement) s).getCondition();
      if (condition instanceof InfixExpression) {
 
        InfixExpression infixExp = (InfixExpression) condition;
        if (infixExp.getOperatorType() == InfixExpression.OP_IS_EQUAL) {
 
          Expression rightExp = infixExp.getRight();
          if (rightExp instanceof Scalar) {
 
            Scalar scalar = (Scalar) rightExp;
            if (scalar.getScalarType() == Scalar.TYPE_STRING) {
              deferredMethods.add(scalar);
            }
          }
        }
      }
    }
    return true;
  }
}

Note: this visitor is very similar to the structured model contribution visitor.

Finally, register this class in plugin.xml:

 <extension point="org.eclipse.php.core.phpIndexingVisitors">
  <visitor class="com.xyz.php.framework.XYZIndexingVisitorExtension" />
</extension>

Outline and PHP Explorer

Adding additional nodes to the PHP explorer can be achieved in 2 different ways.

Contributing to the structured model

Sometimes you need to present some elements that don't really exist. For example, you may want to present fields and methods that are processed using __get() or __call() magic methods:

Code snippet #1

/**
 * Please don't judge me for this code, it's for the sake of example only
 * (even though I've seen PHP code like this in real PHP applications :-])
 */
class MySQL_DB {
 
   public function __call($name, $arguments) {
      if ($name == "select") {
         mysql_query($this->db, "SELECT * FROM ".$arguments[0]);
         // ...
      }
      else if ($name == "insert") {
         // ...
      }
      else if ($name == "delete") {
         // ...
      }
   }
}

Let's say we wish to see select(), insert() and delete() methods in the Outline and PHP Explorer under MySQL_DB class. We'll need to extend the following extension point:

 org.eclipse.php.core.phpSourceElementRequestors

plugin.xml contribution:

 <extension point="org.eclipse.php.core.phpSourceElementRequestors">
    <requestor
          class="com.xyz.php.framework.XYZSourceElementRequestor">
    </requestor>
 </extension>

Source element requester extension implementation:

 
 
public class XYZSourceElementRequestor extends
    PHPSourceElementRequestorExtension {
 
  private ClassDeclaration currentClass;
  private MethodDeclaration currentMethod;
  private Set<Scalar> deferredMethods = new HashSet<Scalar>();
 
  public boolean visit(TypeDeclaration s) throws Exception {
    if (s instanceof ClassDeclaration) {
      currentClass = (ClassDeclaration) s;
    }
    return true;
  }
 
  public boolean endvisit(TypeDeclaration s) throws Exception {
    currentClass = null;
 
    for (Scalar method : deferredMethods) {
      ISourceElementRequestor.MethodInfo methodInfo =
        new ISourceElementRequestor.MethodInfo();
 
      methodInfo.name = method.getValue().replaceAll("['\"]", "");
      methodInfo.modifiers = Modifiers.AccPublic;
      methodInfo.nameSourceStart = method.sourceStart();
      methodInfo.nameSourceEnd = method.sourceEnd();
      methodInfo.declarationStart = method.sourceStart();
      fRequestor.enterMethod(methodInfo);
      fRequestor.exitMethod(method.sourceEnd());
    }
    deferredMethods.clear();
    return true;
  }
 
  public boolean visit(MethodDeclaration s) throws Exception {
    if (currentClass != null && "__call".equals(s.getName())) {
      currentMethod = s;
    }
    return true;
  }
 
  public boolean endvisit(MethodDeclaration s) throws Exception {
    currentMethod = null;
    return true;
  }
 
  public boolean visit(Statement s) throws Exception {
    if (s instanceof IfStatement && currentClass != null
        && currentMethod != null) {
 
      Expression condition = ((IfStatement) s).getCondition();
      if (condition instanceof InfixExpression) {
 
        InfixExpression infixExp = (InfixExpression) condition;
        if (infixExp.getOperatorType() == InfixExpression.OP_IS_EQUAL) {
 
          Expression rightExp = infixExp.getRight();
          if (rightExp instanceof Scalar) {
 
            Scalar scalar = (Scalar) rightExp;
            if (scalar.getScalarType() == Scalar.TYPE_STRING) {
              deferredMethods.add(scalar);
            }
          }
        }
      }
    }
    return true;
  }
}

Voila! Methods are added into PHP Explorer and Outline views, and even clicking on them changes focus in the editor:

Requestor1.png


Contributing a PHPTreeContentProvider

Since PDT 3.1.1. additional nodes can be added to the PHP explorer using the org.eclipse.php.ui.phpTreeContentProviders extension point. Let's say you want

to contribute a custom buildpath container using the phpTreeContentProviders extension point:

<extension point="org.eclipse.php.ui.phpTreeContentProviders">
    <provider labelProvider="com.dubture.composer.core.ui.explorer.PackageTreeLabelProvider" contentProvider="com.dubture.composer.core.ui.explorer.PackageTreeContentProvider"/>
</extension>

All you need to do is to create the labelProvider and contentProvider elements, by implementing an org.eclipse.jface.viewers.ILabelProvider and an org.eclipse.jface.viewers.ITreeContentProvider:


public class PackageTreeLabelProvider extends LabelProvider
{
 
    @Override
    public String getText(Object element)
    {
        if (element instanceof PackagePath) {
            PackagePath path = (PackagePath) element;
            return path.getPackageName();
        }
 
        return null;
    }
 
    @Override
    public Image getImage(Object element)
    {
        if (element instanceof PackagePath) {
            return PHPPluginImages
                    .get(PHPPluginImages.IMG_OBJS_LIBRARY);
        }
 
        return null;
    }
}


public class PackageTreeContentProvider extends ScriptExplorerContentProvider
{
    public PackageTreeContentProvider()
    {
        super(true);
    }
 
 
    @Override
    public Object[] getChildren(Object parentElement)
    {
        if (parentElement instanceof PackagePath) {
 
            PackagePath pPath = (PackagePath) parentElement;
            IScriptProject scriptProject = pPath.getProject();
            IBuildpathEntry entry = pPath.getEntry();
 
            try {
 
                IProjectFragment[] allProjectFragments;
                allProjectFragments = scriptProject.getAllProjectFragments();
                for (IProjectFragment fragment : allProjectFragments) {
                    if (fragment instanceof ExternalProjectFragment) {
                        ExternalProjectFragment external = (ExternalProjectFragment) fragment;
                        if (external.getBuildpathEntry().equals(entry)) {
                            return super.getChildren(external);
                        }
                    }
                }
            } catch (ModelException e) {
                Logger.logException(e);
            }
        } else if (parentElement instanceof ComposerBuildpathContainer) {
            ComposerBuildpathContainer container = (ComposerBuildpathContainer) parentElement;
 
            IAdaptable[] children = container.getChildren();
 
            if (children == null || children.length == 0) {
                return NO_CHILDREN;
            }
 
            return children;
        } else if (parentElement instanceof IScriptProject) {
            try {
                IProject project = ((IScriptProject)parentElement).getProject();
                if (project.hasNature(ComposerNature.NATURE_ID)) {
                    return new Object[]{new ComposerBuildpathContainer((IScriptProject) parentElement)};
                }
            } catch (Exception e) {
                Logger.logException(e);
            }
        }
        return null;
    }
}


PhpTreeContentProvider.png

The example has been taken from the Composer Eclipse Plugin


Language library contributions

PHP language library contains builtin classes, functions and constants - you can see it under PHP project as a "PHP Language Library" node (see image above). This model is used in Code Assist and PHP Function View. By default, it contains only predefined set of PHP extensions. There's a way to contribute more PHP language fragments to this library. The key extension point is:

 org.eclipse.php.core.languageModelProviders

Let's say we need to add PHP-newt extension functions to the Code Assist. First, create the language model provider class:

public class NewtLanguageModelProvider 
    implements ILanguageModelProvider {
 
  public IPath getPath(IScriptProject project) {
    return new Path("resources/newt");
  }
 
  public Plugin getPlugin() {
    return MyPlugin.getDefault();
  }
}

Please not that this interface has been changed since PDT 2.1 according to bug 291729

Register this class in plugin.xml:

 <extension point="org.eclipse.php.core.languageModelProviders">
  <provider class="com.xyz.php.framework.NewtLanguageModelProvider" />
</extension>

Create resources/newt folder in your plug-in directory, and add this folder to the build.properties, then place PHP stub files under this folder.

Error reporting / validation

Sometimes there's a need to add custom PHP source code validator that reports additional errors or warnings to user. Please learn how to do that from this plugin. This example adds validator that checks PHP code for places possibly vulnerable for XSS attack, and reports warnings to developer.


Providing quickfixes for your validation problems

Showing validation errors is fine, but helping out with a quickfix is even better:

You can use the org.eclipse.php.ui.quickFixProcessors extension point to do this. See the PDT Extensions plugin for an example implementation.

Copyright © Eclipse Foundation, Inc. All Rights Reserved.