Эпизод из сериала "Silicon Valley"

Intelephence

Проекты PHP в данный момент у меня заморожены или переданы коллегам, поэтому толком попрактиковаться в Vim пока особенно не получается. Тем не менее есть что обозначить и пусть данная заметка будет эдаким введением в проблематику. Если на первый взгляд кажется что её нет и вовсе - проблематики, то на второй взгляд становится понятно, что не всё так однозначно и просто, примерно так же как и с базами данных.

Если загуглить "vim php lsp" то с высокой вероятностью вы попадете на проект Intelephence и плагины его использующие. Который платный. Вообще удивительно, но вокруг в общем-то опенсорсного PHP многие компании, с упорством достойным лучшего применения, пытаются создать платную инфраструктуру. Является ли при этом PHP Group каким-то бенефициаром такого положения вещей не совсем очевидно. Тем не менее воз и ныне там - кто только не разрабатывает инструментарий, только не сами авторы. И так сложилось, что лидерами в производстве инструментов являются наиболее жадные коммерческие компании как JetBrains и разработчики указанного Intelephence.

Если вы тот кто считает, что это правильно и нужно поддерживать производителя рублем, если вы разработчик промышленных решений - нет проблем - к вашим услугам PhpStorm и Zend Studio. И в них правда нет ничего плохого. Более того, они действительно поддерживают всё и вся связанное с PHP причем самой последней версии. Но, знаете, городить огород с покупкой лицензий для эпизодической поддержки легаси кода, которого нынче значительно больше какого-то передового, мне лично не кажется целесообразным. Только вот с бесплатными инструментами всё несколько запутано.

PhpActor

Бесплатных решений на самом деле предостаточно. Это и PDT от Eclipse, и Apache Netbeans, входящие в состав универсальных IDE. Есть и специализированные инструменты типа CodeLobster. Беда заключается в том, что они всё-таки не так свежи как платные аналоги и при этом такие же тяжелые. Таким образом для эпизодического редактирования легаси PHP проектов чаще всего используется какой-нибудь текстовый редактор с худо-бедно подключенной подсветкой, каким-никаким дополнением и даже с некоторыми разбором тегов. А вот что бы превратить текстовый редактор в современный инструмент программиста PHP опять предлагается обратиться к коммерческим проектам.

В общем через некоторую боль было найдено наиболее адекватное, как мне кажется, решение - PhpActor. Надо сразу оговориться, что это тоже не единственное решение - на слуху еще vimeo/psalm и felixfbecker/php-language-server. Хочется отделаться универсальным "дело вкуса", но это не совсем так. Второй я не попробовал, но он ставится в Vim как отельный плагин и, судя по репозиторию, давно не обновлялся. А первый кажется менее функциональным. Но тут я могу ошибаться. Как и отмечалось, поработать плотно в PHP проекте пока не получается. Во всяком случае список функционала для PhpActor заявлен прямо на главной странице и он в моем случае оказался полностью удовлетворяющим моим представлениям о полноценном LSP.

Примечательно, что на него я набрел совершенно случайно, хотя проект уже достаточно поживший и, как мне показалось, наиболее перспективный даже по сравнению с Intelephence. Нет, ну действительно, странно платить за рефакторинг, который в случае с PHP просто важнейший инструмент, особенно когда он есть бесплатный.

coc-phpactor

Однако, хватит ворчать. Приступим, пожалуй, к более осязаемым вещам. Собственно, раз у нас уже есть coc.nvim , просто берем и ставим расширение.

Перед установкой расширения нужно установить сам LSP. Можно установить его глобально на машину, так что бы им могли пользоваться все пользователи, но если это ваша личная рабочая станция, то проще всего установить соответствующий плагин, который подтянет за собой и LSP. Для этого добавим в ~/.vim/plugins.vim:

Plug 'phpactor/phpactor', {'for': 'php', 'tag': '*', 'do': 'composer install --no-dev -o'}

Привычно из Vim обновим конфигурацию и установим плагин.

:source ~/.vimrc
:PlugInstall

Что бы зацепить LSP в качестве расширения к coc.nvim нужно установить расширение и подключить LSP вручную в ~/.vim/coc-settings.json:

:CocInstall coc-phpactor
:CocConfig

Можно прописать бинарный файл phpactor в пути или положить ссылку куда-нибудь в /usr/local/bin/. Я просто прописал путь до плагина.

{
	...
	"phpactor.enable": true,
    "phpactor.path": "~/.vim/plugged/phpactor/bin/phpactor",
	...
}

Умеет из коробки PhpActor многое, но опять же нужно руками объяснить Vim как это многое вызывать. Для этого нужно добавить ряд комбинаций клавиш либо в основной конфигурации, либо в ~/.vim/ftplugin/php.vim как предложено здесь.

