Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions lisp/php.el
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ a completion list."
:tag "PHP Blade Template Major Mode"
:type 'function)

(defcustom php-blade-template-major-mode-fallback '(mhtml-mode html-mode)
"Major modes to fall back on for a Blade template.

Used when `php-blade-template-major-mode' — `web-mode' by default — is
not installed. The first entry whose function is defined wins; when
none is, `php-default-major-mode' is used.

A Blade template is mostly HTML plus Blade's own directives, so an HTML
mode reads it far better than `php-mode' does. Set this to nil to opt
out and get `php-default-major-mode' instead."
:group 'php
:tag "PHP Blade Template Major Mode Fallback"
:type '(repeat function))

(defcustom php-template-mode-alist
`(("\\.blade" . ,php-blade-template-major-mode)
("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode php-default-major-mode))
Expand Down Expand Up @@ -623,6 +637,11 @@ indentation."
(setq php--buffer-has-html-tag-cache (cons tick result))
result))))

(defun php--blade-template-fallback-mode ()
"Return the first available mode of `php-blade-template-major-mode-fallback'."
(cl-loop for mode in php-blade-template-major-mode-fallback
thereis (and (fboundp mode) mode)))

(defun php-derivation-major-mode ()
"Return major mode for PHP file by file-name and its content."
(let ((mode (assoc-default buffer-file-name
Expand All @@ -638,10 +657,19 @@ indentation."
(when (php-buffer-has-html-tag)
(setq mode php-html-template-major-mode)))))
(when (and mode (not (fboundp mode)))
(if (string-match-p "\\.blade\\." buffer-file-name)
(warn "php-mode is NOT support blade template. %s"
"Please install `web-mode' package")
(setq mode nil)))
;; A Blade template is not PHP, so `php-default-major-mode' cannot
;; read it at all; degrade to an HTML mode instead. Every other
;; template is PHP with HTML in it, and keeps falling back on
;; `php-default-major-mode' below.
(setq mode (when (string-match-p "\\.blade\\." buffer-file-name)
(let ((fallback (php--blade-template-fallback-mode)))
(warn "`%s' is not available for this Blade template; %s.
Install the `web-mode' package for full Blade support."
mode
(if fallback
(format "using `%s' instead" fallback)
(format "falling back on `%s'" php-default-major-mode)))
fallback))))
(or mode php-default-major-mode)))

;;;###autoload
Expand Down
49 changes: 49 additions & 0 deletions tests/php-mode-test.el
Original file line number Diff line number Diff line change
Expand Up @@ -734,6 +734,55 @@ those going through `php-mode-maybe' did not: the same buffer became
(set-buffer-modified-p nil)
(setq buffer-file-name nil))))))

(defun php-mode-test--derive (file-name &rest body-text)
"Return the mode `php-derivation-major-mode' picks for FILE-NAME.
BODY-TEXT is inserted into the buffer first."
(with-temp-buffer
(setq buffer-file-name (expand-file-name file-name temporary-file-directory))
(unwind-protect
(progn
(apply #'insert body-text)
(php-derivation-major-mode))
(set-buffer-modified-p nil)
(setq buffer-file-name nil))))

(ert-deftest php-mode-test-blade-template-fallback ()
"A Blade template degrades to an HTML mode when `web-mode' is missing.

A `.blade.php' file is mostly HTML plus Blade directives, so it is not
PHP and `php-default-major-mode' cannot read it. Before the fallback
existed, `php-derivation-major-mode' returned the unavailable
`web-mode' anyway and `php-mode-maybe' then failed with
`void-function'."
(let ((php-blade-template-major-mode 'php-mode-test--absent-web-mode)
(php-template-mode-alist '(("\\.blade" . php-mode-test--absent-web-mode))))
(should-not (fboundp 'php-mode-test--absent-web-mode))
;; The chosen fallback must be usable: this is what `php-mode-maybe'
;; funcalls, so an unavailable mode would signal `void-function'.
(let ((php-blade-template-major-mode-fallback '(html-mode)))
(should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))
;; Unavailable entries are skipped.
(let ((php-blade-template-major-mode-fallback '(php-mode-test--absent-web-mode html-mode)))
(should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))
;; Opting out falls back on `php-default-major-mode'.
(let ((php-blade-template-major-mode-fallback nil)
(php-default-major-mode 'php-mode))
(should (eq 'php-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))))

(ert-deftest php-mode-test-html-template-fallback-unchanged ()
"A missing `php-html-template-major-mode' still derives to PHP.

Only Blade degrades to an HTML mode. `php-project-php-file-as-template'
defaults to `auto', so any .php file holding an HTML tag reaches this
path; sending those to an HTML mode would take most PHP files away from
`php-mode' for anyone without `web-mode'."
(let ((php-html-template-major-mode 'php-mode-test--absent-web-mode)
(php-project-php-file-as-template 'auto)
(php-default-major-mode 'php-mode)
(php-template-mode-alist nil))
(should-not (fboundp 'php-mode-test--absent-web-mode))
(should (eq 'php-mode (php-mode-test--derive "page.php" "<div>\n<?php echo 'hi'; ?>\n</div>\n")))))

(ert-deftest php-mode-test-php74 ()
"Test highlighting language constructs added in PHP 7.4."
(with-php-mode-test ("7.4/arrow-function.php" :faces t))
Expand Down
Loading