Phava, explained, with examples

bunq
bunq Developers’ Corner
5 min readFeb 12, 2020

This January, we gave our first talk about Phava, our little homemade language that we briefly told you about last July. We opened the presentation round at AmsterdamPHP (thank you for having us, guys! ❤).

In this blog post, we’d like to recap some of the previously unshared details of how Phava works at bunq, as shared during the presentation. Visit the next meetup to hear more and ask any questions in real life. 😜

It’s time to sort out how Phava helps to achieve a disaster-proof architecture that every developer can quickly pick up working with at any point.

No ambiguity

You can’t tell if “Luxembourg” represents the city or the country just by looking at it. We use Phava to avoid such ambiguity and make sure the code is obvious to new coders, existing coders working with other’s code, and even product owners.

Phava does not allow public interfaces to return scalar types other than boolean and array. In the Luxembourg problem case, this means that you are not allowed to return a string for $user->getCountry(). This is where Java principles come in, and we use an object.

This way, you always know what the value you are dealing with represents: the city or the country in our case. When passing values to other functions, you can place strict requirements on what is allowed to be passed.

Encompassing names

You can combine objects with extensions and inheritance. For instance, we have a base class `Pointer`, which is abstract and has a `getPointerString` method.

Pointer is extended by all the pointers we support:

  • PointerEmail, which contains the Email object in its constructor;
  • PointerPhoneNumber, which contains the PhoneNumber object in its constructor;
  • PointerIban, which takes in its constructor an Iban and Name object.

Pointers demonstrate our naming convention: it’s always big to small. If you extend a class, the new class inherits its name and followed by a logical description of the extension. As a result, you name it PointerIban, not IbanPointer.

Always on top of the wrong and unexpected

We do not allow ternary expressions like return $this->status === ‘ACTIVE’ ? ‘YES’ : ‘NO’; because they can easily end up unreadable:

What we do instead is use if-statements followed by throwing an exception for the unexpected. This way, we always know that something is wrong and don’t have to wait 3 months to accidentally find out about some unwanted outcomes.

Objects in objects

If we wanted to send a card to a user, we could end up with a function like this:

It might get cumbersome if we pass the parameters to multiple functions and then decide to add another field.

The object-within-object approach helps us avoid inefficiency. Using Phava, we would do the following:

  1. combine the address fields under the Address object;
  2. pass one parameter;
  3. only use the needed values from this object using getters.

Let’s do the same for making a payment. At bunq, we use decrees as fixed collections of other objects, so in this case, we have the following object:

Following the object-within-object approach, we do this:

No magic tricks

About 60% of clear code at bunq is achieved via no magic allowed. If you have used Laravel or Symfony, you would need to forget how to use __get, __call, __callStatic, and the other, now forbidden methods.

If at any point you have to do a var annotation, it’s a sign you’re doing it wrong. This will never get through code review at bunq:

Instead, we’d frame it this way:

Let’s look at another example: The default description for bunq monetary accounts is “bunq account”. You could use $monetaryAccount->setDescription(new Description(‘bunq account’));, however it’s not allowed at bunq. Scalar types cannot be used directly, and so they are constants.

Instead of setting the description of a monetary account, we set it to default and then extract the constant value.

If you need to check if the description of a monetary account is set to a default description, you can reuse this constant:

Mindblowing it looks indeed. 🤯 It would indeed take half of a working day just to define getters and setters for everything, and then to instantiate everything as an object. The real power of our framework lies in our code generators: they automatically generate the code with getters and setters after I define the properties that, say, a User model has.

Models are also responsible for database operations. So, in addition to the model file, we generate an SQL file, a query builder for the database based on the columns, and a query builder for Elasticsearch.

We cannot share example definitions, the generator or generator outputs because it is them that makes it possible for us to move as quickly as we do.

We have, however, shared the best practices and more examples on our GitHub page.

If you’d like to know more, join us at the next meetup we’re attending. Our next stop is at PHPAmersfoort coming March. See you there!

Haven’t tried banking with full API access yet? Do it by just running a command!

Choose your language:

$ bash <(curl -s https://tinker.bunq.com/php/setup.sh)

$ bash <(curl -s https://tinker.bunq.com/python/setup.sh)

$ bash <(curl -s https://tinker.bunq.com/java/setup.sh)

$ bash <(curl -s https://tinker.bunq.com/csharp/setup.sh)

--

--