Asserts With Flow Annotations in PHP7

Recently I switched from developing React Native apps back to developing websites. You know, due the corona and stuff. And I miss few things. More than a few things to be honest. But what I miss the most is the tooling, Flow in particular.

Martin Adamko
The Startup
Published in
9 min readNov 8, 2020

--

Flow is a static type checker. It checks the Flow type annotations embeded into Javascript code and bugs you with errors while you code before you even start the engines. That’s why it’s tagline states it’s a static type checker for JavaScript.

For the sake of brevity of reading from now on whenever I refer to Flow I mean code with Flow-flavoured annotations taking TypeScript into account. TypeScript and Flow have quite similar in type annotations at least for the basic primitive types. After transpilation of such code to vanilla code you can hit the road with some type safety backed into your coding experience (not the runtime).

I want my Flow back

Now, the situation in the PHP world is different. Type annotations for the function arguments are called hints and don’t get me wrong. They are great but not able to catch the nuances.

I know, there is lots of improvements in type hinting in the era of PHP7 and many more are coming our way with upcoming release of PHP8. But (at least for now) type annotations work just for the function’s arguments coming in.

Also type hints work great for Interfaces and Classes but that would be an overkill for primitive types like strings, numbers or arrays. And you need that basics before you get to fancy classes and stuff.

Let me quickly illustrate where type hinting falls short in PHP. This function will let you pass any array (e.g. an array of objects) even though it is supposed to process array of strings and change them all to lowercase:

You could leave the function as is but you would need to check the $list before passing it to this function. And so we can change it to this:

It’s not an end of the world, also the check can be abstracted away (later in section below), but you certainly don’t want to repeat it for every function that should process arrays of strings and, moreover it’s definitely not as readable as this imaginary function with a imaginary hint string[]:

The string[] is a Flow primitive type annotation for array of strings.

Well this is still not impossible in PHP. (sigh)

Another life-changing experience was when Flow checked my assignments whether the value to be assigned matched expected type. Here’s what that experience looks like with Flow:

As you can see, Flow complains about the literal value 2 not being string as expected by the list constant. Neat. You can try this out for yourself.

Abstraction to the rescue

So we cannot check the assignment before calling the function. We need to check the $list inside the function. Of course we don’t need to write the same logic to check if array items are strings. We can abstract it away, e.g. let's say there will be an is_array_of_strings() function defined somewhere else in our code and it might look like this:

We can later use it to simplify our code into:

It’s not that much of a difference compared to the function with the imaginary string[] hint. It’s readable and we can call it a day.

But let’s not and let me tell you…

A story of PropTypes first

In React you can typecheck the data structures with PropTypes. Interestingly, both Flow and PropTypes were developed in Facebook.

What the PropTypes do is basically an abstraction for checking data but in big scale. It packed full with functions similar to our little array_of_strings_to_lowercase(). So PropTypes is basically another code that checks your cody while your application/program is running (dynamically) in contrary to Flow (type check is done on the code beforehand, statically).

PropTypes are similar in nature to PHP’s assert() in that they are usually turned off in production. (I’m telling you all of this because of this similarity.)

This is an imaginarry prop type definition of a Tweet described using PropTypes and in Flow syntax side by side:

It’s needless to say which definition can be read/written more easily. When the app gets more complex so gets the type definitions more complex and more verbose.

But until Flow came around this was THE approach to check types that existed in React ecosystem for a quite some time. Despite the benefits (and a few shortcomings) of having PropTypes, because you tried to fix a problem (type safety of the components) you inevitably created yet another problem in the end. Most of us will skip writing the PropTypes altogether or whenever the time is just not right (and that happens a lot) which produces code with possibly more bugs.

Moreover, and it’s one of the biggest gotchas, is that PropTypes in React work only inside the components. So why to use any if you cannot use them in other parts of your application?

Flow comes with simple annotation syntax that abstracts away the burden of verbosity of which PropTypes suffer. It’s is easier to write and to read but it’s not code. It gets stripped away in the process of transpilation.

