Open-source project
emacs-php/php-mode avatar
emacs-php/php-mode

php-mode: an Emacs PHP major mode that abandoned CC Mode for a js.el engine

A powerful and flexible Emacs major mode for editing PHP scripts

606 stars117 forksEmacs LispGPL-3.0

At a glance

What is it?
php-mode is a GPL-3.0 Emacs major mode for PHP that no longer depends on CC Mode, since its indentation now comes from an engine derived from js.el, which ships with GNU Emacs. It supports several coding styles, bridges to Eglot and Phpactor, and keeps the old CC Mode implementation available as php-cc-mode.
Who is it for?
php-mode suits Emacs users editing PHP who want per or psr2 indentation without configuring CC Mode, and who want a per-project configuration file and an optional bridge to Eglot, lsp-mode, lsp-bridge or Phpactor. It does not suit anyone still on Emacs 26, since that release was sunset in v1.26.1, and the php-ide bridge is described as experimental.
Can I use it commercially?
Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
Is it still maintained?
Yes. The repository last received commits 59 days ago.
What is it written in?
Mainly Emacs Lisp, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The rewrite that removed the CC Mode dependency

php-mode is a major mode for editing PHP scripts in Emacs, and its most consequential change is architectural. The project states in a highlighted note that PHP Mode has been rewritten and no longer depends on CC Mode. Indentation is now handled by an engine derived from `js.el`, which is bundled with GNU Emacs.

That matters for anyone who has configured Emacs for years. CC Mode brought its own variable conventions and its own habits, and a mode built on top of it inherits them whether you want them or not. Removing the dependency also removes a large external expectation: the indentation engine now comes from a file that ships with Emacs itself, so the mode does not need to track a separate CC Mode release cycle.

The old implementation is not thrown away. The previous CC Mode based implementation is still available as `php-cc-mode`, and the project reserves a section for it. That is a courtesy for people with existing muscle memory or existing configuration, and it also gives a migration path: the two implementations coexist in one package.

There is a second part to the rewrite that shows up in day to day use. Because the indentation engine no longer uses CC Mode, `c-basic-offset` is obsolete in `php-mode`, and you customise `php-indent-offset` instead. The mode keeps a compatibility path for projects that set `c-basic-offset` buffer-locally, through `.dir-locals.el` or file-local variables, in which case it copies the value into `php-indent-offset` and shows a warning. So an old project configuration keeps working and tells you what to change.

Installing on Emacs 28.1 and later

PHP Mode works with Emacs 28.1 or later, and installation on that version is a single command inside Emacs:

lisp
M-x package-install php-mode

The package is also listed on MELPA and on NonGNU ELPA, and the project notes that adding MELPA to `package-archives` extends Emacs with numerous packages from the web. If you would rather not use a package manager at all, the documentation points to a manual installation method for installing the Lisp files directly in the traditional manner.

There is one upgrade trap worth describing in full, because the error it produces looks unrelated to the cause. The warning explains that after upgrading Emacs, opening a PHP file for the first time may produce errors related to CC Mode, and that this happens because a previously byte-compiled version of PHP Mode cached on your disk differs from the newly installed one. The fix is to reinstall the package, either with `M-x php-mode-debug-reinstall` or with `M-x package-reinstall php-mode`. Neither needs a rebuild of your init file, and the project treats this as a routine consequence of an Emacs upgrade rather than a bug.

The current version numbers tell you where the mode sits. The latest release supports Emacs 31.1 and PHP 8.5, and the note at the top of the README says the latest version supports Emacs 30 and invites discussion if the upgrade causes problems. The badges show the same pairing, with Emacs 31.1 and PHP 8.5 as the stated targets.

Coding styles, per by default and symfony2 gone

The mode sets indentation and related editing variables according to `php-mode-coding-style`. The default is `per`, the PER Coding Style 2.0, whereas previous versions defaulted to `pear`. The available styles are `per`, `psr2`, `pear`, `drupal` and `wordpress`.