augroup PhpactorMappings
	au!
	au FileType php nmap <buffer> <Leader>li :PhpactorImportClass<CR>
	au FileType php nmap <buffer> <Leader>lce :PhpactorClassExpand<CR>
	au FileType php nmap <buffer> <Leader>lim :PhpactorImportMissingClasses<CR>
	au FileType php nmap <buffer> <Leader>lm :PhpactorContextMenu<CR>
	au FileType php nmap <buffer> <Leader>ln :PhpactorNavigate<CR>
	au FileType php,cucumber nmap <buffer> <Leader>o
		\ :PhpactorGotoDefinition edit<CR>
	au FileType php nmap <buffer> <Leader>K :PhpactorHover<CR>
	au FileType php nmap <buffer> <Leader>lt :PhpactorTransform<CR>
	au FileType php nmap <buffer> <Leader>lcn :PhpactorClassNew<CR>
	au FileType php nmap <buffer> <Leader>lci :PhpactorClassInflect<CR>
	au FileType php nmap <buffer> <Leader>lr :PhpactorFindReferences<CR>
	au FileType php nmap <buffer> <Leader>mf :PhpactorMoveFile<CR>
	au FileType php nmap <buffer> <Leader>cf :PhpactorCopyFile<CR>
	au FileType php nmap <buffer> <silent> <Leader>lee
		\ :PhpactorExtractExpression<CR>
	au FileType php vmap <buffer> <silent> <Leader>lee
		\ :<C-u>PhpactorExtractExpression<CR>
	au FileType php vmap <buffer> <silent> <Leader>lem
		\ :<C-u>PhpactorExtractMethod<CR>
augroup END

Так привязки будут подключаться только вместе с LSP. Соответственно в php.vim можно вставить назначения без автокоманд. Я сразу поменял предложенные в документации сочетания, они сильно пересекаются с существующими и, видимо, были задуманы под конфигурацию где PhpActor единственный плагин. У меня команды относящиеся к LSP все начинаются с <leader>l. Не знаю, мне так удобнее и понятнее.

Я пока не нашел способа назначить сочетание конкретно на переименование объектов, наверняка такой способ есть, но пока приходится вызывать контекстное меню <leader>lm и выбирать r. На данный момент этого достаточно. А чего недостаточно так это автоформатирования. И тут тоже наблюдается некоторый легкий сумбур и мешанина.

Code Sniffer и Code Style Fixer

Как коллеги, думаю, уже привыкли, всё связанное с PHP обязательно начинается "PHP". Для инструментария многих языков префиксы типа "J" или "Py" явление распространенное, но с PHP происходит что-то особенное. Теряюсь в догадках, о том что именно не так с фантазией у разработчиков PHP, но вот так. Более того фантазии не хватает и при именовании самих продуктов. Так получилось и с "PHP CS"

-- "Как мы назовем свою программу?"

-- "PHP CS"

-- "Такая уже есть"

-- "Ну тогда PHP CS Fixer!"

Обе указанные программы являются диагностическими. Обе умеют некоторым образом "чинить" стилевые и конвенциональные ошибки. При этом Fixer умеет форматировать, но не видит некоторые нарушения конвенций, которые замечает Sniffer. Sniffer - идет больше как линтер, анализируя код более глубоко. Fixer - подгоняет код под стиль. Поэтому я их поставил оба. Вместе они дополняют друг друга, но кое-где бывают конфликты. Детально познакомиться с конкретными разночтениями конвенций типа PSR-12 нужно в процессе использования. И выбирать подход, который конкретно вы или ваша команда считает верным. Благо, что оба инструмента глубоко конфигурируемы и даже дополняемы.

Слишком подробно на них я останавливаться не буду - детальная настройка отдельная история и сильно зависит от ваших требований к себе или к команде и наоборот. Скажу лишь то что настройки по умолчанию PHP Code Sniffer не подойдут к легаси проектам из коробки и завалят вас предупреждениями в количестве больше чем всего строк в исходнике. Я остановился на дефолтах проверки только PSR-12. Если вы используете фреймворки, то имеет смысл включить и правила конкретного фреймворка на проекте.

Ставится Sniffer опять же отдельно.

$ sudo pear install PHP_CodeSniffer

Настройки предлагается делать к каждому проекту индивидуально, что имеет определенный смысл, если вы не разработчик под какой-то один фреймворк или конвенцию. В документации к проекту несложно разобраться самостоятельно, она хорошая, но я посчитал что не хватает примера, с которого можно начать не забивая голову. К среднему проекту с небольшими изменениями подойти должно. Просто создаете phpcs.xml в корне проекта.

