Building a Better PHP — Part 5: Static Analysis & Other CLI Tools
Static Analysis
The real benefit of type hints in Hack is not realized at run-time but instead through static analysis.
Hack’s goal is to perform static analysis, in near real-time, automatically on save. Even on large code bases (like Facebook’s own).
To achieve this, the static analyzer is two parts, hh_server
and hh_client
.
As its name suggests, hh_server
runs in the background, where it’s job is to monitor for changed files (using inotify) and validate them using the static analyzer.
hh_server
is then queried by the hh_client
to retrieve and display any issues.
While you can start hh_server
manually, the recommended behavior is to start it using hh_client
— which will start it automatically if necessary.
You can manually start, stop, and restart hh_server
using the hh_client (start|stop|restart)
commands. Additionally, you can query its status by using hh_client status
.
Setup
In order to use hh_client
we must set up our project root, this is done simply by creating an empty file called .hhconfig
in the root directory.
When you run hh_client
it will traverse up the directory structure until it finds a .hhconfig
, allowing you to run it in sub-directories — it will always run for the entire project from the root however. You can place multiple .hhconfig
files in your directory allowing you to check smaller portions of you code base, but it isn’t recommend:
For example, given the following common project layout:
my-app/
|-- public/
|-- src/
|-- tests/
`-- vendor/
We would place an .hhconfig
file in the my-app
directory to mark it as the project root, however this will include everything in the vendor
and tests
directories also, which we might not want to do. While the static analyzer is super fast, especially in the case of third party libraries, we might not want to know about those issues every time we make a change to our own code.
To resolve this, we can place an additional .hhconfig
inside the src
directory, and run hh_client
from within it — however, now we no longer get the results of the output from code in the public
directory which we likely want.
Using the Static Analyzer
Running hh_client
on the command line will (by default) show you any hack errors in .hh
or .php
files so long as the <?hh
open tag is used — this output is colored, and indented to help readability.
For example, given the following errorneous code:
<?hh // strict
function greeting(string $who):void {
return "Hello $who";
}
function sayHello() {
echo greeting(1.0);
}
Running hh_client
will display the following output:
Stepping through these errors, we can see that:
types.hh:6:10,17: Was expecting a return type hint
- On line 6, the function named at characters 10 through 17, has no return type hint (required in strict mode)
types.hh:7:11,13: Invalid argument
- On line 7, characters 11-13, we are passing in an invalid argument
types.hh:2:19,24: This is a string
- The argument, is defined on line 2, chars 19-24 as a
string
- The argument, is defined on line 2, chars 19-24 as a
types.hh:7:11,13: It is incompatible with a float
- Which is incompatible with the
float
being passed in
- Which is incompatible with the
types.hh:3:2,7: You cannot return a value
- On line 3, at char 2, we a returning a value:
types.hh:2:32,35: This is a void function
- for a function defined as
void
on line 2, char 32-35
- for a function defined as
As you can see, the output is quite verbose, yet very accurate and specific.
Its important to understand the hh_client
always displays the output for the entire project.
Editor Integration
Currently, HHVM ships with support for vim and emacs. Unfortunately — my personal favorite IDE — phpStorm, has an open issue for Hack support but has not yet added support.
Using the static analyzer with your editor can greatly improve your productivity by showing you the hh_client
on save.
HHVM ships with the emacs plugin (found in /usr/share/hhvm/hack/emacs
), while the vim plugin must be installed from the hhvm/vim-hack repository.
If you are using Pathogen, then you can simply clone the repository in ~/.vim/bundle
.
Once you have the plugin installed, saving will cause a new pane to show with the output of hh_client
if there are any errors:
Note: This is not file specific, and will show errors across your entire project.
Refactoring
It is possible to perform some simple refactoring within your project using hh_client check --refactor
. In reality, all this command can do is rename classes, functions, or methods — but having the static analysis means it is much more capable at making these changes. Unfortunately, this command does not yet support namespaces, but even still, it can be useful.
Running the above command will prompt you to enter whether you are refactoring a class, function, or method. Then it will ask you for the current name, and the new name.
It will then update every instance of that class, method, or function, across the entire code base:
$ hh_client check --refactor
WARNING: This tool will only refactor references in typed, hack code. Its results should be manually verified. Namespaces are not yet supported.
What would you like to refactor:
1 - Class
2 - Function
3 - Method
Enter 1, 2, or 3: 2
Enter function name: sayHello
Enter a new name for this function: sayHi
Rewrote 2 files.
Other Options
There are several other interesting flags for the check
command available:
--list-files
— print a simple list of files with errors--search IDENTIFIED
— search for the definition of a function, class, method, interface, trait, constant, or typedef--find-refs FUNCTION|CLASS::METHOD
— finds usages of a specified function or method--find-class-refs CLASS
— finds usages of a specified class--json
— outputcheck
results as JSON--identify-function LINE:POS < FILE
— identify the function at positionPOS
on lineLINE
in STDIN--type-at-pos LINE:POS < FILE
— show the type at positionPOS
on lineLINE
in STDIN
Hackificating
In addition to the static analyzer, HHVM includes tools to help you with converting existing codebases to Hack.
The hackification process is a 3-step process:
- Convert all files to hack files, by simply changing the
<?php
to<?hh
, this exposes the files to the static analyzer - Add soft type hints, this allows you to run the code and log any type mismatches, but will not enforce the type hints
- Remove soft type hints
Switching the Open Tag
The first step can be accomplished by hand, or you can use the hackificator
which will automatically mark files as decl
and strict
when possible. To apply it to an entire directory simply use:
$ hackificator .
Despite what hackificator --help
implies, this simple command will modify the files in place.
Adding Soft Types
Once you’ve done this, you can then use hh_server
to add soft types, this is done with the --convert
flag which takes two arguments, the directory to convert, and the project room — which may be the same value.
To convert your entire project, you might do:
$ hh_server --convert ./ ./
At this point, you will have soft-types applied, and arguments with a null default value will be marked as nullable.
For example this function from Zend Framework 2’s Zend\Barcode package:
public static function makeBarcode($barcode, $barcodeConfig = array())
becomes:
public static function makeBarcode(@?string $barcode, @array $barcodeConfig = array()) : @\Zend\Barcode\Object\ObjectInterface
Now you can now start running this code (this is where having a full test suite comes in handy!) and gathering the logs from HHVM.
Hardering Type Hints
The log file generated by HHVM using your update code will detail what soft type hints are failing — these are then removed entirely. This ensures that no invalid inference is made, and will cause errors if they appear in strict files.
To do this, we use hack_remove_soft_types
. Using the --delete-from-log
argument, we can tell it to strip the failing hints:
$ hack_remove_soft_types --delete-from-log hhvm.log
Note: This tool is dumb, and requires that paths in the log file match the local filesystem. If this is not the case, you can edit the file easily using something like sed
.
Once this has completed, you should verify the output of hh_client
and fix any errors that are now present by hand.
Finally, you can harder all soft hints using the --harden
flag:
$ hack_remove_soft_types --harden FILE
To run this on an entire directory, you can use something like:
$ find ./ -type f -name '*.php' -exec hack_remove_soft_types --harden '{}' ';'
Wrapping Up
This series has covered the history of HipHop, and HHVM, showed you how to use HHVM as a drop-in replacement for PHP, introduced Hack, looked at some more of the advanced features in Hack, and now finally we’ve looked at how to use the Hack static analysis tools, and the HHVM command line tools to help convert your existing code bases.
Since we started this series on the day Facebook initially released Hack to the public, Facebook has released not only the stable release with Hack (3.0) but also two other minor versions (3.1 and 3.2) since then, bringing support for PHP 5.6 features among other things — and they’re showing no signs of slowing down.
Additionally, Facebook has announced their work on an official spec for the PHP Language to ensure that all implementations have the same behavior as the original PHP.net implementation (or at the least, make it easier to spot the differences!).
We’re starting to see major sites like Wikipedia switching to HHVM already — obviously the performance wins are much greater at larger scale (and especially for a non-profit like Wikimedia).
Have you already, or are you planning on moving to HHVM anytime soon? If not, why not? And what about Hack? Will your next project be written entirely in Hack? Why?
We’d love to hear your thoughts on these questions, and anything else related in the comments!
Share your thoughts with @engineyard on Twitter