Issues (28)

src/DI/Weather.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace Faxity\DI;
4
5
use Anax\Commons\ContainerInjectableInterface;
6
use Anax\Commons\ContainerInjectableTrait;
7
use Faxity\Fetch\Fetch;
8
9
/**
10
 * DI module for weather forecasts
11
 */
12
class Weather implements ContainerInjectableInterface
13
{
14
    use ContainerInjectableTrait;
15
16
    /** The base URL of ipstack's api. */
17
    private const DARKSKY_URL = 'https://api.darksky.net';
18
19
    /** Regex to match a coordinate */
20
    private const COORDS_REGEX = '/^-?\d+(\.\d+)?, ?-?\d+(\.\d+)?$/';
21
22
    /**
23
     * @var string $accessKey The ipstack access key.
24
     * @var Fetch $http The http fetch client
25
     */
26
    private $accessKey;
27
    private $http;
28
29
30
    /**
31
     * @param string $accessKey access key to ipstacks API
32
     * @param Fetch|null $fetch Fetch client (optional)
33
     */
34 7
    public function __construct(string $accessKey, ?Fetch $fetch = null)
35
    {
36 7
        $this->accessKey = $accessKey;
37 7
        $this->http = $fetch ?? new Fetch();
38 7
    }
39
40
41
42
    /**
43
     * Formats temperature, removes prevents -0.
44
     * @param float $temp Temperature in celcius
45
     *
46
     * @return string
47
     */
48 4
    private function formatTemp(float $temp) : string
49
    {
50 4
        $temp = round($temp);
51 4
        $temp = $temp == 0 ? 0 : $temp;
52
53 4
        return "{$temp}°C";
54
    }
55
56
57
    /**
58
     * Formats an item from DarkSky API, only extracting the data we want.
59
     * @param object $data Item holding the data
60
     * @param \DateTimeZone $tz Timezone of the location
61
     */
62 4
    private function formatWeatherItem(object $data, \DateTimeZone $tz) : object
63
    {
64 4
        $date = new \DateTime("@{$data->time}");
65 4
        $date->setTimezone($tz);
66
67
        return (object) [
68 4
            "date"    => $date->format("d M, Y"),
69 4
            "summary" => $data->summary ?? "",
70 4
            "icon"    => $data->icon,
71 4
            "minTemp" => $this->formatTemp($data->temperatureMin),
72 4
            "maxTemp" => $this->formatTemp($data->temperatureMax),
73
        ];
74
    }
75
76
77
    /**
78
     * Fetches data from the DarkSky API and formats it.
79
     * @param array $requests URLs to send requests to.
80
     */
81 5
    private function fetchForecast(array $requests) : array
82
    {
83 5
        $bodies = $this->http->getMulti($requests);
84
85
        // Merge the data of the requests
86
        return array_reduce($bodies, function ($acc, $body) {
87
            // If the exceeded its use, throw error
88 5
            if (isset($body->code)) {
89 1
                throw new \Exception($body->error);
90
            }
91
92 4
            $tz = new \DateTimeZone($body->timezone);
93 4
            $data = $body->daily->data;
94
95
            // Format each requests daily data
96
            $items = array_map(function ($item) use ($tz) {
97 4
                return $this->formatWeatherItem($item, $tz);
98 4
            }, $data);
99
100 4
            return array_merge($acc, $items);
101 5
        }, []);
102
    }
103
104
105 7
    public function forecast(string $location, bool $pastMonth = false) : object
106
    {
107 7
        if (preg_match(self::COORDS_REGEX, $location)) {
108
            // Split and trim user input
109
            $coords = array_map(function ($str) {
110 5
                return floatval(trim($str));
111 5
            }, explode(",", $location));
112
        } else {
113 2
            $coords = $this->di->ip->locate($location);
0 ignored issues
show
Accessing ip on the interface Psr\Container\ContainerInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
114
115 2
            if (is_null($coords)) {
116 2
                throw new \Exception("Positionen är inte en koordinat eller en ip-address.");
117
            }
118
        }
119
120
        // Unpack coordinates to variables
121 5
        list($lat, $long) = $coords;
122 5
        $url = self::DARKSKY_URL . "/forecast/{$this->accessKey}/{$lat},{$long}";
123
        $params = (object) [
124 5
            "lang"    => "sv",
125
            "units"   => "si",
126
            "exclude" => "currently,minutely,hourly,flags",
127
        ];
128
129 5
        if ($pastMonth) {
130 1
            $now = time();
131
            $requests = array_map(function ($n) use ($now, $url, $params) {
132 1
                $timestamp = $now - ($n * 24 * 60 * 60);
133
134
                return [
135 1
                    "url" => $url . ",{$timestamp}",
136 1
                    "params" => $params,
137
                ];
138 1
            }, range(1, 30));
139
140 1
            $data = $this->fetchForecast($requests);
141
        } else {
142 4
            $data = $this->fetchForecast([
143 4
                [ "url" => $url, "params" => $params ],
144
            ]);
145
        }
146
147
        return (object) [
148 4
            "coords" => $coords,
149 4
            "data" => $data,
150
        ];
151
    }
152
}
153