├── .gitignore
├── phpstan.neon
├── Exception
└── GeoIpException.php
├── CravlerMaxMindGeoIpBundle.php
├── Resources
└── config
│ └── services.xml
├── LICENSE
├── DependencyInjection
├── CravlerMaxMindGeoIpExtension.php
└── Configuration.php
├── composer.json
├── Service
└── GeoIpService.php
├── README.md
└── Command
└── UpdateDatabaseCommand.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | tools
3 | vendor
4 | composer.lock
5 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | level: 9
3 | excludePaths:
4 | - tools
5 | - vendor
6 |
--------------------------------------------------------------------------------
/Exception/GeoIpException.php:
--------------------------------------------------------------------------------
1 |
9 | */
10 | class CravlerMaxMindGeoIpBundle extends Bundle
11 | {
12 | }
13 |
--------------------------------------------------------------------------------
/Resources/config/services.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
10 |
13 |
14 |
15 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Sergei Vizel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/DependencyInjection/CravlerMaxMindGeoIpExtension.php:
--------------------------------------------------------------------------------
1 | processConfiguration($configuration, $configs);
21 |
22 | $loader = new Loader\XmlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
23 | $loader->load('services.xml');
24 |
25 | $container->findDefinition('cravler_max_mind_geo_ip.service.geo_ip_service')->setArguments([$config]);
26 | $container->findDefinition('cravler_max_mind_geo_ip.command.update_database_command')->setArguments([$config]);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cravler/maxmind-geoip-bundle",
3 | "description": "Bundle integrating MaxMind GeoIP2 database into symfony application",
4 | "type": "symfony-bundle",
5 | "keywords": [
6 | "Symfony",
7 | "MaxMind",
8 | "GeoIP2"
9 | ],
10 | "license": "MIT",
11 | "authors": [
12 | {
13 | "name": "Sergei Vizel",
14 | "email": "sergei.vizel@gmail.com",
15 | "homepage": "http://github.com/cravler"
16 | }
17 | ],
18 | "require": {
19 | "php": ">=8.1",
20 | "geoip2/geoip2": "^3.0",
21 | "symfony/console": "^6.4|^7.0",
22 | "symfony/dependency-injection": "^6.4|^7.0",
23 | "symfony/framework-bundle": "^6.4|^7.0"
24 | },
25 | "require-dev": {
26 | "phpstan/phpstan": "^1.10",
27 | "phpstan/phpstan-symfony": "^1.3",
28 | "phpstan/extension-installer": "^1.3"
29 | },
30 | "config": {
31 | "allow-plugins": {
32 | "phpstan/extension-installer": true
33 | }
34 | },
35 | "autoload": {
36 | "psr-4": { "Cravler\\MaxMindGeoIpBundle\\": "" }
37 | },
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "3.x-dev"
41 | }
42 | },
43 | "scripts": {
44 | "pre-commit": [
45 | "[ -d ./tools/php-cs-fixer ] || (mkdir -p ./tools/php-cs-fixer && composer require --no-interaction --working-dir=./tools/php-cs-fixer friendsofphp/php-cs-fixer)",
46 | "./tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --dry-run --diff -v --stop-on-violation --using-cache=no --rules=@Symfony,-concat_space ./",
47 | "./vendor/bin/phpstan analyse -c ./phpstan.neon ./"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Service/GeoIpService.php:
--------------------------------------------------------------------------------
1 |
19 | */
20 | class GeoIpService
21 | {
22 | /**
23 | * @param array $config
24 | */
25 | public function __construct(private readonly array $config = [])
26 | {
27 | }
28 |
29 | /**
30 | * @param string[] $locales
31 | */
32 | public function getClient(array $locales = ['en']): Client
33 | {
34 | /** @var array{'user_id': ?int, 'license_key': ?string, 'options': ?array} $client */
35 | $client = \is_array($this->config['client'] ?? null) ? $this->config['client'] : [];
36 |
37 | if (!$client['user_id']) {
38 | throw new GeoIpException('"user_id" not defined');
39 | }
40 |
41 | if (!$client['license_key']) {
42 | throw new GeoIpException('"license_key" not defined');
43 | }
44 |
45 | $options = \is_array($client['options'] ?? null) ? $client['options'] : [];
46 |
47 | return new Client($client['user_id'], $client['license_key'], $locales, $options);
48 | }
49 |
50 | /**
51 | * @param string[] $locales
52 | *
53 | * @throws GeoIpException|InvalidDatabaseException
54 | */
55 | public function getReader(string $type = 'country', array $locales = ['en']): Reader
56 | {
57 | $type = \preg_replace_callback('/([A-Z])/', fn (array $matches): string => '_' . \strtolower($matches[1]), $type);
58 |
59 | /** @var array $db */
60 | $db = \is_array($this->config['db'] ?? null) ? $this->config['db'] : [];
61 |
62 | if (!isset($db[$type])) {
63 | throw new GeoIpException(\sprintf('Unknown database type %s', $type));
64 | }
65 |
66 | /** @var string $path */
67 | $path = $this->config['path'];
68 |
69 | return new Reader($path . '/' . $db[$type], $locales);
70 | }
71 |
72 | /**
73 | * @param array $options
74 | *
75 | * @throws GeoIpException|InvalidDatabaseException
76 | */
77 | public function getRecord(string $ipAddress = 'me', string $type = 'country', array $options = []): AnonymousIp|Asn|City|ConnectionType|Country|Domain|Isp|\JsonSerializable
78 | {
79 | $provider = \is_string($options['provider'] ?? null) ? $options['provider'] : 'reader';
80 |
81 | /** @var string[] $locales */
82 | $locales = \is_array($options['locales'] ?? null) ? $options['locales'] : ['en'];
83 |
84 | if ('client' === $provider) {
85 | $provider = $this->getClient($locales);
86 | } else {
87 | $provider = $this->getReader($type, $locales);
88 | }
89 |
90 | $method = \preg_replace_callback('/_([a-z])/', fn (array $matches): string => \strtoupper($matches[1]), $type);
91 |
92 | if (!$method || !\method_exists($provider, $method)) {
93 | throw new GeoIpException(\sprintf('The method "%s" does not exist for %s', $method, \get_class($provider)));
94 | }
95 |
96 | return $provider->{$method}($ipAddress);
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CravlerMaxMindGeoIpBundle
2 |
3 | ## Installation
4 |
5 | ### Step 1: Download the Bundle
6 |
7 | ``` bash
8 | composer require cravler/maxmind-geoip-bundle:3.x-dev
9 | ```
10 |
11 | This command requires you to have Composer installed globally, as explained
12 | in the [installation chapter](https://getcomposer.org/doc/00-intro.md) of the Composer documentation.
13 |
14 | ### Step 2: Enable the Bundle
15 |
16 | This bundle should be automatically enabled by [Flex](https://symfony.com/doc/current/setup/flex.html).
17 | In case you don't use Flex, you'll need to manually enable the bundle by
18 | adding the following line in the `config/bundles.php` file of your project:
19 |
20 | ``` php
21 | ['all' => true],
27 | ];
28 | ```
29 |
30 | ## Configuration
31 |
32 | The default configuration for the bundle looks like this:
33 |
34 | ``` yaml
35 | cravler_max_mind_geo_ip:
36 | client:
37 | user_id: ~
38 | license_key: ~
39 | options: {}
40 | path: '%kernel.project_dir%/resources/MaxMind'
41 | db:
42 | country: 'GeoIP2-Country.mmdb'
43 | city: 'GeoIP2-City.mmdb'
44 | asn: 'GeoIP2-ASN.mmdb'
45 | connection_type: 'GeoIP2-Connection-Type.mmdb'
46 | anonymous_ip: 'GeoIP2-Anonymous-IP.mmdb'
47 | enterprise: 'GeoIP2-Enterprise.mmdb'
48 | domain: 'GeoIP2-Domain.mmdb'
49 | isp: 'GeoIP2-ISP.mmdb'
50 | source:
51 | country: ~
52 | city: ~
53 | asn: ~
54 | connection_type: ~
55 | anonymous_ip: ~
56 | enterprise: ~
57 | domain: ~
58 | isp: ~
59 | md5_check:
60 | country: ~
61 | city: ~
62 | asn: ~
63 | connection_type: ~
64 | anonymous_ip: ~
65 | enterprise: ~
66 | domain: ~
67 | isp: ~
68 | ```
69 |
70 | If you need a `GeoLite2` license:
71 |
72 | 1. [Sign up for a MaxMind account](https://www.maxmind.com/en/geolite2/signup) (no purchase required)
73 | 2. Set your password and create a [license key](https://www.maxmind.com/en/accounts/current/license-key)
74 | `Will this key be used for GeoIP Update? > No`
75 |
76 | ```yaml
77 | # config/packages/cravler_max_mind_geo_ip.yaml
78 |
79 | parameters:
80 | max_mind.license_key: ''
81 |
82 | cravler_max_mind_geo_ip:
83 | source:
84 | country: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&suffix=tar.gz&license_key=%max_mind.license_key%'
85 | city: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&suffix=tar.gz&license_key=%max_mind.license_key%'
86 | asn: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&suffix=tar.gz&license_key=%max_mind.license_key%'
87 | md5_check:
88 | country: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&suffix=tar.gz.md5&license_key=%max_mind.license_key%'
89 | city: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-City&suffix=tar.gz.md5&license_key=%max_mind.license_key%'
90 | asn: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&suffix=tar.gz.md5&license_key=%max_mind.license_key%'
91 | ```
92 |
93 | If you have bought a license:
94 |
95 | ``` yaml
96 | # config/packages/cravler_max_mind_geo_ip.yaml
97 |
98 | parameters:
99 | max_mind.user_id: ''
100 | max_mind.license_key: ''
101 |
102 | cravler_max_mind_geo_ip:
103 | client:
104 | user_id: '%max_mind.user_id%'
105 | license_key: '%max_mind.license_key%'
106 | ...
107 | source:
108 | country: 'https://download.maxmind.com/app/geoip_download?edition_id=GeoIP2-Country&suffix=tar.gz&license_key=%max_mind.license_key%'
109 | ...
110 | md5_check:
111 | country: ~
112 | ...
113 | ```
114 |
115 | > **NB!** Do not forget to change your license data.
116 |
117 | ## Download and update the MaxMind GeoIp2 database
118 |
119 | ``` bash
120 | php bin/console cravler:maxmind:geoip-update
121 | ```
122 |
123 | You can use the *--no-md5-check* option if you want to skip MD5 check.
124 |
125 | ``` bash
126 | php bin/console cravler:maxmind:geoip-update --no-md5-check
127 | ```
128 |
129 | ## How to use
130 |
131 | ### Database Reader
132 |
133 | ``` php
134 | $geoIpService = $container->get('cravler_max_mind_geo_ip.service.geo_ip_service');
135 |
136 | // Replace "city" with the appropriate method for your database, e.g., "country".
137 | $record = $geoIpService->getRecord('128.101.101.101', 'city');
138 |
139 | print($record->country->isoCode . "\n"); // 'US'
140 | print($record->country->name . "\n"); // 'United States'
141 | print($record->city->name . "\n"); // 'Minneapolis'
142 | ```
143 |
144 | ### Web Service Client
145 |
146 | ``` php
147 | $geoIpService = $container->get('cravler_max_mind_geo_ip.service.geo_ip_service');
148 |
149 | $client = $geoIpService->getClient();
150 |
151 | $record = $client->city('128.101.101.101');
152 | ```
153 |
154 | ## License
155 |
156 | This bundle is under the MIT license. See the complete license in the bundle:
157 |
158 | ```
159 | LICENSE
160 | ```
161 |
--------------------------------------------------------------------------------
/DependencyInjection/Configuration.php:
--------------------------------------------------------------------------------
1 | getRootNode();
19 |
20 | $rootNode
21 | ->children()
22 | ->arrayNode('client')
23 | ->addDefaultsIfNotSet()
24 | ->children()
25 | ->scalarNode('user_id')
26 | ->defaultValue(null)
27 | ->end()
28 | ->scalarNode('license_key')
29 | ->defaultValue(null)
30 | ->end()
31 | ->arrayNode('options')
32 | ->prototype('variable')->end()
33 | ->end()
34 | ->end()
35 | ->end()
36 | ->scalarNode('path')
37 | ->defaultValue('%kernel.project_dir%/resources/MaxMind')
38 | ->end()
39 | ->arrayNode('db')
40 | ->addDefaultsIfNotSet()
41 | ->children()
42 | ->scalarNode('country')
43 | ->cannotBeEmpty()
44 | ->defaultValue('GeoIP2-Country.mmdb')
45 | ->end()
46 | ->scalarNode('city')
47 | ->cannotBeEmpty()
48 | ->defaultValue('GeoIP2-City.mmdb')
49 | ->end()
50 | ->scalarNode('asn')
51 | ->cannotBeEmpty()
52 | ->defaultValue('GeoIP2-ASN.mmdb')
53 | ->end()
54 | ->scalarNode('connection_type')
55 | ->cannotBeEmpty()
56 | ->defaultValue('GeoIP2-Connection-Type.mmdb')
57 | ->end()
58 | ->scalarNode('domain')
59 | ->cannotBeEmpty()
60 | ->defaultValue('GeoIP2-Domain.mmdb')
61 | ->end()
62 | ->scalarNode('isp')
63 | ->cannotBeEmpty()
64 | ->defaultValue('GeoIP2-ISP.mmdb')
65 | ->end()
66 | ->scalarNode('anonymous_ip')
67 | ->cannotBeEmpty()
68 | ->defaultValue('GeoIP2-Anonymous-IP.mmdb')
69 | ->end()
70 | ->scalarNode('enterprise')
71 | ->cannotBeEmpty()
72 | ->defaultValue('GeoIP2-Enterprise.mmdb')
73 | ->end()
74 | ->end()
75 | ->end()
76 | ->arrayNode('source')
77 | ->addDefaultsIfNotSet()
78 | ->children()
79 | ->scalarNode('country')
80 | ->defaultValue(null)
81 | ->end()
82 | ->scalarNode('city')
83 | ->defaultValue(null)
84 | ->end()
85 | ->scalarNode('asn')
86 | ->defaultValue(null)
87 | ->end()
88 | ->scalarNode('connection_type')
89 | ->defaultValue(null)
90 | ->end()
91 | ->scalarNode('domain')
92 | ->defaultValue(null)
93 | ->end()
94 | ->scalarNode('isp')
95 | ->defaultValue(null)
96 | ->end()
97 | ->scalarNode('anonymous_ip')
98 | ->defaultValue(null)
99 | ->end()
100 | ->scalarNode('enterprise')
101 | ->defaultValue(null)
102 | ->end()
103 | ->end()
104 | ->end()
105 | ->arrayNode('md5_check')
106 | ->addDefaultsIfNotSet()
107 | ->children()
108 | ->scalarNode('country')
109 | ->defaultValue(null)
110 | ->end()
111 | ->scalarNode('city')
112 | ->defaultValue(null)
113 | ->end()
114 | ->scalarNode('asn')
115 | ->defaultValue(null)
116 | ->end()
117 | ->scalarNode('connection_type')
118 | ->defaultValue(null)
119 | ->end()
120 | ->scalarNode('domain')
121 | ->defaultValue(null)
122 | ->end()
123 | ->scalarNode('isp')
124 | ->defaultValue(null)
125 | ->end()
126 | ->scalarNode('anonymous_ip')
127 | ->defaultValue(null)
128 | ->end()
129 | ->scalarNode('enterprise')
130 | ->defaultValue(null)
131 | ->end()
132 | ->end()
133 | ->end()
134 | ->end();
135 |
136 | return $treeBuilder;
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/Command/UpdateDatabaseCommand.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | #[AsCommand(
15 | name: 'cravler:maxmind:geoip-update',
16 | description: 'Downloads and updates the MaxMind GeoIp2 database',
17 | )]
18 | class UpdateDatabaseCommand extends Command
19 | {
20 | /**
21 | * @param array $config
22 | */
23 | public function __construct(private readonly array $config = [])
24 | {
25 | parent::__construct();
26 | }
27 |
28 | protected function configure(): void
29 | {
30 | $this->addOption('no-md5-check', null, InputOption::VALUE_NONE, 'Disable MD5 check');
31 | }
32 |
33 | protected function execute(InputInterface $input, OutputInterface $output): int
34 | {
35 | /** @var array $db */
36 | $db = \is_array($this->config['db'] ?? null) ? $this->config['db'] : [];
37 |
38 | /** @var array $md5Check */
39 | $md5Check = \is_array($this->config['md5_check'] ?? null) ? $this->config['md5_check'] : [];
40 |
41 | /** @var array $sources */
42 | $sources = \is_array($this->config['source'] ?? null) ? $this->config['source'] : [];
43 |
44 | $exitCode = Command::SUCCESS;
45 |
46 | foreach ($sources as $key => $source) {
47 | if (!$source) {
48 | continue;
49 | }
50 |
51 | $output->writeln('');
52 | $output->write(\sprintf('Downloading %s... ', $source));
53 |
54 | $tmpFile = $this->downloadFile($source);
55 | if (!$tmpFile) {
56 | $output->writeln('FAILED');
57 | $output->writeln(\sprintf('Error during file download occurred on %s', $source));
58 | $exitCode = Command::FAILURE;
59 | continue;
60 | }
61 |
62 | $output->writeln('Done');
63 | $output->write('Unzipping the downloaded data... ');
64 | $tmpFileUnzipped = \dirname($tmpFile) . DIRECTORY_SEPARATOR . $db[$key];
65 |
66 | $success = $this->decompressFile($tmpFile, $tmpFileUnzipped);
67 |
68 | $calculatedMD5 = null;
69 | if (!$input->getOption('no-md5-check')) {
70 | if (\str_contains($tmpFile, '.tar.gz')) {
71 | $calculatedMD5 = \md5_file($tmpFile);
72 | } else {
73 | $calculatedMD5 = \md5_file($tmpFileUnzipped);
74 | }
75 | }
76 |
77 | \unlink($tmpFile);
78 |
79 | if ($success) {
80 | $output->writeln('Done');
81 | } else {
82 | $output->writeln(\sprintf('An error occured when decompressing %s', \basename($tmpFile)));
83 | $exitCode = Command::FAILURE;
84 | continue;
85 | }
86 |
87 | // MD5 check
88 | if (!$input->getOption('no-md5-check')) {
89 | $output->write('Checking file hash... ');
90 | if (\is_string($md5Check[$key] ?? null)) {
91 | $expectedMD5 = \file_get_contents($md5Check[$key]);
92 |
93 | if (!$expectedMD5 || 32 !== \strlen($expectedMD5)) {
94 | \unlink($tmpFileUnzipped);
95 | $output->writeln(\sprintf('Unable to check MD5 for %s', $source));
96 | $exitCode = Command::FAILURE;
97 | continue;
98 | } elseif ($expectedMD5 !== $calculatedMD5) {
99 | \unlink($tmpFileUnzipped);
100 | $output->writeln(\sprintf('MD5 for %s does not match', $source));
101 | $exitCode = Command::FAILURE;
102 | continue;
103 | } else {
104 | $output->writeln('File hash OK');
105 | }
106 | } else {
107 | $output->writeln('Skipped');
108 | }
109 | }
110 |
111 | /** @var string $path */
112 | $path = $this->config['path'];
113 | if (!\file_exists($path)) {
114 | \mkdir($path, 0777, true);
115 | }
116 |
117 | $outputFilePath = $this->config['path'] . DIRECTORY_SEPARATOR . $db[$key];
118 | \chmod(\dirname($outputFilePath), 0777);
119 | $success = @\rename($tmpFileUnzipped, $outputFilePath);
120 |
121 | if ($success) {
122 | $output->writeln(\sprintf('Update completed for %s', $key));
123 | } else {
124 | $output->writeln(\sprintf('Unable to update %s', $key));
125 | $exitCode = Command::FAILURE;
126 | }
127 | }
128 | $output->writeln('');
129 |
130 | return $exitCode;
131 | }
132 |
133 | private function downloadFile(string $source): ?string
134 | {
135 | $tmpFile = \tempnam(\sys_get_temp_dir(), 'maxmind_geoip2_');
136 | if (\is_string($tmpFile) && \str_contains($source, 'tar.gz')) {
137 | @\rename($tmpFile, $tmpFile . '.tar.gz');
138 | $tmpFile .= '.tar.gz';
139 | }
140 |
141 | if (!\is_string($tmpFile) || !@\copy($source, $tmpFile)) {
142 | return null;
143 | }
144 |
145 | return $tmpFile;
146 | }
147 |
148 | private function decompressFile(string $fileName, string $outputFilePath): bool
149 | {
150 | if (\str_contains($fileName, '.tar.gz')) {
151 | $tmpDir = \tempnam(\sys_get_temp_dir(), 'MaxMind_');
152 | if (!\is_string($tmpDir)) {
153 | return false;
154 | }
155 | \unlink($tmpDir);
156 | \mkdir($tmpDir);
157 |
158 | $p = new \PharData($fileName);
159 | $p->decompress();
160 |
161 | $tarFileName = \str_replace('.gz', '', $fileName);
162 | $phar = new \PharData($tarFileName);
163 | $phar->extractTo($tmpDir);
164 | \unlink($tarFileName);
165 |
166 | $foundFiles = \glob($tmpDir . DIRECTORY_SEPARATOR . '*' . DIRECTORY_SEPARATOR . '*.mmdb');
167 | if (\is_array($foundFiles) && \count($foundFiles)) {
168 | @\rename($foundFiles[0], $outputFilePath);
169 | }
170 |
171 | $files = new \RecursiveIteratorIterator(
172 | new \RecursiveDirectoryIterator($tmpDir, \FilesystemIterator::SKIP_DOTS),
173 | \RecursiveIteratorIterator::CHILD_FIRST
174 | );
175 | foreach ($files as $fileInfo) {
176 | /** @var \SplFileInfo $fileInfo */
177 | if ($fileInfo->isDir()) {
178 | \rmdir($fileInfo->getRealPath());
179 | } else {
180 | \unlink($fileInfo->getRealPath());
181 | }
182 | }
183 | \rmdir($tmpDir);
184 | } else {
185 | $gz = \gzopen($fileName, 'rb');
186 | if (!\is_resource($gz)) {
187 | return false;
188 | }
189 | $outputFile = \fopen($outputFilePath, 'wb');
190 | if (!\is_resource($outputFile)) {
191 | return false;
192 | }
193 | while (!\gzeof($gz)) {
194 | $data = \gzread($gz, 4096);
195 | if (false === $data) {
196 | return false;
197 | }
198 | \fwrite($outputFile, $data);
199 | }
200 | \fclose($outputFile);
201 | \gzclose($gz);
202 | }
203 |
204 | return \is_readable($outputFilePath);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------