One removal is worth noting if you are carrying an old configuration. The `symfony2` style has been removed, since PER supersedes the coding style used by modern Symfony. It remains available in `php-cc-mode`, so an old Symfony project can be kept on the legacy implementation or migrated to `per`.

The style is a single variable, which means it can be set globally in your init file or per project. A personal configuration looks like this:

lisp
(defun my-php-mode-init ()
  (subword-mode 1)
  (setq-local show-trailing-whitespace t)
  (setq-local ac-disable-faces '(font-lock-comment-face font-lock-string-face))
  (add-hook 'hack-local-variables-hook 'php-ide-turn-on nil t))

(with-eval-after-load 'php-mode
  (add-hook 'php-mode-hook #'my-php-mode-init)
  (custom-set-variables
   '(php-mode-coding-style 'psr2)
   '(php-mode-template-compatibility nil)
   '(php-imenu-generic-expression 'php-imenu-generic-expression-simple))

The last three lines of that snippet are the ones most projects care about: the coding style, the template compatibility flag and the imenu expression used to build the buffer's outline of functions.

Project level settings go in a `.dir-locals.el` or `.dir-locals-2.el` file in the project root:

lisp
((nil
  (php-project-root . git)
  (php-project-coding-style . psr2)))

The project recommends not putting those files under version control, since they depend on the packages installed on each user's Emacs.

php-ide as a switchboard rather than a language server

The `php-ide` layer, in `php-ide.el`, is described as an experimental bridge between PHP Mode and IDE-like tools. It connects to Eglot, lsp-mode, lsp-bridge and Phpactor, and the project is explicit that it does not implement any of those features itself. It only activates or deactivates whichever ones you choose, through a single `php-ide-mode` minor mode.

The configuration is a list of features and a chosen executable:

lisp
(defun my-php-mode-init ()
  (add-hook 'hack-local-variables-hook 'php-ide-turn-on nil t))

(with-eval-after-load 'php-ide
  (custom-set-variables
   '(php-ide-features '(eglot))              ;; and/or '(phpactor), '(lsp-mode), '(lsp-bridge)
   '(php-ide-eglot-executable 'intelephense)  ;; or 'phpantom, 'php-lsp, 'phpactor, a path string, or a list of strings
   '(php-ide-mode-lighter "")))               ;; hide PHP-IDE from the mode line

The commands are thin. `M-x php-ide-mode` toggles PHP-IDE for the current buffer using `php-ide-features`, `M-x php-ide-turn-on` does the same but never errors when no feature is configured, `M-x php-ide-set-feature` picks interactively among the features actually available on the system, and `M-x php-ide-status` reports what is on, what is configured and what is installed.

The design choice is worth noting against the alternative of an integrated LSP client. A mode that embeds a language server owns the server lifecycle, the diagnostics pipeline and the completion UI. This one delegates all of that and only turns the servers on, which keeps php-mode independent of any one client at the cost of giving up fine grained control over how diagnostics appear.

The dir-locals safety boundary for IDE settings

Because the mode can be configured per project, the project spends a paragraph on what a repository is allowed to configure. `php-ide-features` and `php-ide-eglot-executable` are only treated as safe for `.dir-locals.el` when they name one of PHP-IDE's own built-in features or bundled executable presets, for example `intelephense`, `phpantom`, `php-lsp` or `phpactor`.

Anything else still goes through Emacs's normal confirmation for risky directory-local variables: a raw executable path, explicit command arguments, or a custom `php-ide-mode-functions` hook. The stated reason is direct, that applying those silently would let any repository run an arbitrary command, or a Lisp function, in your Emacs just by having you open a file in it.

That is a sensible boundary and it is narrower than the general Emacs default, which prompts for any unknown directory-local variable. The mode has a reason to widen the safe list, since choosing an LSP server is a common per-project need, but it keeps the dangerous half of the configuration behind a prompt.

A per-project IDE setup therefore looks like this:

