In this post, we will refactor a legacy, badly written code, in order to see an example of refactoring. As an example, we will refactor a function that generates a dynamic image from an array containing data about text to write, rectangles to draw, and images to insert.
We will send using a POST request:
- An array of options containing the background color and the horizontal margin;
- An array of items, for example, text or images.
Route::get('/', function() {
$options = request()->post('options', [
'bg_color' => '#1e2565',
'margin_x' => 200
]);
$img = Image::canvas(1200, 675, $options['bg_color']);
$marginX = $options['margin_x'];
$availableWidth = 1200 - $marginX;
$items = request()->post('items', []);
foreach($items as $item) {
if($item['type'] === 'text' && isset($item['multiline']) && $item['multiline'] === true) {
$perLineChars = $availableWidth / $item['size'] * 2.1;
$exploded = explode(' ', $item['text']);
$lines = [];
$line = '';
foreach($exploded as $word) {
if(strlen($line) + strlen($word) < $perLineChars) {
$line .= $word . ' ';
} else {
$lines[] = $line;
$line = $word . ' ';
}
}
$lines[] = $line;
$currentY = $item['y'];
$spaceBetweenLines = $item['size'] + ($item['space_between_lines'] ?? 15);
foreach($lines as $line) {
$img->text($line, $item['x'], $currentY, function($font) use ($item) {
$font->file(base_path($item['font']));
$font->size($item['size']);
$font->color($item['color']);
$font->align($item['align']);
$font->valign($item['valign']);
});
$currentY += $spaceBetweenLines;
}
}
if($item['type'] === 'text' && (!isset($item['multiline']) || $item['multiline'] === false)) {
$img->text($item['text'], $item['x'], $item['y'], function($font) use ($item) {
$font->file(base_path($item['font']));
$font->size($item['size']);
$font->color($item['color']);
$font->valign($item['valign']);
$font->align($item['align']);
});
}
if($item['type'] === 'rectangle') {
$img->rectangle($item['x1'], $item['y1'], $item['x2'], $item['y2'], function($draw) use ($item) {
$draw->background($item['bg_color']);
});
}
if($item['type'] === 'image') {
$newImg = Image::make($item['base64']);
if(isset($item['widen'])) {
$newImg->widen($item['widen']);
}
$img->insert($newImg, $item['position'], $item['x'], $item['y']);
}
}
return $img->encode('data-url');
});
As you can see, is hard to read, but we can improve it. The refactoring’s purpose is to make the code not only readable but more reliable and efficient.
Example of item array
[{
'type': 'rectangle',
'x1': 100,
'y1': 490,
'x2': 1100,
'y2': 495,
'bg_color': '#e4f1ff',
},
{
'type': 'text',
'text': 'Alessandro',
'x': 160,
'y': 572,
'size': 36,
'space_between_lines': 15,
'color': '#e4f1ff',
'align': 'left',
'valign': 'bottom',
'font': '/public/Lato-Bold.ttf'
}]
Note: I will omit imports, but consider that we are using the Laravel Intervention package to handle the image generation.
Step 1: Extract the logic
As the first step, we can create a new class, called ImageGenerator to generate a new image. So, we create the class and we do a simple copy-paste. This is good because we can generate a new image from outside the routing function without copy-paste the code every time.
class ImageGenerator extends Image {
public static function generateImage($options, $items) {
$img = Image::canvas(1200, 675, $options['bg_color']);
$marginX = $options['margin_x'];
$availableWidth = 1200 - $marginX;
foreach($items as $item) {
if($item['type'] === 'text' && isset($item['multiline']) && $item['multiline'] === true) {
$perLineChars = $availableWidth / $item['size'] * 2.1;
$exploded = explode(' ', $item['text']);
$lines = [];
$line = '';
foreach($exploded as $word) {
if(strlen($line) + strlen($word) < $perLineChars) {
$line .= $word . ' ';
} else {
$lines[] = $line;
$line = $word . ' ';
}
}
$lines[] = $line;
$currentY = $item['y'];
$spaceBetweenLines = $item['size'] + ($item['space_between_lines'] ?? 15);
foreach($lines as $line) {
$img->text($line, $item['x'], $currentY, function($font) use ($item) {
$font->file(base_path($item['font']));
$font->size($item['size']);
$font->color($item['color']);
$font->align($item['align']);
$font->valign($item['valign']);
});
$currentY += $spaceBetweenLines;
}
}
if($item['type'] === 'text' && (!isset($item['multiline']) || $item['multiline'] === false)) {
$img->text($item['text'], $item['x'], $item['y'], function($font) use ($item) {
$font->file(base_path($item['font']));
$font->size($item['size']);
$font->color($item['color']);
$font->valign($item['valign']);
$font->align($item['align']);
});
}
if($item['type'] === 'rectangle') {
$img->rectangle($item['x1'], $item['y1'], $item['x2'], $item['y2'], function($draw) use ($item) {
$draw->background($item['bg_color']);
});
}
if($item['type'] === 'image') {
$newImg = Image::make($item['base64']);
if(isset($item['widen'])) {
$newImg->widen($item['widen']);
}
$img->insert($newImg, $item['position'], $item['x'], $item['y']);
}
}
return $img->encode('data-url');
}
}
Now, we have to call it into the routing function
Route::get('/', function() {
$options = request()->post('options', [
'bg_color' => '#1e2565',
'margin_x' => 200
]);
$items = request()->post('items', []);
return ImageGenerator::generateImage($options, $items);
});
🎯 Goals achieved
✅ Reusability.
--
You can read the other steps (including a bonus step) here
Top comments (0)