A comprehensive PHP refactoring plugin for Neovim that brings PHPStorm-like refactoring capabilities to your favorite editor.
This plugin is still in development, please report any issues you find.
This plugin provides essential PHP refactoring operations:
- Extract Variable - Extract expressions into variables
- Extract Method - Extract code blocks into methods
- Extract Class - Extract functionality into new classes
- Extract Interface - Generate interfaces from classes
- Introduce Constant - Extract values into class constants
- Introduce Field - Extract values into class properties
- Introduce Parameter - Extract values into function parameters
- Change Signature - Modify function/method signatures safely
- Pull Members Up - Move members to parent classes
- Rename Variable - Rename variables within their scope
- Rename Method - Rename methods and their calls
- Rename Class - Rename classes and their references
- TreeSitter Support - Uses TreeSitter for context detection with regex fallback
- Context Awareness - Commands adapt based on cursor position and selection
- Single File Focus - Reliable refactoring within individual PHP files
- Neovim 0.5+ (for LSP support)
- nui.nvim - For beautiful UI components
- Optional: TreeSitter PHP parser (
TSInstall php) - Optional: PHP LSP server (Intelephense, PHPActor)
Using lazy.nvim
{
'adibhanna/phprefactoring.nvim',
dependencies = {
'MunifTanjim/nui.nvim',
},
ft = 'php',
config = function()
require('phprefactoring').setup({
-- Configuration options
})
end,
}Using packer.nvim
use {
'adibhanna/phprefactoring.nvim',
requires = {
'MunifTanjim/nui.nvim',
},
ft = 'php',
config = function()
require('phprefactoring').setup()
end,
}require('phprefactoring').setup({
-- UI Configuration
ui = {
use_floating_menu = true, -- Use floating windows for dialogs
border = 'rounded', -- Border style: 'rounded', 'single', 'double'
width = 40, -- Dialog width
height = nil, -- Auto-calculated height
highlights = {
menu_title = 'Title',
menu_border = 'FloatBorder',
menu_item = 'Normal',
menu_selected = 'PmenuSel',
menu_shortcut = 'Comment',
}
},
-- Refactoring Options
refactor = {
auto_format = true, -- Auto-format after refactoring
},
})All refactoring operations are available as commands:
:PHPExtractVariable " Extract selection to variable
:PHPExtractMethod " Extract selection to method
:PHPExtractClass " Extract selection to class
:PHPExtractInterface " Extract interface from class
:PHPIntroduceConstant " Introduce constant
:PHPIntroduceField " Introduce field/property
:PHPIntroduceParameter " Introduce parameter
:PHPChangeSignature " Change method signature
:PHPPullMembersUp " Pull members to parent class
:PHPRenameVariable " Rename variable in scope
:PHPRenameMethod " Rename method and calls
:PHPRenameClass " Rename class and referencesYou can create your own keymaps using the commands:
-- Example keymaps (add to your Neovim config)
vim.keymap.set('v', '<leader>rv', '<cmd>PHPExtractVariable<cr>', { desc = 'Extract variable' })
vim.keymap.set('v', '<leader>rm', '<cmd>PHPExtractMethod<cr>', { desc = 'Extract method' })
vim.keymap.set('v', '<leader>rc', '<cmd>PHPExtractClass<cr>', { desc = 'Extract class' })
vim.keymap.set('n', '<leader>ri', '<cmd>PHPExtractInterface<cr>', { desc = 'Extract interface' })
vim.keymap.set('n', '<leader>ic', '<cmd>PHPIntroduceConstant<cr>', { desc = 'Introduce constant' })
vim.keymap.set('n', '<leader>if', '<cmd>PHPIntroduceField<cr>', { desc = 'Introduce field' })
vim.keymap.set('n', '<leader>ip', '<cmd>PHPIntroduceParameter<cr>', { desc = 'Introduce parameter' })
vim.keymap.set('n', '<leader>cs', '<cmd>PHPChangeSignature<cr>', { desc = 'Change signature' })
vim.keymap.set('n', '<leader>pm', '<cmd>PHPPullMembersUp<cr>', { desc = 'Pull members up' })
vim.keymap.set('n', '<leader>rn', '<cmd>PHPRenameVariable<cr>', { desc = 'Rename variable' })
vim.keymap.set('n', '<leader>rp', '<cmd>PHPRenameMethod<cr>', { desc = 'Rename method' })
vim.keymap.set('n', '<leader>rt', '<cmd>PHPRenameClass<cr>', { desc = 'Rename class' })Note: These keymaps will work in PHP files. You can customize them to your preference.
// Before
$user = User::find($request->get('user_id'));
// Select `$request->get('user_id')` and run :PHPExtractVariable
// After
$userId = $request->get('user_id');
$user = User::find($userId);// Before (select the validation code)
if (empty($name)) {
throw new InvalidArgumentException('Name is required');
}
if (strlen($name) < 3) {
throw new InvalidArgumentException('Name too short');
}
// After running :PHPExtractMethod
private function validateName($name)
{
if (empty($name)) {
throw new InvalidArgumentException('Name is required');
}
if (strlen($name) < 3) {
throw new InvalidArgumentException('Name too short');
}
}
// Original location becomes:
$this->validateName($name);// Before (cursor on the string)
if ($user->role === 'administrator') {
// After running :PHPIntroduceConstant
const ROLE_ADMINISTRATOR = 'administrator';
if ($user->role === self::ROLE_ADMINISTRATOR) {// Before (cursor in class)
class UserService
{
public function createUser($data) { /* ... */ }
public function updateUser($id, $data) { /* ... */ }
public function deleteUser($id) { /* ... */ }
private function validateData($data) { /* ... */ }
}
// After running :PHPExtractInterface
interface UserServiceInterface
{
public function createUser($data);
public function updateUser($id, $data);
public function deleteUser($id);
}
class UserService implements UserServiceInterface
{
// ... implementation
}// Before (cursor on method signature)
public function processUser($userId, $data) { /* ... */ }
// After running :PHPChangeSignature
// Enter new signature: processUser($userId, $data, $options = [])
public function processUser($userId, $data, $options = []) { /* ... */ }// Before (cursor on variable)
$userName = $request->get('name');
if (strlen($userName) > 0) {
echo "Hello " . $userName;
}
// After running :PHPRenameVariable and entering "fullName"
$fullName = $request->get('name');
if (strlen($fullName) > 0) {
echo "Hello " . $fullName;
}// Before (cursor on method name)
class UserService {
public function processUser($data) { /* ... */ }
public function handleRequest() {
$this->processUser($data);
}
}
// After running :PHPRenameMethod and entering "createUser"
class UserService {
public function createUser($data) { /* ... */ }
public function handleRequest() {
$this->createUser($data);
}
}// Before (cursor on class name in UserService.php)
class UserService {
// implementation
}
$service = new UserService();
// After running :PHPRenameClass and entering "UserManager"
// File is automatically renamed from UserService.php to UserManager.php
class UserManager {
// implementation
}
$service = new UserManager();Note: When renaming a class, if the file name matches the class name (case-insensitive), the file will be automatically renamed to match the new class name.
The plugin automatically detects and uses the best available parsing method:
- TreeSitter Auto-Detection - Automatically uses TreeSitter if PHP parser is available
- Regex Fallback - Falls back to regex patterns when TreeSitter is unavailable
Built on nui.nvim for:
- Input dialogs for collecting user input
- Confirmation dialogs for destructive operations
- Notification system for user feedback
- Install the plugin using your preferred package manager
- Install dependencies:
nui.nvim - Optional: Install TreeSitter PHP parser:
:TSInstall php(auto-detected for better context detection) - Use commands directly or create custom keymaps
- Start refactoring in your PHP files!
Contributions are welcome! Please feel free to submit issues and pull requests.
- Clone the repository
- Install dependencies:
nui.nvim - Set up TreeSitter PHP parser:
:TSInstall php(auto-detected for context detection)
- Create a new module in
lua/phprefactoring/refactors/ - Add the command to
plugin/phprefactoring.lua - Add the function to
lua/phprefactoring/init.lua - Update documentation
MIT License - see LICENSE file for details.
- Inspired by JetBrains PHPStorm's excellent refactoring tools
- Built on the amazing nui.nvim library