Skip to content

Commit 7a7824e

Browse files
committed
Fall back on an HTML mode for Blade when web-mode is missing
Opening a .blade.php file without web-mode installed did not warn and carry on -- it failed. php-derivation-major-mode warned but left MODE set to the unavailable web-mode, so php-mode-maybe went on to funcall it and signalled void-function. Every other template zeroed MODE and fell back on php-default-major-mode; Blade was the one case that could not open at all. Degrade instead. A Blade template is mostly HTML plus Blade's own directives -- it is not PHP, so php-mode cannot read it either -- and an HTML mode renders the bulk of it correctly. The new php-blade-template-major-mode-fallback names the modes to try (mhtml-mode then html-mode, both built in); set it to nil to opt out and get php-default-major-mode as before. The warning now says which mode was substituted rather than claiming Blade is unsupported. The fallback deliberately applies to Blade only. Other templates are PHP with HTML in them, which php-mode does read, and they share php-html-template-major-mode with the auto-detection path -- php-project-php-file-as-template defaults to `auto', so any .php file holding an HTML tag reaches it. Extending the fallback there would take most PHP files away from php-mode for everyone without web-mode. A test pins that behaviour down. Avoids seq-find: seq is not preloaded on every supported Emacs.
1 parent a357bc8 commit 7a7824e

2 files changed

Lines changed: 83 additions & 4 deletions

File tree

lisp/php.el

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,20 @@ a completion list."
180180
:tag "PHP Blade Template Major Mode"
181181
:type 'function)
182182

183+
(defcustom php-blade-template-major-mode-fallback '(mhtml-mode html-mode)
184+
"Major modes to fall back on for a Blade template.
185+
186+
Used when `php-blade-template-major-mode'`web-mode' by default — is
187+
not installed. The first entry whose function is defined wins; when
188+
none is, `php-default-major-mode' is used.
189+
190+
A Blade template is mostly HTML plus Blade's own directives, so an HTML
191+
mode reads it far better than `php-mode' does. Set this to nil to opt
192+
out and get `php-default-major-mode' instead."
193+
:group 'php
194+
:tag "PHP Blade Template Major Mode Fallback"
195+
:type '(repeat function))
196+
183197
(defcustom php-template-mode-alist
184198
`(("\\.blade" . ,php-blade-template-major-mode)
185199
("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode php-default-major-mode))
@@ -623,6 +637,13 @@ indentation."
623637
(setq php--buffer-has-html-tag-cache (cons tick result))
624638
result))))
625639

640+
(defun php--blade-template-fallback-mode ()
641+
"Return the first available mode of `php-blade-template-major-mode-fallback'."
642+
(catch 'found
643+
(dolist (mode php-blade-template-major-mode-fallback)
644+
(when (fboundp mode)
645+
(throw 'found mode)))))
646+
626647
(defun php-derivation-major-mode ()
627648
"Return major mode for PHP file by file-name and its content."
628649
(let ((mode (assoc-default buffer-file-name
@@ -638,10 +659,19 @@ indentation."
638659
(when (php-buffer-has-html-tag)
639660
(setq mode php-html-template-major-mode)))))
640661
(when (and mode (not (fboundp mode)))
641-
(if (string-match-p "\\.blade\\." buffer-file-name)
642-
(warn "php-mode is NOT support blade template. %s"
643-
"Please install `web-mode' package")
644-
(setq mode nil)))
662+
;; A Blade template is not PHP, so `php-default-major-mode' cannot
663+
;; read it at all; degrade to an HTML mode instead. Every other
664+
;; template is PHP with HTML in it, and keeps falling back on
665+
;; `php-default-major-mode' below.
666+
(setq mode (when (string-match-p "\\.blade\\." buffer-file-name)
667+
(let ((fallback (php--blade-template-fallback-mode)))
668+
(warn "`%s' is not available for this Blade template; %s.
669+
Install the `web-mode' package for full Blade support."
670+
mode
671+
(if fallback
672+
(format "using `%s' instead" fallback)
673+
(format "falling back on `%s'" php-default-major-mode)))
674+
fallback))))
645675
(or mode php-default-major-mode)))
646676

647677
;;;###autoload

tests/php-mode-test.el

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,55 @@ than depending on `poly-php', because that package pulls in a released
699699
(set-buffer-modified-p nil)
700700
(setq buffer-file-name nil)))))
701701

702+
(defun php-mode-test--derive (file-name &rest body-text)
703+
"Return the mode `php-derivation-major-mode' picks for FILE-NAME.
704+
BODY-TEXT is inserted into the buffer first."
705+
(with-temp-buffer
706+
(setq buffer-file-name (expand-file-name file-name temporary-file-directory))
707+
(unwind-protect
708+
(progn
709+
(apply #'insert body-text)
710+
(php-derivation-major-mode))
711+
(set-buffer-modified-p nil)
712+
(setq buffer-file-name nil))))
713+
714+
(ert-deftest php-mode-test-blade-template-fallback ()
715+
"A Blade template degrades to an HTML mode when `web-mode' is missing.
716+
717+
A `.blade.php' file is mostly HTML plus Blade directives, so it is not
718+
PHP and `php-default-major-mode' cannot read it. Before the fallback
719+
existed, `php-derivation-major-mode' returned the unavailable
720+
`web-mode' anyway and `php-mode-maybe' then failed with
721+
`void-function'."
722+
(let ((php-blade-template-major-mode 'php-mode-test--absent-web-mode)
723+
(php-template-mode-alist '(("\\.blade" . php-mode-test--absent-web-mode))))
724+
(should-not (fboundp 'php-mode-test--absent-web-mode))
725+
;; The chosen fallback must be usable: this is what `php-mode-maybe'
726+
;; funcalls, so an unavailable mode would signal `void-function'.
727+
(let ((php-blade-template-major-mode-fallback '(html-mode)))
728+
(should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))
729+
;; Unavailable entries are skipped.
730+
(let ((php-blade-template-major-mode-fallback '(php-mode-test--absent-web-mode html-mode)))
731+
(should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))
732+
;; Opting out falls back on `php-default-major-mode'.
733+
(let ((php-blade-template-major-mode-fallback nil)
734+
(php-default-major-mode 'php-mode))
735+
(should (eq 'php-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n"))))))
736+
737+
(ert-deftest php-mode-test-html-template-fallback-unchanged ()
738+
"A missing `php-html-template-major-mode' still derives to PHP.
739+
740+
Only Blade degrades to an HTML mode. `php-project-php-file-as-template'
741+
defaults to `auto', so any .php file holding an HTML tag reaches this
742+
path; sending those to an HTML mode would take most PHP files away from
743+
`php-mode' for anyone without `web-mode'."
744+
(let ((php-html-template-major-mode 'php-mode-test--absent-web-mode)
745+
(php-project-php-file-as-template 'auto)
746+
(php-default-major-mode 'php-mode)
747+
(php-template-mode-alist nil))
748+
(should-not (fboundp 'php-mode-test--absent-web-mode))
749+
(should (eq 'php-mode (php-mode-test--derive "page.php" "<div>\n<?php echo 'hi'; ?>\n</div>\n")))))
750+
702751
(ert-deftest php-mode-test-php74 ()
703752
"Test highlighting language constructs added in PHP 7.4."
704753
(with-php-mode-test ("7.4/arrow-function.php" :faces t))

0 commit comments

Comments
 (0)