lisp
((nil (php-project-root . git)
      (php-ide-features . (eglot))))

The broader project integration is handled by `php-project-get-root-dir`, which first looks for a PHP-specific marker and is documented alongside the `project.el` and Projectile support, so a PHP file opened inside another kind of project tree still resolves to the right root.

How the project tests itself, and what a release contains

The repository is an Eask project, with an `Eask` file and a Makefile that wraps it. The test target is short:

makefile
test: clean all
	$(EASK) install polymode web-mode
	$(EASK) test ert ./tests/php-mode-test.el

The test run installs `polymode` and `web-mode` and then runs the ERT suite in `tests/php-mode-test.el`. There is a second target for the legacy implementation, `test-cc`, which runs `tests/php-cc-mode-test.el`, and the Makefile explains why the two run in separate Eask invocations: the two major modes still share several public symbol names, so running them in one Emacs session would let them clobber each other.

Other targets cover the packaging chores. `compile` and `all` build the Lisp and autoloads, `authors` regenerates `AUTHORS.md` from the git history with the `.mailmap` file applied, and `clean` removes build artefacts. There is also a `dev` target that installs a `prepare-commit-msg` hook into `.git/hooks`, so a commit message template is applied automatically.

On releases, the tags are informative. v1.28.0 supports PHP 8.5 and Emacs 31.1 and was published on 2026-08-25, v1.27.0 added support for Emacs 30 as a pretest plus PHP 8.4 syntax on 2024-12-19, and v1.26.1 marked the sunset of Emacs 26 on 2024-09-12.

The licence is GPL-3.0, which for an Emacs Lisp package distributed through MELPA is the norm and carries the usual obligations for a distributed derivative. That is a statement about the licence terms rather than advice on your obligations. The repository is not archived and the last push was on 2026-08-25, the same day as the most recent release.

Editorial conclusion

php-mode suits Emacs users editing PHP who want per or psr2 indentation without configuring CC Mode, and who want a per-project configuration file and an optional bridge to Eglot, lsp-mode, lsp-bridge or Phpactor. It does not suit anyone still on Emacs 26, since that release was sunset in v1.26.1, and the php-ide bridge is described as experimental. Before upgrading Emacs, read the note about stale byte-compiled files and run M-x php-mode-debug-reinstall or M-x package-reinstall php-mode, and check that php-indent-offset rather than c-basic-offset carries your indentation, since the latter is obsolete in the rewritten mode.

Frequently asked questions

What Emacs versions does php-mode support?

PHP Mode works with Emacs 28.1 or later. The current release adds support for Emacs 31.1 and PHP 8.5, and Emacs 26 was sunset in v1.26.1.

How do I install php-mode?

On Emacs 28.1 or later, run `M-x package-install php-mode`. The package is also on MELPA and NonGNU ELPA, and the documentation describes a manual installation for people who prefer not to use a package manager.

Why do I get CC Mode errors after upgrading Emacs?

A previously byte-compiled version of PHP Mode cached on your disk differs from the newly installed one. The project says reinstalling resolves it, with `M-x php-mode-debug-reinstall` or `M-x package-reinstall php-mode`.

Which coding styles does php-mode support and what is the default?

The default is `per`, the PER Coding Style 2.0, while earlier versions defaulted to `pear`. The available styles are `per`, `psr2`, `pear`, `drupal` and `wordpress`, and the `symfony2` style has been removed because PER supersedes it.

What does the php-ide feature in php-mode actually do?

It is an experimental bridge that activates or deactivates Eglot, lsp-mode, lsp-bridge or Phpactor through a single `php-ide-mode` minor mode, without implementing any of those features itself. `php-ide-features` selects which ones are used and `php-ide-eglot-executable` chooses the server binary.

Official sources

  1. emacs-php/php-mode on GitHub
  2. Issues
  3. License: GPL-3.0
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/emacs-php-php-mode.svg)](https://hysenlabs.com/projects/emacs-php-php-mode)

Was this useful?