Building a Better PHP — Part 5: Static Analysis & Other CLI Tools

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:

hh_client  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
    • types.hh:7:11,13: It is incompatible with a float
      • Which is incompatible with the float being passed in
  • 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

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:

hh_client  vim integration

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 — output check results as JSON
  • --identify-function LINE:POS < FILE — identify the function at position POS on line LINE in STDIN
  • --type-at-pos LINE:POS < FILE — show the type at position POS on line LINE in STDIN

Hackificating

In addition to the static analyzer, HHVM includes tools to help you with converting existing codebases to Hack.

Warning!
This process is far from perfect. You should verify all changes by hand.

The hackification process is a 3-step process:

  1. Convert all files to hack files, by simply changing the <?php to <?hh, this exposes the files to the static analyzer
  2. Add soft type hints, this allows you to run the code and log any type mismatches, but will not enforce the type hints
  3. 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!

About Davey Shafik

Davey Shafik is a full time PHP Developer with 12 years experience in PHP and related technologies. A Community Engineer for Engine Yard, he has written three books (so far!), numerous articles and spoken at conferences the globe over.

Davey is best known for his books, the Zend PHP 5 Certification Study Guide and PHP Master: Write Cutting Edge Code, and as the originator of PHP Archive (PHAR) for PHP 5.3.