PHP OOP: Exceptions
November 28, 2020
<?php
class CurlException extends Exception {
public function __construct($curl)
{
$error = curl_error($curl);
curl_close($curl);
$this->message = $error;
}
}
<?php
class HTTPException extends Exception {
public function __construct($data)
{
$data = json_decode($data, true);
$this->message = $data['message'];
$this->code = $data['cod'];
}
}
<?php
class OpenWeather {
const ICON_SIZE = '2x'; // '1x', '2x' or '4x'
const UNITS = 'metric';
private $api_key;
public function __construct(string $var)
{
$this->api_key = $var;
}
public function getCurrent(array $coordinates): array
{
$result = [];
$data = $this->callAPI($coordinates);
if ($data !== null) {
$result = $this->getResult($data["current"], $data["timezone"]);
}
return $result;
}
public function getLastHours(array $coordinates): array
{
$result = [];
$data = $this->callAPI($coordinates);
if ($data !== null) {
foreach ($data["hourly"] as $hour) {
$result[] = $this->getResult($hour, $data["timezone"]);
}
}
return $result;
}
public static function getName(string $location): array
{
$parts = explode('_', $location);
$loc_array['city'] = implode( '-', array_map( 'ucfirst', explode('-', $parts[0]) ) );
$loc_array['country'] = strtoupper($parts[1]);
return $loc_array;
}
private function callAPI(array $coordinates): ?array
{
$time = time()-1;
$latitude = $coordinates[0];
$longitude = $coordinates[1];
$unit = self::UNITS;
$curl = curl_init("https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={$latitude}&lon={$longitude}&dt=$time&units={$unit}&appid={$this->api_key}");
curl_setopt_array($curl, [
CURLOPT_CAINFO => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'openweather.cer',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => 3500
]);
$data = curl_exec($curl);
if ($data === false) {
throw new CurlException($curl);
}
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code !== 200) {
curl_close($curl);
throw new HTTPException($data);
}
curl_close($curl);
return json_decode($data, true);
}
private function getResult($data_when, $timezone): array
{
if (self::ICON_SIZE === '2x' || self::ICON_SIZE === '4x') {
$icon_size = '@' . self::ICON_SIZE;
} else {
$icon_size = '';
}
return [
'temp' => $data_when["temp"] . '°C',
'description' => ucfirst($data_when["weather"][0]["description"]),
'icon' => 'http://openweathermap.org/img/wn/' . $data_when["weather"][0]["icon"] . $icon_size . '.png',
'time' => $this->getTimeFormated($data_when["dt"], $timezone),
];
}
private function getTimeFormated($timestamp, $timezone): array
{
$date = new DateTime("@" . $timestamp);
$date->setTimeZone(new DateTimeZone($timezone));
return [
'all' => $date->format('M d g:i a'),
'date' => $date->format('M d'),
'hour' => $date->format('g a'),
];
}
}
<?php
$locations = [
'paris_fr' => [48.85, 2.35],
'new-york_us' => [40.71, -74.00],
'kerikeri_nz' => [-35.22, 173.94]
];
$title = 'Weather forecast';
require_once 'includes/header.php';
require_once 'class/OpenWeather.php';
require_once 'class/Exceptions/CurlException.php';
require_once 'class/Exceptions/HTTPException.php';
$alert_class = $error = null;
$weather = new OpenWeather('a4f34eb4cec4aa5200aa8b415963e297c');
try {
foreach ($locations as $location => $coordinates) {
$locations_current[$location] = $weather->getCurrent($coordinates);
$locations_lastHours[$location] = $weather->getLastHours($coordinates);
}
} catch (CurlException $e) {
$error = '<b>API error info:</b> ' . $e->getMessage() . '.';
$alert_class = 'danger';
} catch (HTTPException $e) {
$error = '<b>' . $e->getCode() . ' Error status:</b> ' . $e->getMessage();
$alert_class = 'primary';
}
if($error) {
$error = '<b>Oops, this was not supposed to happen.</b>
<div class="small mb-2">' . $error . '</div>
Please try again later or <a href="contact.php">contact us</a>.';
$error_sub = '<a href="index.php">Go back to homepage</a>';
} ?>
<p class="lead mb-5">The list below has been made through the OpenWeather API. I links the API to this website thanks to the curl extension for PHP and following <a href="/lessons/20201127_php_Use%20an%20API%20with%20cURL/">this lesson</a>.</p>
<?php if($error): ?>
<div class="alert <?= $alert_class ? 'alert-'.$alert_class : '' ?>"><?= $error ?></div>
<div><?= $error_sub ?></div>
<?php else: ?>
<div class="row">
<?php foreach ($locations_current as $location => $data): ?>
<?php $title = OpenWeather::getName($location) ?>
<div class="col-md-<?= count($locations) === 4 ? '3' : '4' ?> mb-5">
<div class="card">
<div class="card-body text-center">
<h4><?= $title['city'] . ' <span class="small text-muted">(' . $title['country'] . ')</span>' ?></h4>
<div class="small text-muted"><?= $data['time']['all'] ?></div>
<img src="<?= $data['icon']; ?>"/>
<div><?= $data['description'] ?></div>
<div><b><?= $data['temp'] ?></b></div>
</div>
</div>
<div class="mt-3">
<ul class="list-unstyled">
<?php
$lastHours = $locations_lastHours[$location];
for ($i = count($lastHours) - 1; $i >= count($lastHours) - 15; $i -= 2): ?>
<?php if (!empty($lastHours[$i])): ?>
<li class="small text-muted"><?= $lastHours[$i]['time']['hour'] . ' : <b>' . $lastHours[$i]['temp'] . '</b> - ' . $lastHours[$i]['description'] ?></li>
<?php endif; ?>
<?php
endfor; ?>
</ul>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php require 'includes/footer.php'; ?>
<?php
class OpenWeather {
const ICON_SIZE = '2x'; // '1x', '2x' or '4x'
const UNITS = 'metric';
private $api_key;
public function __construct(string $var)
{
$this->api_key = $var;
}
public function getCurrent(array $coordinates): array
{
$result = [];
$data = $this->callAPI($coordinates);
if ($data !== null) {
$result = $this->getResult($data["current"], $data["timezone"]);
}
return $result;
}
public function getLastHours(array $coordinates): array
{
$result = [];
$data = $this->callAPI($coordinates);
if ($data !== null) {
foreach ($data["hourly"] as $hour) {
$result[] = $this->getResult($hour, $data["timezone"]);
}
}
return $result;
}
public static function getName(string $location): array
{
$parts = explode('_', $location);
$loc_array['city'] = implode( '-', array_map( 'ucfirst', explode('-', $parts[0]) ) );
$loc_array['country'] = strtoupper($parts[1]);
return $loc_array;
}
private function callAPI(array $coordinates): ?array
{
$time = time()-1;
$latitude = $coordinates[0];
$longitude = $coordinates[1];
$unit = self::UNITS;
$curl = curl_init("https://api.openweathermap.org/data/2.5/onecall/timemachine?lat={$latitude}&lon={$longitude}&dt=$time&units={$unit}&appid={$this->api_key}");
curl_setopt_array($curl, [
CURLOPT_CAINFO => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'certificates' . DIRECTORY_SEPARATOR . 'openweather.cer',
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT_MS => 4000
]);
$data = curl_exec($curl);
if ($data === false) {
$error = curl_error($curl);
curl_close($curl);
throw new Exception($error);
}
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($code !== 200) {
curl_close($curl);
throw new Exception($data);
}
curl_close($curl);
return json_decode($data, true);
}
private function getResult($data_when, $timezone): array
{
if (self::ICON_SIZE === '2x' || self::ICON_SIZE === '4x') {
$icon_size = '@' . self::ICON_SIZE;
} else {
$icon_size = '';
}
return [
'temp' => $data_when["temp"] . '°C',
'description' => ucfirst($data_when["weather"][0]["description"]),
'icon' => 'http://openweathermap.org/img/wn/' . $data_when["weather"][0]["icon"] . $icon_size . '.png',
'time' => $this->getTimeFormated($data_when["dt"], $timezone),
];
}
private function getTimeFormated($timestamp, $timezone): array
{
$date = new DateTime("@" . $timestamp);
$date->setTimeZone(new DateTimeZone($timezone));
return [
'all' => $date->format('M d g:i a'),
'date' => $date->format('M d'),
'hour' => $date->format('g a'),
];
}
}
<?php
$locations = [
'paris_fr' => [48.85, 2.35],
'new-york_us' => [40.71, -74.00],
'kerikeri_nz' => [-35.22, 173.94]
];
$title = 'Weather forecast';
require_once 'includes/header.php';
require_once 'class/OpenWeather.php';
$error = null;
$weather = new OpenWeather('4f34eb4cec4aa5200aa8b415963e297c');
try {
foreach ($locations as $location => $coordinates) {
$locations_current[$location] = $weather->getCurrent($coordinates);
$locations_lastHours[$location] = $weather->getLastHours($coordinates);
}
} catch (Exception $e) {
$message_line = $e->getMessage();
if (!empty($message_line)) {
if ((substr($message_line, 0, 1) === '{' && substr($message_line, -1, 1))) {
$message_array = json_decode($message_line, true);
$message = 'Code ' . $message_array['cod'] . ': ' . $message_array['message'];
} else {
$message = $e->getMessage();
}
} else {
$message = 'no details';
}
$error = "An error occured, please try again later.
<div class='small'>$message</div>";
} ?>
<p class="lead mb-5">The list below has been made through the OpenWeather API. I links the API to this website thanks to the curl extension for PHP and following <a href="/lessons/20201127_php_Use%20an%20API%20with%20cURL/">this lesson</a>.</p>
<?php if($error): ?>
<div class="alert alert-danger"><?= $error ?></div>
<?php else: ?>
<div class="row">
<?php foreach ($locations_current as $location => $data): ?>
<?php $title = OpenWeather::getName($location) ?>
<div class="col-md-<?= count($locations) === 4 ? '3' : '4' ?> mb-5">
<div class="card">
<div class="card-body text-center">
<h4><?= $title['city'] . ' <span class="small text-muted">(' . $title['country'] . ')</span>' ?></h4>
<div class="small text-muted"><?= $data['time']['all'] ?></div>
<img src="<?= $data['icon']; ?>"/>
<div><?= $data['description'] ?></div>
<div><b><?= $data['temp'] ?></b></div>
</div>
</div>
<div class="mt-3">
<ul class="list-unstyled">
<?php
$lastHours = $locations_lastHours[$location];
for ($i = count($lastHours) - 1; $i >= count($lastHours) - 15; $i -= 2): ?>
<?php if (!empty($lastHours[$i])): ?>
<li class="small text-muted"><?= $lastHours[$i]['time']['hour'] . ' : <b>' . $lastHours[$i]['temp'] . '</b> - ' . $lastHours[$i]['description'] ?></li>
<?php endif; ?>
<?php
endfor; ?>
</ul>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php require 'includes/footer.php'; ?>
$class Exception
<?php
function add($a, $b)
{
if (!is_numeric($a) | !is_numeric($b))
{
throw new Exception('Both parameters must be numbers'); // A new exception is thrown if one of the two parameters is not a number.
}
return $a + $b;
}
try // We will try to perform the instructions located in this block.
{
echo add(12, 3), '<br />';
echo add('azerty', 54), '<br />';
echo add(4, 8);
}
catch (Exception $e) // We will catch the "Exception" exceptions if one is raised
{
echo 'An exception was thrown. Error message: ', $e->getMessage();
}
if(error condition here) { throw new Exception('Custom error message here.'); }
try { // paste here the code that may have unpredictable behavior, like returning an error }
catch(Exception $e) { // do something }
(By convention, the variable for the exception is $e).$e->getMessage()
: displays the error message present in the instantiation of the exception (step 1 with throw
)$e->getCode()
: displays PHP error codeclass CustomException extends Exception
class CustomException extends Exception { }
__construct()
: see PHP Doc for details on parameters to insert. Always use a custom error description (\$description). Sometimes the error code will be added as a 2nd parameter.)
new CustomException($description, $code);
__construct()
method in CustomException.php, and define the parameters according to the PHP doc. In the throw, create an instance without defining parameters: new CustomException;
class Error
try {
// List here actions that may lead to errors
}
catch (Error $e) {
$error = $e->getMessage();
// Do some stuff if a basic PHP error occurs
// (in this example, we fill the $error variable with a custom error message)
}
declare(strict_types=1);
catch (Exception | Error $e) { // do some stuff }