1 <?php
2
3 namespace GeoIp2\Database;
4
5 use GeoIp2\Exception\AddressNotFoundException;
6 use GeoIp2\ProviderInterface;
7 use MaxMind\Db\Reader as DbReader;
8 use MaxMind\Db\Reader\InvalidDatabaseException;
9
10 /**
11 * Instances of this class provide a reader for the GeoIP2 database format.
12 * IP addresses can be looked up using the database specific methods.
13 *
14 * ## Usage ##
15 *
16 * The basic API for this class is the same for every database. First, you
17 * create a reader object, specifying a file name. You then call the method
18 * corresponding to the specific database, passing it the IP address you want
19 * to look up.
20 *
21 * If the request succeeds, the method call will return a model class for
22 * the method you called. This model in turn contains multiple record classes,
23 * each of which represents part of the data returned by the database. If
24 * the database does not contain the requested information, the attributes
25 * on the record class will have a `null` value.
26 *
27 * If the address is not in the database, an
28 * {@link \GeoIp2\Exception\AddressNotFoundException} exception will be
29 * thrown. If an invalid IP address is passed to one of the methods, a
30 * SPL {@link \InvalidArgumentException} will be thrown. If the database is
31 * corrupt or invalid, a {@link \MaxMind\Db\Reader\InvalidDatabaseException}
32 * will be thrown.
33 *
34 */
35 class Reader implements ProviderInterface
36 {
37 private $dbReader;
38 private $locales;
39
40 /**
41 * Constructor.
42 *
43 * @param string $filename The path to the GeoIP2 database file.
44 * @param array $locales List of locale codes to use in name property
45 * from most preferred to least preferred.
46 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
47 * is corrupt or invalid
48 */
49 public function __construct(
50 $filename,
51 $locales = array('en')
52 ) {
53 $this->dbReader = new DbReader($filename);
54 $this->locales = $locales;
55 }
56
57 /**
58 * This method returns a GeoIP2 City model.
59 *
60 * @param string $ipAddress IPv4 or IPv6 address as a string.
61 *
62 * @return \GeoIp2\Model\City
63 *
64 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
65 * not in the database.
66 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
67 * is corrupt or invalid
68 */
69 public function city($ipAddress)
70 {
71 return $this->modelFor('City', 'City', $ipAddress);
72 }
73
74 /**
75 * This method returns a GeoIP2 Country model.
76 *
77 * @param string $ipAddress IPv4 or IPv6 address as a string.
78 *
79 * @return \GeoIp2\Model\Country
80 *
81 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
82 * not in the database.
83 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
84 * is corrupt or invalid
85 */
86 public function country($ipAddress)
87 {
88 return $this->modelFor('Country', 'Country', $ipAddress);
89 }
90
91 /**
92 * This method returns a GeoIP2 Anonymous IP model.
93 *
94 * @param string $ipAddress IPv4 or IPv6 address as a string.
95 *
96 * @return \GeoIp2\Model\AnonymousIp
97 *
98 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
99 * not in the database.
100 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
101 * is corrupt or invalid
102 */
103 public function anonymousIp($ipAddress)
104 {
105 return $this->flatModelFor(
106 'AnonymousIp',
107 'GeoIP2-Anonymous-IP',
108 $ipAddress
109 );
110 }
111
112 /**
113 * This method returns a GeoIP2 Connection Type model.
114 *
115 * @param string $ipAddress IPv4 or IPv6 address as a string.
116 *
117 * @return \GeoIp2\Model\ConnectionType
118 *
119 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
120 * not in the database.
121 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
122 * is corrupt or invalid
123 */
124 public function connectionType($ipAddress)
125 {
126 return $this->flatModelFor(
127 'ConnectionType',
128 'GeoIP2-Connection-Type',
129 $ipAddress
130 );
131 }
132
133 /**
134 * This method returns a GeoIP2 Domain model.
135 *
136 * @param string $ipAddress IPv4 or IPv6 address as a string.
137 *
138 * @return \GeoIp2\Model\Domain
139 *
140 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
141 * not in the database.
142 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
143 * is corrupt or invalid
144 */
145 public function domain($ipAddress)
146 {
147 return $this->flatModelFor(
148 'Domain',
149 'GeoIP2-Domain',
150 $ipAddress
151 );
152 }
153
154 /**
155 * This method returns a GeoIP2 Enterprise model.
156 *
157 * @param string $ipAddress IPv4 or IPv6 address as a string.
158 *
159 * @return \GeoIp2\Model\Enterprise
160 *
161 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
162 * not in the database.
163 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
164 * is corrupt or invalid
165 */
166 public function enterprise($ipAddress)
167 {
168 return $this->modelFor('Enterprise', 'Enterprise', $ipAddress);
169 }
170
171 /**
172 * This method returns a GeoIP2 ISP model.
173 *
174 * @param string $ipAddress IPv4 or IPv6 address as a string.
175 *
176 * @return \GeoIp2\Model\Isp
177 *
178 * @throws \GeoIp2\Exception\AddressNotFoundException if the address is
179 * not in the database.
180 * @throws \MaxMind\Db\Reader\InvalidDatabaseException if the database
181 * is corrupt or invalid
182 */
183 public function isp($ipAddress)
184 {
185 return $this->flatModelFor(
186 'Isp',
187 'GeoIP2-ISP',
188 $ipAddress
189 );
190 }
191
192 private function modelFor($class, $type, $ipAddress)
193 {
194 $record = $this->getRecord($class, $type, $ipAddress);
195
196 $record['traits']['ip_address'] = $ipAddress;
197 $class = "GeoIp2\\Model\\" . $class;
198
199 return new $class($record, $this->locales);
200 }
201
202 private function flatModelFor($class, $type, $ipAddress)
203 {
204 $record = $this->getRecord($class, $type, $ipAddress);
205
206 $record['ip_address'] = $ipAddress;
207 $class = "GeoIp2\\Model\\" . $class;
208
209 return new $class($record);
210 }
211
212 private function getRecord($class, $type, $ipAddress)
213 {
214 if (strpos($this->metadata()->databaseType, $type) === false) {
215 $method = lcfirst($class);
216 throw new \BadMethodCallException(
217 "The $method method cannot be used to open a "
218 . $this->metadata()->databaseType . " database"
219 );
220 }
221 $record = $this->dbReader->get($ipAddress);
222 if ($record === null) {
223 throw new AddressNotFoundException(
224 "The address $ipAddress is not in the database."
225 );
226 }
227 if (!is_array($record)) {
228 // This can happen on corrupt databases. Generally,
229 // MaxMind\Db\Reader will throw a
230 // MaxMind\Db\Reader\InvalidDatabaseException, but occasionally
231 // the lookup may result in a record that looks valid but is not
232 // an array. This mostly happens when the user is ignoring all
233 // exceptions and the more frequent InvalidDatabaseException
234 // exceptions go unnoticed.
235 throw new InvalidDatabaseException(
236 "Expected an array when looking up $ipAddress but received: "
237 . gettype($record)
238 );
239 }
240 return $record;
241 }
242
243 /**
244 * @throws \InvalidArgumentException if arguments are passed to the method.
245 * @throws \BadMethodCallException if the database has been closed.
246 * @return \MaxMind\Db\Reader\Metadata object for the database.
247 */
248 public function metadata()
249 {
250 return $this->dbReader->metadata();
251 }
252
253 /**
254 * Closes the GeoIP2 database and returns the resources to the system.
255 */
256 public function close()
257 {
258 $this->dbReader->close();
259 }
260 }
261