# phpinspect.el
PHPInspect is a minor mode that provides code intelligence for PHP in Emacs. At
its core is a PHP parser implemented in Emacs Lisp. PHPInspect comes with
backends for `completion-at-point`, `company-mode` and `eldoc`. A backend for
`xref` (which provides go-to-definition functionality) is planned to be
implemented at a later date. The main documentation of the mode is in the
docstring of the mode itself (`C-h f phpinspect-mode RET` to view, or read it in
the source code of [phpinspect.el](phpinspect.el)).

## Projects and Finding Types
By default, phpinspect will recognize composer projects and read their
composer.json files for autoload information which is used to find files in
which the types/classes/functions you use in your code are defined. It is also
possible to add an "include directory" of files that should always be read and
indexed for a certain project. To do this, open a file in a project and run `M-x
phpinspect-project-add-include-dir`. You can also edit the list of include
directories via `M-x customize-goup RET phpinspect RET`.

## Example Configuration
If you already have a completion UI setup that is able to use
`completion-at-point-functions` as completion source, you can basically just
enable phpinspect-mode and you'll be good to go. An example of a basic mode hook
configuration to get the most out of phpinspect is the following:

```elisp
(defun my-php-personal-hook ()
  ;; Shortcut to add use statements for classes you use.
  (define-key php-mode-map (kbd \"C-c u\") 'phpinspect-fix-imports)

  ;; Shortcuts to quickly search/open files of PHP classes.
  ;; You can make these local to php-mode, but making them global
  ;; like this makes them work in other modes/filetypes as well, which
  ;; can be handy when jumping between templates, config files and PHP code.
  (global-set-key (kbd \"C-c a\") 'phpinspect-find-class-file)
  (global-set-key (kbd \"C-c c\") 'phpinspect-find-own-class-file)

  ;; Enable phpinspect-mode
  (phpinspect-mode))

(add-hook 'php-mode-hook #'my-php-personal-hook)
```

## Example config with company mode setup

```elisp
;;;###autoload
(defun my-php-personal-hook ()
  ;; It is important to enable `company-mode' before setting
  ;; the variables below.
  (company-mode)
  (setq-local company-minimum-prefix-length 0)
  (setq-local company-tooltip-align-annotations t)
  (setq-local company-idle-delay 0.1)
  (setq-local company-backends '(phpinspect-company-backend))

  ;; Shortcut to add use statements for classes you use.
  (define-key php-mode-map (kbd "C-c u") 'phpinspect-fix-imports)

  ;; Shortcuts to quickly search/open files of PHP classes.
  (global-set-key (kbd "C-c a") 'phpinspect-find-class-file)
  (global-set-key (kbd "C-c c") 'phpinspect-find-own-class-file)

  (phpinspect-mode))

(add-hook 'php-mode-hook #'my-php-personal-hook)
```

## Install from git

```bash
git clone https://git.snorba.art/hugo/phpinspect.el ~/projects/phpinspect.el
cd ~/projects/phpinspect.el
make
```

```elisp
(add-to-list 'load-path "~/projects/phpinspect.el")
(require 'phpinspect)
```

## Compilation
It is highly recommended to byte- or native compile phpinspect. Aside from the
normal performance boost that this brings to most packages, it can reduce
phpinspect's parsing time by up to 90%. It especially makes a difference when
incrementally parsing edited buffers. For example:

### benchmarks/parse-file.el uncompiled on Ryzen 5 3600 (time in seconds):
```
Incremental parse (warmup):
Elapsed time: 0.168390 (0.019751 in 1 GC’s)
Incremental parse:
Elapsed time: 0.143811 (0.000000 in 0 GC’s)
Incremental parse (no edits):
Elapsed time: 0.000284 (0.000000 in 0 GC’s)
Incremental parse repeat (no edits):
Elapsed time: 0.000241 (0.000000 in 0 GC’s)
Incremental parse after buffer edit:
Elapsed time: 0.012449 (0.000000 in 0 GC’s)
Incremental parse after 2 more edits:
Elapsed time: 0.015839 (0.000000 in 0 GC’s)
Bare (no token reuse) parse (warmup):
Elapsed time: 0.048996 (0.000000 in 0 GC’s)
Bare (no token reuse) parse:
Elapsed time: 0.052495 (0.000000 in 0 GC’s)
```

### benchmarks/parse-file.el with native compilation on Ryzen 5 3600 (time in seconds):
```
Incremental parse (warmup):
Elapsed time: 0.023432 (0.000000 in 0 GC’s)
Incremental parse:
Elapsed time: 0.018350 (0.000000 in 0 GC’s)
Incremental parse (no edits):
Elapsed time: 0.000076 (0.000000 in 0 GC’s)
Incremental parse repeat (no edits):
Elapsed time: 0.000058 (0.000000 in 0 GC’s)
Incremental parse after buffer edit:
Elapsed time: 0.001212 (0.000000 in 0 GC’s)
Incremental parse after 2 more edits:
Elapsed time: 0.001381 (0.000000 in 0 GC’s)
Bare (no token reuse) parse (warmup):
Elapsed time: 0.013874 (0.000000 in 0 GC’s)
Bare (no token reuse) parse:
Elapsed time: 0.013878 (0.000000 in 0 GC’s)
```

## Development

### Building

```bash
make
```

### Running tests
Tests are implemented using `ert`. You can run them in batch mode with the following
command:

```bash
make test

# or:
emacs -L ./ -batch -l ert -l ./test/phpinspect-test.el -f ert-run-tests-batch-and-exit
```