<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer" xsi:noNamespaceSchemaLocation="phpcs.xsd">
    <description>The coding standard for PHP_CodeSniffer itself.</description>

    <file>public</file>
    <file>src</file>
    <file>tests</file>
    
    <exclude-pattern>*/vendor/*</exclude-pattern>
    <exclude-pattern>*/tools/*</exclude-pattern>
    <exclude-pattern>*/docs/*</exclude-pattern>
    <exclude-pattern>*/.phpdoc/*</exclude-pattern>

    <arg name="basepath" value="."/>
    <arg name="colors"/>
    <arg name="parallel" value="75"/>
    <arg name="extensions" value="php,inc"/>

    <!-- Don't hide tokenizer exceptions -->
    <rule ref="Internal.Tokenizer.Exception">
        <type>error</type>
    </rule>

    <!-- Include the whole PSR12 standard -->
    <rule ref="PSR12"/>

    <!-- Exclude some rules -->
    
    <rule ref="PSR2.Classes.ClassDeclaration.OpenBraceNewLine">
        <exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine"/>
    </rule>

</ruleset>

И можете запустить линтер отдельно phpcs из корня проекта, но удобнее делать это с помощью плагина из Vim. Добавим плагин в ~/.vim/plugins.vim.

" PHP Code Sniffer
Plug 'bpearson/vim-phpcs'

А так же нужно будет установить стандарт для проверки вручную например в ~/.vim/ftplugin/php.vim.

let Vimphpcs_Standard='PSR12'

Теперь после перезапуска и установки плагина вам будет доступна команда CodeSniff которая будет преобразовывать вывод в QuickFix буфер. Должны быть и более удобные плагины, но это то что удалось найти мне. И этого пока достаточно. Потому что основной работой по диагностике проблем в коде занимается всё-таки LSP а для соблюдения стиля лучше использовать второй линтер-форматировщик Style Fixer.

Fixer и вовсе предлагается устанавливать через composer причем в специальной директории tools/php-cs-fixer в составе проекта. Не буду сейчас спорить удобно ли это в каждом случае, но смысл в этом есть. Для чего в phpcs.xml было добавлено исключение <exclude-pattern>*/tools/*</exclude-pattern>. Идем в проект и выполняем.

$ mkdir -p tools/php-cs-fixer
$ composer require --working-dir=tools/php-cs-fixer friendsofphp/php-cs-fixer

Нужно также сразу настроить инструмент создав специальный скрипт .php-cs-fixer.dist.php в корне проекта. Минимально он выглядит примерно так:

<?php

$finder = PhpCsFixer\Finder::create()
    ->exclude('somedir')
    ->notPath('src/Symfony/Component/Translation/Tests/fixtures/resources.php')
    ->in(__DIR__)
;

$config = new PhpCsFixer\Config();
return $config->setRules([
        '@PSR12' => true,
    ])
    ->setFinder($finder)
;

Для справки в репозиторий я положил более развернутую конфигурацию с набором других опций.

Далее для установки всего этого добра в Vim делаем:

:CocInstall coc-php-cs-fixer

И добавляем несколько строчек в ~/.vim/coc-settings.json или в локальный .vim/coc-settings.json:

{
...
    "phpactor.config": {
        "language_server_php_cs_fixer.enabled" : true,
        "language_server_php_cs_fixer.bin": "%project_root%\/tools/php-cs-fixer\/vendor\/bin\/php-cs-fixer"
    },
	"[php]" : {
		"coc.preferences.formatOnSave": true
	},
...
}

Последнее иногда может наделать лишнего, особенно пока не настроено так как надо. Поэтому для сохранения без форматирования нужно предупреждать об этом Vim :noa w. Или можете вообще не использовать автоформатирование и вызывать только когда необходимо с помощью команд coc.nvim. Ну или воспользоваться formatexpr из предыдущей статьи.

Что еще?

Для того что бы называть полученную конфигурацию IDE не хватает как минимум интеграции с инструментом модульного тестирования типа phpunit и с дебаггером. И если без автогенерации тестов в принципе еще как-то жить можно. То без пошаговой инспекции пожалуй с какого-то момента не получится. При желании дополнительно можно что-то придумать с поиском импортируемых зависимостей через composer, прикрутить профилировщик, среду функционального тестирования, но это уже вопрос не столько к редактору, сколько к организации рабочего процесса разработки в целом.

В следующий раз наверное имеет смысл затронуть DAP интеграцию PhpActor c PhpUnit. Остальное, типа phpDocumentor работает прекрасно и без непосредственной интеграции с редактором и до них я сам еще не дошел. Возможно когда-нибудь потом.

:write || :quit