Skip to content

Commit a873513

Browse files
Merge pull request #9 from ipdata/claude/update-ipdata-api-3ggxS
Add EU endpoint support and improve API client functionality
2 parents bddeadc + 44ca7e8 commit a873513

File tree

5 files changed

+146
-16
lines changed

5 files changed

+146
-16
lines changed

README.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,27 @@ $psr17Factory = new Psr17Factory();
2727
$ipdata = new Ipdata('my_api_key', $httpClient, $psr17Factory);
2828
```
2929

30+
### EU endpoint
31+
32+
To route requests through ipdata's EU servers (Paris, Ireland and Frankfurt) for GDPR compliance, pass the EU base URL:
33+
34+
```php
35+
$ipdata = new Ipdata('my_api_key', $httpClient, $psr17Factory, Ipdata::EU_BASE_URL);
36+
```
37+
3038
## How to use
3139

32-
To send a geocode request you simply need to provide the IP address you are interested in.
40+
### Look up your own IP
41+
42+
Call `lookup()` with no arguments to get the location of the calling IP address.
43+
44+
```php
45+
$data = $ipdata->lookup();
46+
```
47+
48+
### Look up a specific IP
49+
50+
To send a geocode request you simply need to provide the IP address you are interested in.
3351

3452
```php
3553
$data = $ipdata->lookup('69.78.70.144');
@@ -56,6 +74,12 @@ The output will be the response from the API server with one additional `status`
5674
"flag": "https:\/\/ipdata.co\/flags\/us.png",
5775
"emoji_flag": "\ud83c\uddfa\ud83c\uddf8",
5876
"emoji_unicode": "U+1F1FA U+1F1F8",
77+
"company": {
78+
"name": "Verizon Wireless",
79+
"domain": "verizonwireless.com",
80+
"network": "69.78.0.0\/16",
81+
"type": "isp"
82+
},
5983
"asn": {
6084
"asn": "AS6167",
6185
"name": "Cellco Partnership DBA Verizon Wireless",
@@ -90,12 +114,22 @@ The output will be the response from the API server with one additional `status`
90114
},
91115
"threat": {
92116
"is_tor": false,
117+
"is_vpn": false,
118+
"is_icloud_relay": false,
93119
"is_proxy": false,
120+
"is_datacenter": false,
94121
"is_anonymous": false,
95122
"is_known_attacker": false,
96123
"is_known_abuser": false,
97124
"is_threat": false,
98-
"is_bogon": false
125+
"is_bogon": false,
126+
"blocklists": [],
127+
"scores": {
128+
"vpn_score": 0,
129+
"proxy_score": 0,
130+
"threat_score": 0,
131+
"trust_score": 0
132+
}
99133
},
100134
"count": "6",
101135
"status": 200
@@ -125,7 +159,7 @@ If you want to look up multiple IPs at the same time you may use the `bulkLookup
125159

126160

127161
```php
128-
$data = $ipdata->buildLookup(['1.1.1.1', '69.78.70.144'], ['longitude', 'latitude', 'country_name']);
162+
$data = $ipdata->bulkLookup(['1.1.1.1', '69.78.70.144'], ['longitude', 'latitude', 'country_name']);
129163
echo json_encode($data, JSON_PRETTY_PRINT);
130164
```
131165

composer.json

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,8 @@
1717
"require-dev": {
1818
"nyholm/psr7": "^1.2",
1919
"php-http/discovery": "^1.7",
20-
"php-http/httplug": "^2.0",
2120
"php-http/mock-client": "^1.3",
22-
"phpunit/phpunit": "^8.5",
23-
"symfony/http-client": "^5.0"
21+
"phpunit/phpunit": "^8.5"
2422
},
2523
"autoload": {
2624
"psr-4": {

phpstan-baseline.neon

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ parameters:
55
count: 1
66
path: src/Ipdata.php
77

8+
-
9+
message: "#^Method Ipdata\\\\ApiClient\\\\Ipdata\\:\\:bulkLookup\\(\\) has parameter \\$ips with no value type specified in iterable type array\\.$#"
10+
count: 1
11+
path: src/Ipdata.php
12+
13+
-
14+
message: "#^Method Ipdata\\\\ApiClient\\\\Ipdata\\:\\:bulkLookup\\(\\) return type has no value type specified in iterable type array\\.$#"
15+
count: 1
16+
path: src/Ipdata.php
17+
818
-
919
message: "#^Method Ipdata\\\\ApiClient\\\\Ipdata\\:\\:buildLookup\\(\\) has parameter \\$ips with no value type specified in iterable type array\\.$#"
1020
count: 1

src/Ipdata.php

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
*/
2020
class Ipdata
2121
{
22-
private const BASE_URL = 'https://api.ipdata.co';
22+
private const DEFAULT_BASE_URL = 'https://api.ipdata.co';
23+
public const EU_BASE_URL = 'https://eu-api.ipdata.co';
2324

2425
/**
2526
* @var string|null
@@ -36,13 +37,18 @@ class Ipdata
3637
*/
3738
private $requestFactory;
3839

40+
/**
41+
* @var string
42+
*/
43+
private $baseUrl;
44+
3945
/**
4046
* Get an instance of the API client. Give it an API key, a PSR-18 client and a PSR-17 request factory.
4147
*
4248
* @param ClientInterface|null $httpClient if null, we will try to use php-http/discovery to find an installed client
4349
* @param RequestFactoryInterface|null $requestFactory if null, we will try to use php-http/discovery to find an installed factory
4450
*/
45-
public function __construct(string $apiKey, ClientInterface $httpClient = null, RequestFactoryInterface $requestFactory = null)
51+
public function __construct(string $apiKey, ClientInterface $httpClient = null, RequestFactoryInterface $requestFactory = null, string $baseUrl = self::DEFAULT_BASE_URL)
4652
{
4753
if (null === $httpClient) {
4854
if (!class_exists(Psr18ClientDiscovery::class)) {
@@ -71,14 +77,15 @@ public function __construct(string $apiKey, ClientInterface $httpClient = null,
7177
$this->httpClient = $httpClient;
7278
$this->apiKey = $apiKey;
7379
$this->requestFactory = $requestFactory;
80+
$this->baseUrl = $baseUrl;
7481
}
7582

7683
/**
7784
* @param array<string> $fields
7885
*
7986
* @throws \Psr\Http\Client\ClientExceptionInterface
8087
*/
81-
public function lookup(string $ip, array $fields = []): array
88+
public function lookup(string $ip = '', array $fields = []): array
8289
{
8390
$query = [
8491
'api-key' => $this->apiKey,
@@ -88,7 +95,8 @@ public function lookup(string $ip, array $fields = []): array
8895
$query['fields'] = implode(',', $fields);
8996
}
9097

91-
$request = $this->requestFactory->createRequest('GET', sprintf('%s/%s?%s', self::BASE_URL, $ip, http_build_query($query)));
98+
$url = $ip !== '' ? sprintf('%s/%s', $this->baseUrl, $ip) : $this->baseUrl;
99+
$request = $this->requestFactory->createRequest('GET', sprintf('%s?%s', $url, http_build_query($query)));
92100
$response = $this->httpClient->sendRequest($request);
93101

94102
return $this->parseResponse($response);
@@ -97,11 +105,12 @@ public function lookup(string $ip, array $fields = []): array
97105
/**
98106
* Bulk lookup, requires paid subscription.
99107
*
108+
* @param array<string> $ips
100109
* @param array<string> $fields
101110
*
102111
* @throws \Psr\Http\Client\ClientExceptionInterface
103112
*/
104-
public function buildLookup(array $ips, array $fields = []): array
113+
public function bulkLookup(array $ips, array $fields = []): array
105114
{
106115
$query = [
107116
'api-key' => $this->apiKey,
@@ -111,14 +120,27 @@ public function buildLookup(array $ips, array $fields = []): array
111120
$query['fields'] = implode(',', $fields);
112121
}
113122

114-
$request = $this->requestFactory->createRequest('POST', sprintf('%s/bulk?%s', self::BASE_URL, http_build_query($query)));
123+
$request = $this->requestFactory->createRequest('POST', sprintf('%s/bulk?%s', $this->baseUrl, http_build_query($query)));
115124
$request->getBody()->write(json_encode($ips));
116-
$request = $request->withAddedHeader('Content-Type', 'text/plain');
125+
$request = $request->withHeader('Content-Type', 'application/json');
117126
$response = $this->httpClient->sendRequest($request);
118127

119128
return $this->parseResponse($response);
120129
}
121130

131+
/**
132+
* @deprecated Use bulkLookup() instead.
133+
*
134+
* @param array<string> $ips
135+
* @param array<string> $fields
136+
*
137+
* @throws \Psr\Http\Client\ClientExceptionInterface
138+
*/
139+
public function buildLookup(array $ips, array $fields = []): array
140+
{
141+
return $this->bulkLookup($ips, $fields);
142+
}
143+
122144
private function parseResponse(ResponseInterface $response): array
123145
{
124146
$body = $response->getBody()->__toString();

tests/IpdataTest.php

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,60 @@ public function testParseInvalidJsonResponse()
135135
$ipdata->lookup('69.78.70.144');
136136
}
137137

138-
public function testConstructWithDiscovery()
138+
public function testCallerIpLookup()
139139
{
140-
$ipdata = new Ipdata('secret_key');
141-
$this->assertInstanceOf(Ipdata::class, $ipdata);
140+
$httpClient = new MockClient();
141+
$httpClient->addResponse($this->createResponse());
142+
$ipdata = $this->createIpdata($httpClient);
143+
$ipdata->lookup();
144+
145+
$request = $httpClient->getLastRequest();
146+
$this->assertEquals('GET', $request->getMethod());
147+
$this->assertEquals('', $request->getUri()->getPath());
148+
$this->assertEquals('api-key=secret_key', $request->getUri()->getQuery());
149+
}
150+
151+
public function testBulkLookup()
152+
{
153+
$httpClient = new MockClient();
154+
$httpClient->addResponse($this->createResponse());
155+
$ipdata = $this->createIpdata($httpClient);
156+
$ipdata->bulkLookup(['8.8.8.8', '69.78.70.144']);
157+
158+
$request = $httpClient->getLastRequest();
159+
$this->assertEquals('POST', $request->getMethod());
160+
$this->assertEquals('/bulk', $request->getUri()->getPath());
161+
$this->assertEquals('api-key=secret_key', $request->getUri()->getQuery());
162+
$this->assertEquals('["8.8.8.8","69.78.70.144"]', $request->getBody()->__toString());
163+
}
164+
165+
public function testBulkLookupContentType()
166+
{
167+
$httpClient = new MockClient();
168+
$httpClient->addResponse($this->createResponse());
169+
$ipdata = $this->createIpdata($httpClient);
170+
$ipdata->bulkLookup(['8.8.8.8']);
171+
172+
$request = $httpClient->getLastRequest();
173+
$this->assertEquals('application/json', $request->getHeaderLine('Content-Type'));
174+
}
175+
176+
public function testCustomBaseUrl()
177+
{
178+
$httpClient = new MockClient();
179+
$httpClient->addResponse($this->createResponse());
180+
$ipdata = new Ipdata('secret_key', $httpClient, new Psr17Factory(), Ipdata::EU_BASE_URL);
181+
$ipdata->lookup('8.8.8.8');
182+
183+
$request = $httpClient->getLastRequest();
184+
$this->assertEquals('eu-api.ipdata.co', $request->getUri()->getHost());
185+
}
186+
187+
public function testConstructWithDiscoveryThrowsWhenNoClientAvailable()
188+
{
189+
$this->expectException(\LogicException::class);
190+
$this->expectExceptionMessage('Could not find any installed HTTP clients');
191+
new Ipdata('secret_key');
142192
}
143193

144194
private function createIpdata(ClientInterface $httpClient): Ipdata
@@ -166,6 +216,12 @@ private function createResponse(array $data = [], int $statusCode = 200): Respon
166216
'flag' => 'https://ipdata.co/flags/us.png',
167217
'emoji_flag' => "\ud83c\uddfa\ud83c\uddf8",
168218
'emoji_unicode' => 'U+1F1FA U+1F1F8',
219+
'company' => [
220+
'name' => 'Verizon Wireless',
221+
'domain' => 'verizonwireless.com',
222+
'network' => '69.78.0.0/16',
223+
'type' => 'isp',
224+
],
169225
'asn' => [
170226
'asn' => 'AS6167',
171227
'name' => 'Cellco Partnership DBA Verizon Wireless',
@@ -197,12 +253,22 @@ private function createResponse(array $data = [], int $statusCode = 200): Respon
197253
],
198254
'threat' => [
199255
'is_tor' => false,
256+
'is_vpn' => false,
257+
'is_icloud_relay' => false,
200258
'is_proxy' => false,
259+
'is_datacenter' => false,
201260
'is_anonymous' => false,
202261
'is_known_attacker' => false,
203262
'is_known_abuser' => false,
204263
'is_threat' => false,
205264
'is_bogon' => false,
265+
'blocklists' => [],
266+
'scores' => [
267+
'vpn_score' => 0,
268+
'proxy_score' => 0,
269+
'threat_score' => 0,
270+
'trust_score' => 0,
271+
],
206272
],
207273
'count' => '3',
208274
];

0 commit comments

Comments
 (0)