Skip to content

Commit 8da0a2b

Browse files
MarcEspiardMarc Espiard
andauthored
Add IPStack support and associated tests (#62)
Co-authored-by: Marc Espiard <[email protected]>
1 parent 4f00321 commit 8da0a2b

File tree

6 files changed

+294
-6
lines changed

6 files changed

+294
-6
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ $config = [
108108
];
109109
```
110110

111+
#### IPStack
112+
113+
To use the ipstack as the driver set the config.
114+
115+
Example:
116+
```php
117+
$config = [
118+
'driver' => 'ipstack',
119+
'ipstack' => [
120+
'key' => 'YOUR IPSTACK KEY',
121+
'secure' => true, // (optional) use https
122+
],
123+
];
124+
```
125+
126+
111127
### Laravel
112128

113129
To use this package in Laravel, simply update the config file in `config/geoip.php` to get the same effect. The driver can be set using the `GEOIP_DRIVER` env.

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
"keywords": [
66
"geoip",
77
"ip-api",
8-
"maxmind"
8+
"maxmind",
9+
"ipstack"
910
],
1011
"license": "MIT",
1112
"authors": [

config/geoip.php

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
| GeoIP Driver Type
77
|--------------------------------------------------------------------------
88
|
9-
| Supported: "ip-api", "maxmind_database", "maxmind_api"
9+
| Supported: "ip-api", "maxmind_database", "maxmind_api", "ipstack"
1010
|
1111
*/
1212
'driver' => env('GEOIP_DRIVER', 'ip-api'),
@@ -55,4 +55,14 @@
5555
//local required on client call but defaults to 'en' for english.
5656
'locales' => ['en'],
5757
],
58+
59+
/*
60+
|--------------------------------------------------------------------------
61+
| IPStack Driver
62+
|--------------------------------------------------------------------------
63+
*/
64+
'ipstack' => [
65+
// Get your access key here: https://ipstack.com/product
66+
'key' => env('GEOIP_IPSTACK_KEY'),
67+
],
5868
];

src/Drivers/IpStackDriver.php

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
<?php
2+
3+
namespace PulkitJalan\GeoIP\Drivers;
4+
5+
use GuzzleHttp\Exception\GuzzleException;
6+
use Illuminate\Support\Arr;
7+
use GuzzleHttp\Client as GuzzleClient;
8+
use PulkitJalan\GeoIP\Exceptions\InvalidCredentialsException;
9+
10+
class IpStackDriver extends AbstractGeoIPDriver
11+
{
12+
/**
13+
* @param array $config
14+
* @param GuzzleClient|null $guzzle
15+
* @throws InvalidCredentialsException
16+
*/
17+
public function __construct(array $config, GuzzleClient $guzzle = null)
18+
{
19+
parent::__construct($config, $guzzle);
20+
21+
if (! Arr::get($this->config, 'key')) {
22+
throw new InvalidCredentialsException();
23+
}
24+
}
25+
26+
/**
27+
* Get array of data using ipstack.
28+
*
29+
* @param string $ip
30+
* @return array
31+
* @throws InvalidCredentialsException
32+
* @throws GuzzleException
33+
*/
34+
public function get($ip)
35+
{
36+
$data = $this->getRaw($ip);
37+
38+
if (empty($data) || (Arr::get($data, 'latitude') === 0 && Arr::get($data, 'longitude') === 0)) {
39+
return $this->getDefault();
40+
}
41+
42+
return [
43+
'city' => Arr::get($data, 'city'),
44+
'country' => Arr::get($data, 'country_name'),
45+
'countryCode' => Arr::get($data, 'country_code'),
46+
'latitude' => (float) number_format(Arr::get($data, 'latitude', 0), 5),
47+
'longitude' => (float) number_format(Arr::get($data, 'longitude', 0), 5),
48+
'region' => Arr::get($data, 'region_name'),
49+
'regionCode' => Arr::get($data, 'region_code'),
50+
'timezone' => Arr::get($data, 'time_zone.id'),
51+
'postalCode' => Arr::get($data, 'zip'),
52+
];
53+
}
54+
55+
/**
56+
* Get the raw GeoIP info using ipstack.
57+
*
58+
* @param string $ip
59+
* @return array
60+
* @throws InvalidCredentialsException
61+
* @throws GuzzleException
62+
*/
63+
public function getRaw($ip)
64+
{
65+
$data = json_decode($this->guzzle->get($this->getUrl($ip))->getBody(), true);
66+
67+
if (Arr::get($data, 'success') === false && Arr::get($data, 'error.type') === 'invalid_access_key') {
68+
throw new InvalidCredentialsException();
69+
}
70+
71+
return $data;
72+
}
73+
74+
/**
75+
* Get the ipstack url.
76+
*
77+
* @param string $ip
78+
* @return string
79+
*/
80+
protected function getUrl($ip)
81+
{
82+
$protocol = 'http'.(Arr::get($this->config, 'secure', true) ? 's' : '');
83+
84+
return $protocol.'://api.ipstack.com/'.$ip.'?access_key='.Arr::get($this->config, 'key');
85+
}
86+
}

src/GeoIPManager.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use Illuminate\Support\Str;
77
use GuzzleHttp\Client as GuzzleClient;
88
use PulkitJalan\GeoIP\Drivers\IPApiDriver;
9+
use PulkitJalan\GeoIP\Drivers\IpStackDriver;
910
use PulkitJalan\GeoIP\Drivers\MaxmindApiDriver;
1011
use PulkitJalan\GeoIP\Drivers\AbstractGeoIPDriver;
1112
use PulkitJalan\GeoIP\Drivers\MaxmindDatabaseDriver;
@@ -35,7 +36,7 @@ public function __construct(array $config, GuzzleClient $guzzle = null)
3536
/**
3637
* Get the driver based on config.
3738
*
38-
* @return \PulkitJalan\GeoIP\AbstractGeoIPDriver
39+
* @return AbstractGeoIPDriver
3940
*/
4041
public function getDriver($driver = null): AbstractGeoIPDriver
4142
{
@@ -53,7 +54,7 @@ public function getDriver($driver = null): AbstractGeoIPDriver
5354
/**
5455
* Get the ip-api driver.
5556
*
56-
* @return \PulkitJalan\GeoIP\IPApiDriver
57+
* @return IPApiDriver
5758
*/
5859
protected function createIpApiDriver(array $data): IPApiDriver
5960
{
@@ -63,7 +64,7 @@ protected function createIpApiDriver(array $data): IPApiDriver
6364
/**
6465
* Get the Maxmind driver.
6566
*
66-
* @return \PulkitJalan\GeoIP\MaxmindDriver
67+
* @return MaxmindDatabaseDriver
6768
*/
6869
protected function createMaxmindDatabaseDriver(array $data): MaxmindDatabaseDriver
6970
{
@@ -73,10 +74,20 @@ protected function createMaxmindDatabaseDriver(array $data): MaxmindDatabaseDriv
7374
/**
7475
* Get the Maxmind driver.
7576
*
76-
* @return \PulkitJalan\GeoIP\MaxmindDriver
77+
* @return MaxmindApiDriver
7778
*/
7879
protected function createMaxmindApiDriver(array $data): MaxmindApiDriver
7980
{
8081
return new MaxmindApiDriver($data, $this->guzzle);
8182
}
83+
84+
/**
85+
* Get the ip stack driver.
86+
*
87+
* @return IpStackDriver
88+
*/
89+
protected function createIpStackDriver(array $data): IpStackDriver
90+
{
91+
return new IpStackDriver($data, $this->guzzle);
92+
}
8293
}
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
<?php
2+
3+
use GuzzleHttp\Client;
4+
use GuzzleHttp\Psr7\Response;
5+
use PulkitJalan\GeoIP\Exceptions\GeoIPException;
6+
use PulkitJalan\GeoIP\Exceptions\InvalidCredentialsException;
7+
use PulkitJalan\GeoIP\GeoIP;
8+
9+
test('ipstack', function () {
10+
$config = [
11+
'driver' => 'ipstack',
12+
'ipstack' => [
13+
'key' => 'test',
14+
],
15+
];
16+
17+
$client = Mockery::mock(Client::class);
18+
19+
$client->shouldReceive('get')
20+
->times(1)
21+
->andReturn(
22+
new Response(
23+
200,
24+
[],
25+
json_encode([
26+
'city' => 'test city',
27+
'country_name' => 'test country name',
28+
'country_code' => 'test country code',
29+
'latitude' => 1,
30+
'longitude' => -1,
31+
'region_name' => 'test region name',
32+
'region_code' => 'test region code',
33+
'time_zone.id' => 'test timezone.id',
34+
'zip' => 'test zip',
35+
])
36+
)
37+
);
38+
39+
$geoip = new GeoIP($config, $client);
40+
$geoip = $geoip->setIp($this->validIp);
41+
42+
expect($geoip->get())->toEqual(
43+
[
44+
'city' => 'test city',
45+
'country' => 'test country name',
46+
'countryCode' => 'test country code',
47+
'latitude' => 1.0,
48+
'longitude' => -1.0,
49+
'region' => 'test region name',
50+
'regionCode' => 'test region code',
51+
'timezone' => 'test timezone.id',
52+
'postalCode' => 'test zip',
53+
]
54+
);
55+
});
56+
57+
test('ipstack throws exception getraw', function () {
58+
$config = [
59+
'driver' => 'ipstack',
60+
'ipstack' => [
61+
'key' => 'test',
62+
],
63+
];
64+
$this->expectException(GeoIPException::class);
65+
66+
$geoip = new GeoIP($config);
67+
$geoip = $geoip->setIp($this->validIp);
68+
69+
$geoip->getRaw();
70+
});
71+
72+
test('ipstack throws exception without key', function () {
73+
$config = [
74+
'driver' => 'ipstack',
75+
];
76+
77+
$this->expectException(InvalidCredentialsException::class);
78+
79+
$geoip = new GeoIP($config);
80+
$geoip = $geoip->setIp($this->validIp);
81+
82+
$geoip->get();
83+
});
84+
85+
test('ipstack secure config value defaults to true when missing', function () {
86+
$config = [
87+
'driver' => 'ipstack',
88+
'ipstack' => [
89+
'key' => 'test',
90+
],
91+
];
92+
93+
$client = Mockery::mock(Client::class);
94+
95+
$client->shouldReceive('get')
96+
->withArgs(function ($url) {
97+
$this->expect($url)->toStartWith('https://');
98+
99+
return true;
100+
})
101+
->times(1)
102+
->andReturn(
103+
new Response(200, [], json_encode(['city' => 'test city']))
104+
);
105+
106+
$geoip = new GeoIP($config, $client);
107+
$geoip = $geoip->setIp($this->validIp);
108+
109+
$geoip->get();
110+
});
111+
test('ipstack respects false secure config value', function () {
112+
$config = [
113+
'driver' => 'ipstack',
114+
'ipstack' => [
115+
'key' => 'test',
116+
'secure' => false,
117+
],
118+
];
119+
120+
$client = Mockery::mock(Client::class);
121+
122+
$client->shouldReceive('get')
123+
->withArgs(function ($url) {
124+
$this->expect($url)->toStartWith('http://');
125+
126+
return true;
127+
})
128+
->times(1)
129+
->andReturn(
130+
new Response(200, [], json_encode(['city' => 'test city']))
131+
);
132+
133+
$geoip = new GeoIP($config, $client);
134+
$geoip = $geoip->setIp($this->validIp);
135+
136+
$geoip->get();
137+
});
138+
test("ipstack respects true secure config value", function () {
139+
$config = [
140+
'driver' => 'ipstack',
141+
'ipstack' => [
142+
'key' => 'test',
143+
'secure' => true,
144+
],
145+
];
146+
147+
$client = Mockery::mock(Client::class);
148+
149+
$client->shouldReceive('get')
150+
->withArgs(function ($url) {
151+
$this->expect($url)->toStartWith('https://');
152+
153+
return true;
154+
})
155+
->times(1)
156+
->andReturn(
157+
new Response(200, [], json_encode(['city' => 'test city']))
158+
);
159+
160+
$geoip = new GeoIP($config, $client);
161+
$geoip = $geoip->setIp($this->validIp);
162+
163+
$geoip->get();
164+
});

0 commit comments

Comments
 (0)