diff --git a/composer.json b/composer.json index 144c410..8744037 100644 --- a/composer.json +++ b/composer.json @@ -59,6 +59,7 @@ "php-http/curl-client": "^2.2", "php-http/message": "^1.13", "phpstan/phpstan": "^1.9.2", + "symfony/cache": "^4.4 || ^5.0 || ^6.0", "symfony/config": "^4.4 || ^5.0 || ^6.0", "symfony/phpunit-bridge": "^5.2 || ^6.0", "symfony/validator": "^4.4 || ^5.0 || ^6.0", diff --git a/tests/Functional/CustomTestKernel.php b/tests/Functional/CustomTestKernel.php new file mode 100644 index 0000000..e3207a5 --- /dev/null +++ b/tests/Functional/CustomTestKernel.php @@ -0,0 +1,247 @@ +shutdown(); + $this->warmupDir = $warmupDir; + $this->boot(); + } + + /* + * Needed, otherwise the used cache is different on each kernel boot, which is a big issue in PluginInteractionTest + */ + public function getCacheDir(): string + { + return realpath(sys_get_temp_dir()).'/NyholmBundleTest/cachePluginInteractionTest'; + } + + /** + * Returns the kernel parameters. + */ + protected function getKernelParameters(): array + { + $bundles = []; + $bundlesMetadata = []; + + foreach ($this->bundles as $name => $bundle) { + $bundles[$name] = \get_class($bundle); + $bundlesMetadata[$name] = [ + 'path' => $bundle->getPath(), + 'namespace' => $bundle->getNamespace(), + ]; + } + + return [ + 'kernel.project_dir' => realpath($this->getProjectDir()) ?: $this->getProjectDir(), + 'kernel.environment' => $this->environment, + 'kernel.runtime_environment' => '%env(default:kernel.environment:APP_RUNTIME_ENV)%', + 'kernel.debug' => $this->debug, + 'kernel.build_dir' => realpath($buildDir = $this->warmupDir ?: $this->getBuildDir()) ?: $buildDir, + 'kernel.cache_dir' => realpath($cacheDir = ($this->getCacheDir() === $this->getBuildDir() ? ($this->warmupDir ?: $this->getCacheDir()) : $this->getCacheDir())) ?: $cacheDir, + 'kernel.logs_dir' => realpath($this->getLogDir()) ?: $this->getLogDir(), + 'kernel.bundles' => $bundles, + 'kernel.bundles_metadata' => $bundlesMetadata, + 'kernel.charset' => $this->getCharset(), + 'kernel.container_class' => $this->getContainerClass(), + ]; + } + + /** + * @internal + */ + public function setAnnotatedClassCache(array $annotatedClasses) + { + file_put_contents(($this->warmupDir ?: $this->getBuildDir()).'/annotations.map', sprintf('getContainerClass(); + $buildDir = $this->warmupDir ?: $this->getBuildDir(); + $cache = new ConfigCache($buildDir.'/'.$class.'.php', $this->debug); + $cachePath = $cache->getPath(); + + // Silence E_WARNING to ignore "include" failures - don't use "@" to prevent silencing fatal errors + $errorLevel = error_reporting(\E_ALL ^ \E_WARNING); + + try { + if (false && \is_object($this->container = include $cachePath) + && (!$this->debug || (self::$freshCache[$cachePath] ?? $cache->isFresh())) + ) { + self::$freshCache[$cachePath] = true; + $this->container->set('kernel', $this); + error_reporting($errorLevel); + + return; + } + } catch (\Throwable $e) { + } + + $oldContainer = \is_object($this->container) ? new \ReflectionClass($this->container) : $this->container = null; + + try { + is_dir($buildDir) ?: mkdir($buildDir, 0777, true); + + if ($lock = fopen($cachePath.'.lock', 'w')) { + if (!flock($lock, \LOCK_EX | \LOCK_NB, $wouldBlock) && !flock($lock, $wouldBlock ? \LOCK_SH : \LOCK_EX)) { + fclose($lock); + $lock = null; + } elseif (true || !\is_object($this->container = include $cachePath)) { + $this->container = null; + } elseif (!$oldContainer || \get_class($this->container) !== $oldContainer->name) { + flock($lock, \LOCK_UN); + fclose($lock); + $this->container->set('kernel', $this); + + return; + } + } + } catch (\Throwable $e) { + } finally { + error_reporting($errorLevel); + } + + if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) { + $collectedLogs = []; + $previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) { + if (\E_USER_DEPRECATED !== $type && \E_DEPRECATED !== $type) { + return $previousHandler ? $previousHandler($type, $message, $file, $line) : false; + } + + if (isset($collectedLogs[$message])) { + ++$collectedLogs[$message]['count']; + + return null; + } + + $backtrace = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 5); + // Clean the trace by removing first frames added by the error handler itself. + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (isset($backtrace[$i]['file'], $backtrace[$i]['line']) && $backtrace[$i]['line'] === $line && $backtrace[$i]['file'] === $file) { + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + for ($i = 0; isset($backtrace[$i]); ++$i) { + if (!isset($backtrace[$i]['file'], $backtrace[$i]['line'], $backtrace[$i]['function'])) { + continue; + } + if (!isset($backtrace[$i]['class']) && 'trigger_deprecation' === $backtrace[$i]['function']) { + $file = $backtrace[$i]['file']; + $line = $backtrace[$i]['line']; + $backtrace = \array_slice($backtrace, 1 + $i); + break; + } + } + + // Remove frames added by DebugClassLoader. + for ($i = \count($backtrace) - 2; 0 < $i; --$i) { + if (\in_array($backtrace[$i]['class'] ?? null, [DebugClassLoader::class, LegacyDebugClassLoader::class], true)) { + $backtrace = [$backtrace[$i + 1]]; + break; + } + } + + $collectedLogs[$message] = [ + 'type' => $type, + 'message' => $message, + 'file' => $file, + 'line' => $line, + 'trace' => [$backtrace[0]], + 'count' => 1, + ]; + + return null; + }); + } + + try { + $container = null; + $container = $this->buildContainer(); + $container->compile(); + } finally { + if ($collectDeprecations) { + restore_error_handler(); + + @file_put_contents($buildDir.'/'.$class.'Deprecations.log', serialize(array_values($collectedLogs))); + @file_put_contents($buildDir.'/'.$class.'Compiler.log', null !== $container ? implode("\n", $container->getCompiler()->getLog()) : ''); + } + } + + $this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass()); + + if ($lock) { + flock($lock, \LOCK_UN); + fclose($lock); + } + + $this->container = require $cachePath; + $this->container->set('kernel', $this); + + if ($oldContainer && \get_class($this->container) !== $oldContainer->name) { + // Because concurrent requests might still be using them, + // old container files are not removed immediately, + // but on a next dump of the container. + static $legacyContainers = []; + $oldContainerDir = \dirname($oldContainer->getFileName()); + $legacyContainers[$oldContainerDir.'.legacy'] = true; + foreach (glob(\dirname($oldContainerDir).\DIRECTORY_SEPARATOR.'*.legacy', \GLOB_NOSORT) as $legacyContainer) { + if (!isset($legacyContainers[$legacyContainer]) && @unlink($legacyContainer)) { + (new Filesystem())->remove(substr($legacyContainer, 0, -7)); + } + } + + touch($oldContainerDir.'.legacy'); + } + + $preload = $this instanceof WarmableInterface ? (array) $this->warmUp($this->container->getParameter('kernel.cache_dir')) : []; + + if ($this->container->has('cache_warmer')) { + $preload = array_merge($preload, (array) $this->container->get('cache_warmer')->warmUp($this->container->getParameter('kernel.cache_dir'))); + } + + if ($preload && method_exists(Preloader::class, 'append') && file_exists($preloadFile = $buildDir.'/'.$class.'.preload.php')) { + Preloader::append($preloadFile, $preload); + } + } +} diff --git a/tests/Functional/PluginInteractionTest.php b/tests/Functional/PluginInteractionTest.php new file mode 100644 index 0000000..2164cd6 --- /dev/null +++ b/tests/Functional/PluginInteractionTest.php @@ -0,0 +1,76 @@ +addTestBundle(BazingaGeocoderBundle::class); + $kernel->addTestCompilerPass(new PublicServicePass('|[Bb]azinga:*|')); + $kernel->addTestCompilerPass(new PublicServicePass('|[gG]eocoder:*|')); + $kernel->handleOptions($options); + + return $kernel; + } + + public function testCachePluginUsesIpFromFakeIpPlugin(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->setClearCacheAfterShutdown(false); + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/cache_symfony.yml'); + $kernel->addTestConfig(__DIR__.'/config/geo_plugin_fakeip_with_cache_cn.yml'); + }]); + $kernel->setClearCacheAfterShutdown(false); + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $geoPluginGeocoder = $container->get('bazinga_geocoder.provider.geoPlugin'); + $result = $geoPluginGeocoder->geocodeQuery(GeocodeQuery::create('::1')); + $country = $result->first()->getCountry()->getCode(); + self::assertEquals('CN', $country); + + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->setClearCacheAfterShutdown(false); + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/cache_symfony.yml'); + $kernel->addTestConfig(__DIR__.'/config/geo_plugin_fakeip_with_cache_fr.yml'); + }]); + $kernel->setClearCacheAfterShutdown(false); + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $geoPluginGeocoder = $container->get('bazinga_geocoder.provider.geoPlugin'); + $result = $geoPluginGeocoder->geocodeQuery(GeocodeQuery::create('::1')); + $country = $result->first()->getCountry()->getCode(); + self::assertEquals('FR', $country); + } +} diff --git a/tests/Functional/config/cache_symfony.yml b/tests/Functional/config/cache_symfony.yml new file mode 100644 index 0000000..419c11d --- /dev/null +++ b/tests/Functional/config/cache_symfony.yml @@ -0,0 +1,12 @@ +framework: + cache: + app: cache.adapter.filesystem + system: cache.adapter.system + pools: + app.cache.geoPlugin: + adapter: cache.app + default_lifetime: 600 +services: + app.simple_cache: + class: Symfony\Component\Cache\Psr16Cache + arguments: ['@app.cache.geoPlugin'] \ No newline at end of file diff --git a/tests/Functional/config/geo_plugin_fakeip_with_cache_cn.yml b/tests/Functional/config/geo_plugin_fakeip_with_cache_cn.yml new file mode 100644 index 0000000..6bf704c --- /dev/null +++ b/tests/Functional/config/geo_plugin_fakeip_with_cache_cn.yml @@ -0,0 +1,14 @@ +# See the docs at https://github.com/geocoder-php/BazingaGeocoderBundle +bazinga_geocoder: + # The local IP (127.0.0.1) will be replaced by the fake_ip + # see https://github.com/geocoder-php/BazingaGeocoderBundle/blob/5.0.0/Resources/doc/index.md#fake-local-ip + fake_ip: + local_ip: ::1 + ip: 123.123.123.128 + # this ip is in china + providers: + geoPlugin: + factory: Bazinga\GeocoderBundle\ProviderFactory\GeoPluginFactory + cache: 'app.simple_cache' + cache_lifetime: 42 + cache_precision: ~ \ No newline at end of file diff --git a/tests/Functional/config/geo_plugin_fakeip_with_cache_fr.yml b/tests/Functional/config/geo_plugin_fakeip_with_cache_fr.yml new file mode 100644 index 0000000..f0b4b32 --- /dev/null +++ b/tests/Functional/config/geo_plugin_fakeip_with_cache_fr.yml @@ -0,0 +1,14 @@ +# See the docs at https://github.com/geocoder-php/BazingaGeocoderBundle +bazinga_geocoder: + # The local IP (127.0.0.1) will be replaced by the fake_ip + # see https://github.com/geocoder-php/BazingaGeocoderBundle/blob/5.0.0/Resources/doc/index.md#fake-local-ip + fake_ip: + local_ip: ::1 + ip: 87.98.128.10 + # this ip is in france + providers: + geoPlugin: + factory: Bazinga\GeocoderBundle\ProviderFactory\GeoPluginFactory + cache: 'app.simple_cache' + cache_lifetime: 42 + cache_precision: ~ \ No newline at end of file