Sublime Text for modern PHP development

Sublime Text for modern PHP development

I decided to give a chance to the editor I spent many years before I switched to Neovim: Sublime Text. I found that the plugin ecosystem is not as big as VS Code, but it has enough packages to make modern PHP work very well. These are the features I was able to configure:

  • Code diagnostics
  • Coding style
  • Semantic errors
  • Static analysis errors
  • Debugging
  • Test runners
  • Refactoring tools
  • Rename variables and functions
  • Extract interfaces
  • Move classes
  • Code navigation
  • Find references
  • Go to definition
  • Find implementations
  • etc

PHP

Using the LSP package, I configured phpactor to use it in my projects. For this, I downloaded the latest phar from GitHub, moved it to /usr/local/bin/phpactor and added this to the LSP settings in Sublime Text (using the command palette, select Preferences: LSP Settings):

// Settings in here override those in "LSP/LSP.sublime-settings",
{
  "diagnostics_delay_ms": 800,
  "show_diagnostics_panel_on_save": 0,
  "document_highlight_style": "background",
  "show_diagnostics_in_view_status": false,
  "show_diagnostics_count_in_view_status": true,
  "diagnostics_gutter_marker": "dot",
  "log_debug": false,
  "show_code_actions": "bulb",
  "show_inlay_hints": true,
  "clients": {
    "phpactor": {
      "command": ["/usr/local/bin/phpactor", "language-server", "-v"],
      "selector": "source.php | embedding.php",
    },
    ...
  },

}

Then, in an existing project, I enabled it by default by selecting in the command palette LSP: Enable Language Server in Project, and then phpactor.

Now, phpactor can use a file in the root of the project named .phpactor.json for configurations. I normally work on Drupal projects, so I followed the instructions in their documentation and additionally added support for PHP Stan, PHP Code Sniffer, PHP Code Beautifier. Also, phpactor is capable of detecting Symfony services, which is very useful when developing custom modules, and with a contrib module, it is possible to use this plugin with Drupal!

I'm using phpcs and phpstan locally, i.e. not running in a container because it is faster and easier to debug and update. So, after installing PHP locally, I installed the tools globally so they can be accessed in ~/.config/composer/vendor/bin

# Install phpcs, phpcbf and style rules
composer global req drupal/coder
# Install PHP Stan
composer global req phpstan/phpstan

For all Drupal projects I work on, I install the container dumper and the autoloader with the Drupal classes to help phpactor read the Drupal project:

# Genereates autoloader entries for Drupal classes
composer req --dev fenetikm/autoload-drupal
composer dump-autoload
# Install the Drupal container dumper
composer req --dev en drupal/container_dumper
drush en container_dumper
# The container is dumped by default to .cache/drupal_container.xml on cache rebuild.
drush cr 

.phpactor.json

{
    "$schema": "/phpactor.schema.json",
    "language_server_phpstan.enabled": true,
    "language_server_phpstan.level": "4",
    "language_server_phpstan.bin": "/home/<USER>/.config/composer/vendor/bin/phpstan",
    "php_code_sniffer.enabled": true,
    "php_code_sniffer.bin": "/home/<USER>/.config/composer/vendor/bin/phpcs",
    "php_code_sniffer.cwd": "%project_root%",
    "php_code_sniffer.args": [
        "--standard=Drupal,DrupalPractice"
    ],

    "symfony.enabled": true,
    "symfony.xml_path": "%project_root%/.cache/drupal_container.xml",

    "behat.enabled": false,
    "code_transform.indentation": "  ",
    "indexer.include_patterns": [
        "/**/*.php",
        "/**/*.inc",
        "/**/*.module"
    ],
    "indexer.supported_extensions": [
        "php",
        "inc",
        "module"
    ],
    "prophecy.enabled": false
}

Testing

Using the package PHPUnitKit I can run PHP Unit files in docker, something I was unable to do in Neovim with neotest.

It is possible to override settings per project, so for projects that have PHP unit tests in DDEV I normally have these settings:

{
  ...
  "settings": {
    ...
        "phpunit.docker": true,
      "phpunit.docker_command": [ "docker", "exec", "-t", "ddev-<YOUR PROJECT>-web",],
      "phpunit.docker_paths": {
        // DDEV mount dir
        "$folder": "/var/www/html",
      },
      "phpunit.debug": false,
      "phpunit.executable": "vendor/bin/phpunit",
      "phpunit.options": {
        "printer": "\\Drupal\\Tests\\Listeners\\HtmlOutputPrinter",
        "bootstrap": "/var/www/html/docroot/core/tests/bootstrap.php",
        "testdox": "",
      },

Configured this way, phpactor can provide style diagnostics, semantic errors, static analysis warnings and refactoring tools. Additionally, I get service container autocomplete with types.

image

Debugging

Using the Debugger package, it is possible to use the same protocol VS Code uses, and so far, I haven't had any problem with it. Instead of having the debugger configurations in a .vscode/launch.json file, they live in the project settings. I'm using DDEV, so I use this:

{
  ...

  "debugger_configurations":
    [
        {
      "name": "Listen for XDebug",
      "type": "php",
      "request": "launch",
      "port": 9003,
      "log": false,
      "pathMappings": {
        "/var/www/html": "${folder}",
      },
    },
    ],
}

VIM

Coming from Neovim, I wanted to use the same shortcuts, so I installed Neovintageous. I was surprised by how well this program can do things and how easily it can be set up, and by how well it is documented. I was able to copy my most used key-bindings from LunarVim to Sublime Text, and keep almost the same workflow.

Below is my .neovintageousrc, the .vimrc equivalent.

" Type :help nv for help.

let mapleader = <Space>

" Go to definition
nnoremap gd :LspSymbolDefinition side_by_side=false force_group=true fallback=true<CR>
" Find Implementation
nnoremap gI :LspSymbolImplementation side_by_side=false force_group=true fallback=true<CR>
" Find Type Definition
nnoremap gD :LspSymbolTypeDefinition side_by_side=false force_group=true fallback=true<CR>
" Find References
nnoremap gr :LspSymbolReferences side_by_side=false force_group=true fallback=false group=-1 include_declaration=false<CR>

" Close all terminus panes and open the workspace selector.
nnoremap <leader>o :TerminusCloseAll<bar>:PromptSelectWorkspace<CR>

nnoremap <leader>gm :ShowOverlay overlay=goto text=@<CR>
" nnoremap <leader>gm :LspDocumentSymbols<CR>
nnoremap <leader>gs :GsShowStatus<CR>
nnoremap <leader>gp :ToggleInlineDiff<CR>
noremap <leader>gr :RevertModification<CR>
noremap <leader>gj :NextModification<CR>
noremap <leader>gk :PrevModification<CR>

" Yank and paste using system clipboard.
noremap <leader>y "+y
noremap <leader>Y "+Y
noremap <leader>p "+p
noremap <leader>P "+P

" Jump to next/previous.
noremap <M--> :JumpBack<CR>

" Terminal
" noremap <leader>tt :ToggleTerminusPanel<CR>
noremap <leader>tt :TerminusOpen<CR>
" Debugger
" nnoremap <leader>ld :ShowOverlay overlay=command_palette text=debugger<CR>
nnoremap <leader>dt :Debugger action=toggle_breakpoint<CR>
nnoremap <leader>dB :Debugger action=clear_breakpoints<CR>
nnoremap <leader>do :Debugger action=step_over<CR>
nnoremap <leader>di :Debugger action=step_in<CR>
nnoremap <leader>dc :Debugger action=continue<CR>
nnoremap <leader>dq :Debugger action=quit<CR>
nnoremap <leader>ds :Debugger action=start<CR>
nnoremap <leader>dU :Debugger action=open<CR>

" LSP
nnoremap <leader>lS :LspWorkspaceSymbols<CR>
nnoremap <leader>ls :LspDocumentSymbols<CR>
nnoremap <leader>lj :LspNextDiagnostic<CR>
nnoremap <leader>lk :LspPrevDiagnostic<CR>
nnoremap <leader>lH :LspToggleInlayHints<CR>
nnoremap FileType json,js,rb,go,rs <leader>lf :LspFormatDocument<CR>
nnoremap FileType php <leader>lf :Phpcbf<CR>
nnoremap <leader>lh :LspHover<CR>
nnoremap <leader>la :LspCodeActions<CR>
nnoremap <leader>lA :ShowOverlay overlay=command_palette text=phpactor<CR>
vnoremap <leader>lr :LspCodeActions only_kinds=refactor<CR>
nnoremap <leader>r :LspSymbolRename<CR>
nnoremap <leader>ld :SublimeLinterPanelToggle<CR>
" nnoremap <leader>ld :LspGotoDiagnostic uri=%<CR>
nnoremap <leader>c :tabc<CR>
nnoremap <Tab> :NextView<CR>
nnoremap <S-Tab> :PrevView<CR>

" nnoremap <C-j> :SelectLines forward=false<CR>
" nnoremap <C-k> :SelectLines forward=true<CR>

nnoremap <C-h> :TravelToPane direction=left<CR>
nnoremap <C-l> :TravelToPane direction=right<CR>
nnoremap <C-j> :TravelToPane direction=down<CR>
nnoremap <C-k> :TravelToPane direction=up<CR>

nnoremap gc :ToggleComment<CR>

nnoremap <leader>sr :SublimeLinterLineReport<CR>
nnoremap <leader>st :ShowPanel panel=find_in_files<CR>

" Sidebar
nnoremap <leader>e :Neovintageous action=toggle_side_bar<CR>

" Bookmarks
nnoremap <leader>a :ToggleBookmark<CR>
nnoremap <C-e> :ViewBookmarks<CR>

" GoTo file
nnoremap <leader>, :ShowOverlay overlay=goto show_files=true<CR>

" Use nv CTRL keys
nnoremap <C-P> :ShowOverlay overlay=command_palette<CR>

" Select lines
nnoremap <C-down> :SelectLines forward=true<CR>
nnoremap <C-up> :SelectLines forward=false<CR>

" Buffers
nnoremap <leader>bf :TabFilter<CR>
nnoremap <leader>sr :OpenRecentlyClosedFile<CR>
nnoremap <leader>bc :CloseOtherTabs<CR>
nnoremap <leader>z :ZoomPane fraction=0.9<CR>
nnoremap <leader>Z :UnzoomPane<CR>

" GitOpen
" nnoremap <leader>go :GitOpen<CR>
" nnoremap <leader>gc :GitOpen commit=true<CR>
" nnoremap <leader>gi :GitOpen issue=true<CR>

" text manipulation
noremap <A-j> :SwapLineDown<CR>
noremap <A-k> :SwapLineUp<CR>
vnoremap <A-j> :SwapLineDown<CR>
vnoremap <A-k> :SwapLineUp<CR>

" Related files
nnoremap <leader>R :RelatedFiles<CR>

" Search Ignore case
set ic

Packages

Many of the VIM maps use commands provided by third-party packages. Below some of my most used ones, but I also recommend a visit to the NeoVintageuos developer blog.


You'll only receive email when they publish something new.

More from Arturo Linares
All posts