DEV Community

Alessandro D'Orazio
Alessandro D'Orazio

Posted on

Awesome Laravel: shift from legacy code to modern ✨

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');
});
Enter fullscreen mode Exit fullscreen mode

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'
}]
Enter fullscreen mode Exit fullscreen mode

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');
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

🎯 Goals achieved
✅ Reusability.

--

You can read the other steps (including a bonus step) here

Latest comments (0)