Skip to content

Commit

Permalink
New Post: Decorator pattern in open source code
Browse files Browse the repository at this point in the history
  • Loading branch information
eerison committed Sep 27, 2024
1 parent 1d373ea commit 8867ada
Showing 1 changed file with 136 additions and 0 deletions.
136 changes: 136 additions & 0 deletions src/content/blog/decorator-pattern-in-open-source-code.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
---
author: Erison Silva
pubDatetime: 2024-09-27T10:00:00Z
title: Decorator pattern in open source code
slug: decorator-pattern-in-open-source-code
featured: true
draft: false
tags:
- pattern
- developer
description: How Decorator pattern can help us modify some functionalities from open source code.
---

In some open source repositories that contain a **small** number of maintainers are common to see classes as final, it happens because they can make modification without complex *Backward Compatibility Promise*, But on the other hand it keeps code a bit "complicated" to modify!

But well it is not so complicated since they implements interfaces and use *composition* in the code 😁, and here starts the beneficial of [Decorator pattern][decorator-pattern] 💅.


## Open source code

I was looking for some project that has a good number of users, Then I choose [EasyAdminBundle][easy-admin-bundle] as example, it has more then **+18k users** and **4k starts**

the piece of code that I gonna use is [AdminUrlGenerator.php][admin-url-generator], and we gonna add a dispatch event when the [generate method][admin-url-generator-method] is called.

## Creating a decorator

As the AdminUrlGenerator.php is a final class, it is not possible to extends this class and overite `generate` method, and in this case an alternative is use a decorator.

```php
<?php

declare(strict_types = 1);

namespace App\Decorator;

use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use App\Event\UrlGeneratedEvent;

final class AdminUrlGeneratorDecorator implements AdminContextProvider
{
public function __construct(
private AdminUrlGenerator $adminUrlGenerator,
private EventDispatcherInterface $event,
) {
}

public function generateUrl(): string
{
$url = $this->adminUrlGenerator->generate();
$this->event->dispatch(new UrlGeneratedEvent($url));

return $url;
}

public function setRoute(string $routeName, array $routeParameters = []): self
{
$this->adminUrlGenerator->setRoute($routeName, $routeParameters);

return $this;
}

public function get(string $paramName): mixed
{
return $this->adminUrlGenerator->get($paramName);
}

//....
}
```

## Breaking Down AdminUrlGeneratorDecorator class

```php
<?php

final class AdminUrlGeneratorDecorator implements AdminContextProvider
```

Here we are creating the decorator and implementing the same interface used in AdminUrlGenerator class, it is important because this class is injected via `__construct` on ClassXYZ via [AdminContextProvider][admin-context-provider-interface], and as both class implements the same interface we can choose which class we want to in inject and in our case we gonna inject the new class created (AdminUrlGeneratorDecorator)

```php
<?php

public function __construct(
private AdminUrlGenerator $adminUrlGenerator,
private EventDispatcherInterface $event,
) {
}
```

- `AdminUrlGenerator` is the original class that we want to modify the behavour
- `EventDispatcherInterface` is the Symofony event class, we gonna use this inside of method that we want to overite.

```php
<?php
public function generateUrl(): string
{
$url = $this->adminUrlGenerator->generate();
$this->event->dispatch(new UrlGeneratedEvent($url));

return $url;
}
```

It is the method that we want to modify, on the first line we are calling the original method to generate the url.
the second line is dispatching our event and in the last one we are returning the url generated by original method.

```php
<?php
public function setRoute(string $routeName, array $routeParameters = []): self
{
$this->adminUrlGenerator->setRoute($routeName, $routeParameters);

return $this;
}

public function get(string $paramName): mixed
{
return $this->adminUrlGenerator->get($paramName);
}

//....
```
Maybe you were asking yourself, "why do I need those methods if I just want to change `generate` method?"

It is needed because [AdminContextProvider][admin-context-provider-interface] is forcing to implement those methods.
and this case we must implement them and call the original implementation, like I did with `setRoute` and `get` methods

> Just to simplify the example I did not add all methods, But you must add all of them.
[decorator-pattern]: https://refactoring.guru/design-patterns/decorator
[easy-admin-bundle]: https://github.com/EasyCorp/EasyAdminBundle
[admin-url-generator]: https://github.com/EasyCorp/EasyAdminBundle/blob/v4.12.0/src/Router/AdminUrlGenerator.php
[admin-url-generator-method]: https://github.com/EasyCorp/EasyAdminBundle/blob/v4.12.0/src/Router/AdminUrlGenerator.php#L261
[admin-context-provider-interface]: https://github.com/EasyCorp/EasyAdminBundle/blob/v4.12.0/src/Router/AdminUrlGeneratorInterface.php#L10

0 comments on commit 8867ada

Please sign in to comment.