Dynamic analysis is good and bad in the same time. It works with real data of your app, that’s the good part. Almost like tiny unit tests running every time you start your app and call that function. You see the validation at work (or not) and fix it and from that time it can catch any unexpected errors. But it might miss some edge cases depending on how you use your app, which is the bad.

On the other hand static analysis can catch many edge cases but it depends a lot on your skills to define types. It might seem to work and give you false feeling of safety.

With PHP we’re left with the dynamic checks. The edge cases could (and should) be caught with unit tests.

So… can we somehow use the Flow type annotation syntax and make it work under the hood?

Turning Flow syntax into Code

Now let’s imagine the next best thing to having Flow type support available in PHP. No need to write our own validations for basic stuff. Let’s call our magical function is(). It lets you assert Flow type annotation of value:

All we need is to (somehow):

  1. parse the annotation;
  2. turn it into code (to compile it);
  3. test the value.

Parsing annotations (and any syntax in general) is done by walking the syntax word by word and turning it into an Abstract Syntaxt Tree.

This post is already way too long, so let me spare you the trouble. I have published an early version of the tool I’d like to call the Duck Types for PHP and as the name suggest, it’s suitable for type checking of the structure of the data in the spirit of:

If it walks like a duck and talks like a duck, treat it like a duck, even if it’s not a duck — a dynamic typing for PHP inspired by Flow types.

Don’t expect interfaces, classes or anything fancy. Expect to be able to type check your values against:

  • Primitive types like null, undefined, number, numeric, string, int, float, bool, boolean, array, object and * (exists)
  • Literal types forstring, int and float
  • Maybe types marked with ? sign, e.g. ?bool
  • Object types, e.g.{ hello: 'world' }
  • Exact object type, e.g. {| hello: 'world' |}
  • Array types, e.g. string[]
  • Tuple types, e.g. [number, string, 'three']
  • Union types, e.g. int | float | string
  • Intersection types, e.g.{ a: int } & {b : float }
  • Grouping with parentheses, e.g. (int | string)[]

All this should give pretty much of the flexibility to write your code with confidence and with minimum effort. Just learn how to write Flow Annotations.

Everything else lies outside of the scope of Duck Types for PHP.

The result

Here’s how our little example function expecting array of strings would look like:

The Type::is() part parses the Flow annotation (first string argument) and checks if the value (second argument) passes type validation. This alone will halt the program whenever the value of the $list doesn’t match the type. Wrapped in assert() could be easily stripped away to run in production without any performance hit of the asserted expression.

Asserts are a language construct in PHP7 and as such you can easily remove their execution in the run-tine skipping the execution of the code altogether. (You can also define constant to tell Duck Types to skip validations if you don’t want to or cannot use native assert() method.)

PS: My early tests shows that all the parsing and type validation happens in matter of milliseconds or even less.

So, that was a form for usage with theassert(). If you want to pass the value only when it the type is compatible you could inline the check within the Type:pass() method like this:

How you use the Duck Types is up to your likings. You can use it just to compile the Flow annotation into \Closure or to use in asserts or not at all.

Reuse and Composition of types

A type system to be scalable must be:

  • Extensible
  • Composable

Here’s how to register (or redefine any built-in) custom type:

Using the type is as easy as calling one-liner:

Summary

Testing your code is vital to your project health and affects the velocity of your projects. Testing your code should not be hard. Having asserts within your code does not replace unit testing but can help you catch more edge cases.

Duck Types for PHP in addition to using the built-in types allows you to register your own types & reuse them within other type annotations allowing composition of types (nesting within other) and therefor much wider reuse.

With Duck Types your asserts turn to readable and short one-liners.

That’s all folks, let me know what you think in the comments bellow.

Cheers,

Martin

--

--

Martin Adamko
The Startup

One that loves design, illustration, photography, digs in code, adores his dog and enjoys life & good coffee. http://be.net/martin_adamko