Lesson 5
Backward Compatibility Practice with PHP
Backward Compatibility: Practice

Welcome back! Today, we'll master what we learned about backward compatibility in practice. Prepare to apply all the knowledge to practical tasks, but first, let's look at two examples and analyze them.

Task 1: Enhancing a Complex Data Processing Function with Optional Parameters and Default Values

Let's say that initially, we have a complex data processing class designed to operate on an array of associative arrays, applying a transformation that converts all string values within the array to uppercase. Here's the initial version using PHP:

php
1<?php 2class DataProcessor 3{ 4 public function processData(array $items) 5 { 6 $processedItems = []; 7 foreach ($items as $item) { 8 $processedItem = []; 9 foreach ($item as $key => $value) { 10 if (is_string($value)) { 11 $processedItem[$key] = strtoupper($value); 12 } else { 13 $processedItem[$key] = $value; 14 } 15 } 16 $processedItems[] = $processedItem; 17 } 18 19 for ($i = 0; $i < min(3, count($processedItems)); $i++) { 20 echo "Processed Item: "; 21 print_r($processedItems[$i]); 22 } 23 } 24}

We intend to expand this functionality, adding capabilities to filter the items based on a condition and allowing for custom transformations. The aim is to retain backward compatibility while introducing these enhancements. Here's the updated approach using optional parameters and closures:

php
1<?php 2class DataProcessor 3{ 4 public function processData(array $items, callable $condition = null, callable $transform = null) 5 { 6 if ($condition === null) { 7 $condition = function ($item) { 8 return true; 9 }; 10 } 11 12 $processedItems = []; 13 foreach ($items as $item) { 14 if ($condition($item)) { 15 $processedItem = []; 16 17 if ($transform !== null) { 18 $processedItem = $transform($item); 19 } else { 20 // Default transformation: Convert string values to uppercase 21 foreach ($item as $key => $value) { 22 $processedItem[$key] = is_string($value) ? strtoupper($value) : $value; 23 } 24 } 25 26 $processedItems[] = $processedItem; 27 } 28 } 29 30 for ($i = 0; $i < min(3, count($processedItems)); $i++) { 31 echo "Processed Item: "; 32 print_r($processedItems[$i]); 33 } 34 } 35} 36 37// Usage examples: 38$data = [ 39 ["name" => "apple", "quantity" => 10], 40 ["name" => "orange", "quantity" => 5] 41]; 42 43$processor = new DataProcessor(); 44 45// Default behavior - convert string values to uppercase 46$processor->processData($data); 47 48// Custom filter - select items with a quantity greater than 5 49$processor->processData($data, function ($item) { 50 return $item['quantity'] > 5; 51}); 52 53// Custom transformation - convert names to uppercase and multiply the quantity by 2 54$processor->processData($data, null, function ($item) { 55 return [ 56 'name' => strtoupper($item['name']), 57 'quantity' => $item['quantity'] * 2 58 ]; 59});

In this evolved version, we've used optional parameters callable $condition and callable $transform for custom filtering and transformation of items. The default behavior processes all items, converting string values to uppercase, thus maintaining original functionality for existing code paths.

Task 2: Using the Adapter Design Pattern for Backward Compatibility

Imagine now that we are building a music player, and recently, market demands have grown. Now, users expect support not just for MP3 and WAV but also for FLAC files within our music player system. This development poses a unique challenge: How do we extend our music player's capabilities to embrace this new format without altering its established interface?

Let's say that we currently have a MusicPlayer class that can only play MP3 files:

php
1<?php 2class MusicPlayer 3{ 4 public function play($file) 5 { 6 if (substr($file, -4) === '.mp3') { 7 echo "Playing " . $file . " as mp3.\n"; 8 } else { 9 echo "File format not supported.\n"; 10 } 11 } 12}

Let's approach this challenge by introducing an adapter that encapsulates different formats and extensions modularly and maintainably:

php
1<?php 2class MusicPlayerAdapter 3{ 4 private $player; 5 private $formatAdapters; 6 7 public function __construct(MusicPlayer $player) 8 { 9 $this->player = $player; 10 $this->formatAdapters = [ 11 '.wav' => function($file) { 12 $convertedFile = str_replace('.wav', '.mp3', $file); 13 echo "Converting $file to $convertedFile and playing as mp3...\n"; 14 $this->player->play($convertedFile); 15 }, 16 '.flac' => function($file) { 17 $convertedFile = str_replace('.flac', '.mp3', $file); 18 echo "Converting $file to $convertedFile and playing as mp3...\n"; 19 $this->player->play($convertedFile); 20 } 21 ]; 22 } 23 24 public function play($file) 25 { 26 $fileExtension = strtolower(pathinfo($file, PATHINFO_EXTENSION)); 27 $fileExtension = '.' . $fileExtension; 28 29 if (isset($this->formatAdapters[$fileExtension])) { 30 $this->formatAdapters[$fileExtension]($file); 31 } else { 32 $this->player->play($file); 33 } 34 } 35} 36 37// Upgraded music player with enhanced functionality through the composite adapter 38$legacyPlayer = new MusicPlayer(); 39$enhancedPlayer = new MusicPlayerAdapter($legacyPlayer); 40 41$enhancedPlayer->play("song.mp3"); // Supported directly 42$enhancedPlayer->play("song.wav"); // Supported through adaptation 43$enhancedPlayer->play("song.flac"); // Newly supported through additional adaptation

This adaptation ensures that we can extend the MusicPlayer to include support for additional file formats without altering its original code. The MusicPlayerAdapter acts as a unified interface to the legacy MusicPlayer, handling formats by determining the appropriate strategy based on the file type.

Lesson Summary

Great job! You've delved into backward compatibility while learning how to utilize optional parameters, default values, and the Adapter Design Pattern in PHP. Get ready for some hands-on practice to consolidate these concepts! Remember, practice makes perfect. Happy Coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.