3.1. Adapter¶
3.1.1. Intent¶
According to the Gang of Four, the Adapter pattern is a way to “convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.” (Design Patterns: Elements of Reusable Object-Oriented Software, 2013, p. 139).
3.1.2. When to use it?¶
Use the Adapter pattern if you are are concerned about the integration of incompatible components / libraries, or if you face an existing class that doesn’t match the interface you need.
Note that the pattern also allows to decouple the client code from the adaptee implementations.
3.1.4. Implementation¶
FirstWeatherApi.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <?php
namespace Phpatterns\Structural\Adapter\FirstWeatherApi;
class FirstWeatherApi
{
/**
* Get the weather main metrics for a given city.
* @param string $city
* @return array
* array['temp'] Temperature (°C)
* array['pressure'] Atmospheric pressure (hPa)
* array['humidity'] Humidity (%)
* array['temp_min'] Minimum temperature at the moment (°C)
* array['temp_max'] Maximum temperature at the moment (°C)
*/
public function getMainMetrics($city)
{
// ..
// Do the necessary to call the first Weather API and get data for the given city.
// ..
return [
'temp' => 10.5,
'pressure' => 1016.8,
'humidity' => 91,
'temp_min' => 8.3,
'temp_max' => 12.0,
];
}
}
|
SecondWeatherApi.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 | <?php
namespace Phpatterns\Structural\Adapter\SecondWeatherApi;
class SecondWeatherApi
{
/** @var \SimpleXMLElement */
private $weatherData = null;
/**
* Get the weather data for a given city.
* @param string $city
*/
public function loadWeatherData($city)
{
$id = $this->getIDFromCity($city);
// ...
// Do the necessary to call the second Weather API and get data for the given city ID.
// ...
$xmlResponse = <<<'EOD'
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0">
<channel>
<title>Weather - Paris, FR</title>
<yweather:atmosphere humidity="93" visibility="9.99" pressure="1015.92" rising="2" />
<item>
<title>Conditions for Paris, FR at 10:30 am CET</title>
<yweather:condition text="Partly Cloudy" temp="9" date="Sat, 26 Dec 2015 10:30 am CET" />
</item>
</channel>
</rss>
EOD;
$this->weatherData = simplexml_load_string($xmlResponse);
}
/**
* Retrieve ID for a city.
* (Example : Spatial entities provided by Yahoo! GeoPlanet are referenced by a 32-bit
* identifier: the Where On Earth ID (WOEID))
* Have a look at: https://developer.yahoo.com/geo/geoplanet
* @param $city
* @return int
*/
private function getIDFromCity($city)
{
// ...
// use Yahoo! GeoPlanet API (or another one) to retrieve the ID for $city
// ...
// ID set manually to Paris - France for the example
$id = 615702;
return $id;
}
/**
* Get the temperature for the loaded city
* @return float | null if no data
*/
public function getTemperature()
{
if ($this->weatherData) {
$conditions = $this->weatherData->channel->item->xpath('yweather:condition');
return (float) $conditions[0]->attributes()->temp;
}
return null;
}
/**
* Get the humidity for the loaded city
* @return float | null if no data
*/
public function getHumidity()
{
if ($this->weatherData) {
$atmosphere = $this->weatherData->channel->xpath('yweather:atmosphere');
return (float) $atmosphere[0]->attributes()->humidity;
}
return null;
}
/**
* Get the pressure for the loaded city
* @return float | null if no data
*/
public function getPressure()
{
if ($this->weatherData) {
$atmosphere = $this->weatherData->channel->xpath('yweather:atmosphere');
return (float) $atmosphere[0]->attributes()->pressure;
}
return null;
}
}
|
WeatherInterface.php
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?php
namespace Phpatterns\Structural\Adapter;
interface WeatherInterface
{
/**
* Get the weather for a given city (temperature, humidity and pressure only).
* @param string $city
* @return Weather
*/
public function getWeather($city);
}
|
FirstWeatherApiAdapter.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace Phpatterns\Structural\Adapter\FirstWeatherApi;
use Phpatterns\Structural\Adapter;
class FirstWeatherApiAdapter implements Adapter\WeatherInterface
{
/** @var FirstWeatherApi */
private $firstWeatherApi;
public function __construct(FirstWeatherApi $firstWeatherApi)
{
$this->firstWeatherApi = $firstWeatherApi;
}
/**
* Get the weather for a given city.
* @param string $city
* @return Adapter\Weather
*/
public function getWeather($city)
{
$mainMetrics = $this->firstWeatherApi->getMainMetrics($city);
return new Adapter\Weather(
(float) $mainMetrics['temp'],
(float) $mainMetrics['humidity'],
(float) $mainMetrics['pressure']
);
}
}
|
SecondWeatherApiAdapter.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <?php
namespace Phpatterns\Structural\Adapter\SecondWeatherApi;
use Phpatterns\Structural\Adapter;
class SecondWeatherApiAdapter implements Adapter\WeatherInterface
{
/** @var SecondWeatherApi */
private $secondWeatherApi;
public function __construct(SecondWeatherApi $secondWeatherApi)
{
$this->secondWeatherApi = $secondWeatherApi;
}
/**
* Get the weather for a given city.
* @param string $city
* @return Adapter\Weather
*/
public function getWeather($city)
{
$this->secondWeatherApi->loadWeatherData($city);
return new Adapter\Weather(
$this->secondWeatherApi->getTemperature(),
$this->secondWeatherApi->getHumidity(),
$this->secondWeatherApi->getPressure()
);
}
}
|
Weather.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | <?php
namespace Phpatterns\Structural\Adapter;
class Weather
{
/** @var float */
private $temperature;
/** @var float */
private $humidity;
/** @var float */
private $pressure;
public function __construct($temperature, $humidity, $pressure)
{
$this->temperature = $temperature;
$this->humidity = $humidity;
$this->pressure = $pressure;
}
}
|
3.1.5. Tests¶
AdapterTest.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | <?php
namespace Test\Phpatterns\Structural\Adapter;
use Phpatterns\Structural\Adapter;
use Phpatterns\Structural\Adapter\FirstWeatherApi;
use Phpatterns\Structural\Adapter\SecondWeatherApi;
class AdapterTest extends \PHPUnit_Framework_TestCase
{
/**
* Testing the adapter mechanism
* @param Adapter\WeatherInterface $weather
* @dataProvider adapterProvider
*/
public function testAdapterMechanism(Adapter\WeatherInterface $weather)
{
$this->assertInstanceOf(
Adapter\Weather::class,
$weather->getWeather('Paris')
);
}
public function adapterProvider()
{
return [
[
new FirstWeatherApi\FirstWeatherApiAdapter(
new FirstWeatherApi\FirstWeatherApi()
)
],
[
new SecondWeatherApi\SecondWeatherApiAdapter(
new SecondWeatherApi\SecondWeatherApi()
)
]
];
}
}
|