Using callbacks to prevent code duplication
Tags: php, webdev
Published: 2022-04-11 11:57:25

Sometimes it can be useful to use a callback function or other `callable` to prevent a bunch of code duplication.

Let's say you have an event subscriber that encrypts and decrypts data in the life cycle of an entity. When the entity is stored, some data will be encrypted; and when the entity is loaded, the data will be decrypted.

Here is a maybe somewhat contrived but minimal example of this:

```php
final class EncryptionSubscriber
{
    public function __construct(private CryptographerInterface $cryptographer) { }
    
    private function getEncryptedProperties(Entity $entity): array {
        // some code to detect the encrypted properties of the entity.
        return ['encrypted'];
    }

    public function onSave(Event $event): void {
        $entity = $event->getEntity();
        
        foreach ($this->getEncryptedProperties($entity) as $property) {
            $value = $entity->getValue($property);
            $entity->setValue($property, $this->cryptographer->encrypt($value));
        }
    }
    
    public function onLoad(Event $event): void {
        $entity = $event->getEntity();

        foreach ($this->getEncryptedProperties($entity) as $property) {
            $value = $entity->getValue($property);
            $entity->setValue($property, $this->cryptographer->decrypt($value));
        }
    }
}

```

While this isn't that bad, the `onSave` and `onLoad` are practically the same method; only the content of `setValue()` is different. And to be honest, with two tiny functions like these; I might not bother. But it often happens that three or even more methods are the same. Or the functions are way more elaborate; while they still only differ by a tiny thing.

## Extracting a `callback` helper

A solution to this duplication can be to extract one helper method that contains all the logic once. But at the point where the logic is different we call a `callable` which is injected. The original `onSave` and `onLoad` will then defer to that helper method, and provide the `callable` to handle the specific difference.

```php
final class EncryptionSubscriber
{
    // ...

    public function onSave(Event $event): void
    {
        $this->processEvent($event, fn($value) => $this->cryptographer->encrypt($value));
    }

    public function onLoad(Event $event): void
    {
        $this->processEvent($event, fn($value) => $this->cryptographer->decrypt($value));
    }

    private function processEvent(Event $event, callable $callback): void
    {
        $entity = $event->getEntity();
        foreach ($this->getEncryptedProperties($entity) as $property) {
            $value = $entity->getValue($property);
            $entity->setValue($property, $callback($value)); // [tl! highlight]
        }
    }
}
```

As you can see, we added a `processEvent()` method that also receives a `callable $callback`. This callback is executed with the current value instead of the previous `encrypt` or `decrypt` calls. This makes the entire method a very generic implementation. Only the specific difference is handled by the callback.

If the callback needs more context to do its job, that can be added as additional parameters on the `$callback()` call.

While the `onSave` and `onLoad` still have some necessary duplication; the amount is way less and the difference is easier to spot. And with the help of some shorthand `fn` it's very readable.

## Thoughts and comments?
Like I said, there is a time and place for things like this. If the duplication is minimal; you might not need to bother with it. But I think it's a nice tool to know. Do you have other nice examples where this would be useful? Please share it with the rest of us. And if you have any other comments, drop them below.