Greenfield Projects with Hack
Hack’s documentation and marketing is largely focused on how easy it is to migrate an existing PHP project; while this is a unique advantage of Hack, there isn’t much guidance on how new projects should be designed.
Until late 2015, the Hack and HHVM documentation site was a fork of PHP’s own documentation site. This had many shortcomings, and ultimately we decided that the best approach would be something custom. As most of the public Hack code at that point was toy examples, we decided to also make the site itself open, and start investigating the greenfield problems.
There are 3 basic approaches to ‘library code’ in Hack if there isn’t already a Hack version:
- Use a PHP library, without typechecker support
- Use a PHP library, and add HHI files so that Hack understands it
- Write something new
The Hack/HHVM site uses a mix of all three, though mostly #2 and #3.
Using a PHP library without typechecker support
While Hack code can call PHP code, the typechecker doesn’t understand
it; it won’t tell you about the kinds of mistakes it usually finds,
and you can’t use it in strict mode, or in any typechecked code if you
assume_php=false in your
.hhconfig. Despite this, it can still
be the best approach if a library is only used in a very small number of places or in places that aren’t
type-checked, such as pseudomains in your entrypoint. For example,
the Hack/HHVM documentation uses
to create PSR-7 objects from the entrypoint:
Using a PHP library with HHI files
An HHI file is basically Hack declarations without implementations; the existing PHP implementation is used by the runtime, but the typechecker (and IDEs like Nuclide) are able to use the declarations to provide full functionality. The suitability of this approach depends on the complexity of the library, if the API design is suitable for static typing, and how much your code interacts with it.
The API of FastRoute makes this approach a nearly-perfect fit,
especially since the introduction of
type, and as the project includes
an HHI file,
the typechecker fully understands the API. For example, Hack (and your IDE) understand
FastRoute\simpleDispatcher()’s first parameter
FastRoute\RouteCollector, and if you specify
routeParser in the options array, it should be the name
PHPUnit’s API isn’t type-safe (from Hack’s perspective), however there are still benefits to using an HHI file:
- Your unit tests can be in strict mode
<?hh // strict)
- The typechecker can tell you if you typo/miss-remember, eg
- Hack-aware IDEs like Nuclide can offer offer standard functionality like autocomplete
While PHPUnit does not include an HHI file, Simon Welsh has created 91-carriage/phpunit-hhi, a Composer-installable package containing corresponding HHI files.
There are now pure-Hack alternatives such as Isaac Leinweber’s HackUnit; while these have advantages like support for async, at the time they were much less mature, and familiarity is an advantage in itself.
We decided to use the (at-the-time) new PSR-7 standard for low-level request/response objects; these are exposed in quite a few places, so I created an HHI package; in the process, we discovered several design decisions in the API that make it not particularly suitable for Hack - for example:
documented as returning a string, however
can accept anything, and
getRequestTarget() must return it verbatim - so, the
return type of
getRequestTarget() is actually unknown.
Union types: Hack supports nullable types, but not general union types;
null|array|object, so isn’t well-suited to Hack. The
object is also unusual: PSR-7 is intended
to encourage interoperability, however this leaves some core
functionality as implementation-defined.
We were able to minimize these problems by assuming the worst - making the broadest possible assumptions about what we might be returned, and the narrowest about what can be passed. This did influence later decisions about creating HHIs vs new Hack implementations.
Write something new
We took this approach when we needed truly new functionality, or when we wanted more ‘Hack-like’ APIs; this would generally mean:
- Fully-typeable APIs (eg no union types)
- First-class support for XHP
- First-class support for async and
- No usage of
__get(), or similar
In particular, async support is something that needs to be designed into a framework from the beginning to have full benefits: the entire request/script needs to be structured as a dependency tree, with every node on the tree being awaitable if there is any chance of a descendent node needing to do any asyncronous operations (eg data fetching). Ultimately this means that existing PHP frameworks are not suitable; this led to us creating:
- A request-routing micro-framework (on top of FastRoute)
- A hierarchy of
Unfortunately, as we considered updating the documentation to be
fairly urgent, we did not build these as re-usable libraries. I’ve
since split out and improved the request-handling code and
docs.hhvm.com is now using the library version.