I wrote a PHPStorm plugin
Tags: php, java, phpstorm
Published: 2019-07-26 12:36:50

There is a high chance you were once so upset at your IDE for not having a feature you need, that you thought of writing a plugin for it. Most of us end up never doing it and finishing the job without the said plugin. The same happened to me.
I had an idea for a PHPStorm plugin for months but never *finding* time to write it. Until I had to do the same boring routine one too many times.
So I wrote a PHPStorm plugin.

## The problem
When you have a class that has getters and setters in some (rare) cases you will need to write something like this: 
```php
$car = new Car();
$car->setMake('Audi');
$car->setModel('A6');
```
With some auto-complete features that InteliJ platform provides I can finish this pretty quickly. But what if a class has a dozen of getters or setters you need to call?
![Idea](https://media.giphy.com/media/LrGHJGtTbT7PO/giphy.gif)

## The idea
The idea was simple a developer places the caret on the `$car` variable and by pressing a shortcut, generate all the getters or setters (depending on the shortcut) for the given object.

## Writing the plugin
I was really nervous cause I haven't touched *java* since my school days but I made a decision to just do it.
![Just do it!](https://media.giphy.com/media/GcSqyYa2aF8dy/giphy.gif)

### Prerequisites
- PHPStorm - contains `php-openapi.jar` and `php.jar`
- InteliJ IDEA Community Edition

### Setup 
I've followed a setup written by the people at Jetbrains, [Setting-up environment for PhpStorm plugin development](https://confluence.jetbrains.com/display/PhpStorm/Setting-up+environment+for+PhpStorm+plugin+development). If you are going to follow this setup make sure that when you are configuring the SDK you add `php-openapi.jar` and `php.jar` like so ![](https://i.imgur.com/EXwjbcg.png)
don't make the same mistake as me by not seeing the warning to not set those in `Libraries`.

### Debugger setup
By default InteliJ will setup and run another instance of InteliJ when you click on the debug button. It works really good, but I was developing a plugin for PHPStorm, not for InteliJ. I've searched far and wide for a debugger setup if you are developing a PHPStorm plugin and I couldn't find any. So I had to discover how to do it on my own. 
You can go to the edit debug configurations like so ![Debug configuration](https://i.imgur.com/qAmhihc.png) in the JRE field navigate to the path of your PHPStorm and voila, now it works. Easier than I thought it would be.

### Coding
I must say I found it hard to do anything, documentation is pretty scarce and I forgot a lot of java rules and syntax.
Unfortunately I couldn't find a way to tap in the power of PHPStorm and to ask it; what it knows about a certain variable. The only choice I had left is to use [PSI](https://www.jetbrains.org/intellij/sdk/docs/basics/architectural_overview/psi_files.html) and try to figure out where is the declaration of the variable and what type it is. After a few hours of trial and error I have succeeded in that.
```java
    static void generateMethods(AnActionEvent event, String typeOfMethod) {
        final Project project = event.getData(PlatformDataKeys.PROJECT);
        final Editor editor = event.getData(PlatformDataKeys.EDITOR);
        if (editor != null) {
            final CaretModel caret = editor.getCaretModel();
            PsiFile file = event.getData(LangDataKeys.PSI_FILE);
            if (file != null) {
                PsiElement selectedElement = file.findElementAt(caret.getOffset());
                if (selectedElement != null && selectedElement.getParent().getReference() != null) {
                    PsiElement variableDeclaration = selectedElement.getParent().getReference().resolve();                        
                    if (variableDeclaration != null) {
                        PsiElement assignmentExpression = variableDeclaration.getContext();
                        if (assignmentExpression != null) {
                            final Collection<PsiElement> selectedElementClass = new SmartList<>();
                            assignmentExpression.accept(new PsiRecursiveElementVisitor() {
                                @Override
                                public void visitElement(PsiElement element) {
                                    if (element instanceof ClassReferenceImpl) {
                                        if (element.getReference() != null) {
                                            selectedElementClass.add(element.getReference().resolve());
                                        }
                                    }
                                    super.visitElement(element);
                                }
                            });
                        }
                    }
                }
            }
        }
    }
```
Believe me, I'm not proud of this code and I am looking forward to rewriting it soon.

Now the only thing left was to get the getters and setters of the instantiated class and add the calls to the editor. I managed to get the methods of a class without any issues. I expected the writing to the editor to be the easiest task of them all and boy was I wrong. Again I had to spend a few hours to figure out how to do it and this is the best I could come up with
```java
Document document = editor.getDocument();
boolean firstMethodGeneration = true;
for(PsiElement initClass:selectedElementClass) {
    PhpClassImpl phpClass=(PhpClassImpl)initClass;
    for(Method method:phpClass.getMethods()) {
        String methodName=method.getName();

        // If method name has "set" on position 0 add it to editor
        if(methodName.indexOf(typeOfMethod)==0) {
            boolean FirstSetter=firstMethodGeneration;
            WriteCommandAction.runWriteCommandAction(project,()->{
                if(FirstSetter){
                    caret.moveToOffset(caret.getVisualLineEnd());
                    document.insertString(caret.getOffset(),"\n");
                }
                caret.moveToOffset(caret.getVisualLineEnd());
                document.insertString(caret.getOffset(),selectedElement.getText()
                +"->"+methodName+"();\n");
            });
        }
        firstMethodGeneration=false;
    }
}
```

## Final result

In the end I got a very limited plugin that can generate setters or getters only for the variables instantiated in the same file. It can't determine variable type from a function return or more advanced stuff like that. Having said that I'm still very happy how it turned out and I'm planning to make it more powerful in the future and add more features to the plugin.
![](https://i.imgur.com/67fRtng.gif)
If you want to see more of the plugin feel free to visit the GitHub repository. {% github KristijanKanalas/PHPCodeGeneratorPlus %}