From 497b68f40dba8b716c186aec5ebc1dc649ca08b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Ruano?= Date: Tue, 6 Dec 2022 00:11:22 +0900 Subject: [PATCH 1/8] chore: add test for fix plugin fakeip order --- composer.json | 1 + tests/Functional/CustomTestKernel.php | 247 ++++++++++++++++++ tests/Functional/PluginInteractionTest.php | 76 ++++++ tests/Functional/config/cache_symfony.yml | 12 + .../geo_plugin_fakeip_with_cache_cn.yml | 14 + .../geo_plugin_fakeip_with_cache_fr.yml | 14 + 6 files changed, 364 insertions(+) create mode 100644 tests/Functional/CustomTestKernel.php create mode 100644 tests/Functional/PluginInteractionTest.php create mode 100644 tests/Functional/config/cache_symfony.yml create mode 100644 tests/Functional/config/geo_plugin_fakeip_with_cache_cn.yml create mode 100644 tests/Functional/config/geo_plugin_fakeip_with_cache_fr.yml 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 From 650b4bf30bb916b9aadfa9e1b99a73995956bb39 Mon Sep 17 00:00:00 2001 From: Christopher Georg Date: Thu, 22 Dec 2022 17:30:43 +0100 Subject: [PATCH 2/8] tests: add tests for php 8.2 --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65dd06f..d1ed61b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: fail-fast: true matrix: strategy: ['default'] - php: ['7.4', '8.0', '8.1'] + php: ['7.4', '8.0', '8.1', '8.2'] include: - php: '7.4' sf_version: '5.4.*' @@ -73,8 +73,8 @@ jobs: sf_version: '6.0.*' - php: '8.1' sf_version: '6.0.*' - - php: '8.1' - sf_version: '6.1.*' + - php: '8.2' + sf_version: '6.2.*' steps: - name: "Setup PHP" uses: shivammathur/setup-php@v2 @@ -110,7 +110,7 @@ jobs: max-parallel: 10 fail-fast: true matrix: - php: ['7.4', '8.0', '8.1'] + php: ['7.4', '8.0', '8.1', '8.2'] include: - php: '7.4' sf_version: '4.4.*' @@ -124,8 +124,8 @@ jobs: sf_version: '6.0.*' - php: '8.1' sf_version: '6.0.*' - - php: '8.1' - sf_version: '6.1.*' + - php: '8.2' + sf_version: '6.2.*' steps: - name: "Setup PHP" uses: shivammathur/setup-php@v2 From 608f88339f50b1313a7b273a692ecb14985c8462 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomas=20Nork=C5=ABnas?= Date: Tue, 3 Oct 2023 06:15:20 +0300 Subject: [PATCH 3/8] Refactor tests (#344) - Drop dependency on `OrmTestCase` and instead use `DoctrineBundle` for setting orm automatically instead of manually; - ~~Use MockHttpClient from symfony/http-client instead of making real http calls in tests~~ Use mock Client from php-http/mock-client; - Merge CI's phpunit jobs into single one; - Separate inlined fixtures in tests to separate classes; --- .github/workflows/ci.yml | 97 ++--- .php-cs-fixer.php | 1 + composer.json | 9 +- doc/doctrine.md | 4 +- phpunit.xml.dist | 5 - src/BazingaGeocoderBundle.php | 5 - src/Command/GeocodeCommand.php | 4 - src/DataCollector/GeocoderDataCollector.php | 5 - .../Compiler/FactoryValidatorPass.php | 2 - .../Compiler/ProfilerPass.php | 2 - src/Doctrine/ORM/GeocoderListener.php | 4 +- src/Mapping/Driver/AnnotationDriver.php | 6 - src/Mapping/Driver/AttributeDriver.php | 5 - src/ProviderFactory/AbstractFactory.php | 8 +- src/ProviderFactory/ChainFactory.php | 3 - src/Validator/Constraint/Address.php | 5 +- .../Compiler/AddProvidersPassTest.php | 2 +- tests/Doctrine/ORM/GeocoderListenerTest.php | 411 ------------------ tests/Functional/CustomTestKernel.php | 3 - .../Entity/DummyWithEmptyProperty.php | 70 +++ .../Fixtures/Entity/DummyWithGetter.php | 101 +++++ .../Entity/DummyWithInvalidGetter.php | 101 +++++ .../Fixtures/Entity/DummyWithProperty.php | 72 +++ tests/Functional/GeocoderListenerTest.php | 305 +++++++++++++ tests/Functional/Helper/CacheHelperV8.php | 4 +- .../deprecated_httplug_client_option.yml | 6 +- tests/Functional/config/framework.yml | 6 + tests/Functional/config/listener.yml | 11 + tests/Functional/config/listener_php7.yml | 43 ++ tests/Functional/config/listener_php8.yml | 42 ++ tests/Mapping/Driver/AnnotationDriverTest.php | 39 +- tests/Mapping/Driver/AttributeDriverTest.php | 31 +- tests/Mapping/Driver/Fixtures/Dummy.php | 43 ++ tests/Mapping/Driver/Fixtures/Dummy2.php | 17 + .../Constraint/AddressValidatorTest.php | 37 +- 35 files changed, 908 insertions(+), 601 deletions(-) delete mode 100644 tests/Doctrine/ORM/GeocoderListenerTest.php create mode 100644 tests/Functional/Fixtures/Entity/DummyWithEmptyProperty.php create mode 100644 tests/Functional/Fixtures/Entity/DummyWithGetter.php create mode 100644 tests/Functional/Fixtures/Entity/DummyWithInvalidGetter.php create mode 100644 tests/Functional/Fixtures/Entity/DummyWithProperty.php create mode 100644 tests/Functional/GeocoderListenerTest.php create mode 100644 tests/Functional/config/listener.yml create mode 100644 tests/Functional/config/listener_php7.yml create mode 100644 tests/Functional/config/listener_php8.yml create mode 100644 tests/Mapping/Driver/Fixtures/Dummy.php create mode 100644 tests/Mapping/Driver/Fixtures/Dummy2.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1ed61b..23b255a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: name: "PHPStan" runs-on: ubuntu-latest env: - php-version: 8.1 + php-version: 8.2 steps: - name: "Setup PHP" uses: shivammathur/setup-php@v2 @@ -19,11 +19,12 @@ jobs: tools: flex - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install Composer dependencies" - run: | - composer install --prefer-dist --no-progress --no-interaction --optimize-autoloader + uses: "ramsey/composer-install@v2" + with: + composer-options: "--optimize-autoloader" - name: "Run PHPStan" run: | @@ -34,7 +35,7 @@ jobs: name: PHP-CS-Fixer runs-on: ubuntu-latest env: - php-version: 8.1 + php-version: 8.2 steps: - name: "Setup PHP" uses: shivammathur/setup-php@v2 @@ -43,11 +44,12 @@ jobs: tools: flex, cs2pr - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install Composer dependencies" - run: | - composer install --prefer-dist --no-progress --no-interaction --optimize-autoloader + uses: "ramsey/composer-install@v2" + with: + composer-options: "--optimize-autoloader" - name: "Run PHP-CS-Fixer" run: vendor/bin/php-cs-fixer fix -v --dry-run --using-cache=no --format=checkstyle | cs2pr @@ -59,63 +61,17 @@ jobs: max-parallel: 10 fail-fast: true matrix: - strategy: ['default'] + dependencies: ['highest'] php: ['7.4', '8.0', '8.1', '8.2'] include: - php: '7.4' - sf_version: '5.4.*' - strategy: 'lowest' - - php: '8.0' - sf_version: '5.4.*' - - php: '8.1' - sf_version: '5.4.*' - - php: '8.0' - sf_version: '6.0.*' - - php: '8.1' - sf_version: '6.0.*' - - php: '8.2' - sf_version: '6.2.*' - steps: - - name: "Setup PHP" - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - tools: flex - coverage: none - - - name: "Checkout code" - uses: actions/checkout@v3 - - - name: "Install Composer dependencies (default)" - if: matrix.strategy != 'lowest' - env: - SYMFONY_REQUIRE: ${{ matrix.sf_version }} - run: composer update --no-interaction --optimize-autoloader - - - name: "Install Composer dependencies (lowest)" - if: matrix.strategy == 'lowest' - env: - SYMFONY_REQUIRE: ${{ matrix.sf_version }} - run: composer update --no-interaction --prefer-stable --prefer-lowest --optimize-autoloader - - - name: "Run tests" - env: - SYMFONY_DEPRECATIONS_HELPER: 'max[self]=2' - run: ./vendor/bin/simple-phpunit -v --testsuite main - - doctrine: - name: "PHPUnit (Doctrine)" - runs-on: ubuntu-latest - strategy: - max-parallel: 10 - fail-fast: true - matrix: - php: ['7.4', '8.0', '8.1', '8.2'] - include: + sf_version: '4.4.*' + dependencies: 'lowest' - php: '7.4' sf_version: '4.4.*' - php: '7.4' sf_version: '5.4.*' + dependencies: 'lowest' - php: '8.0' sf_version: '5.4.*' - php: '8.1' @@ -135,14 +91,27 @@ jobs: coverage: none - name: "Checkout code" - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: "Install Composer dependencies" - env: - SYMFONY_REQUIRE: ${{ matrix.sf_version }} - run: composer update --prefer-source --no-interaction --optimize-autoloader + uses: "ramsey/composer-install@v2" + with: + composer-options: "--optimize-autoloader" + dependency-versions: "${{ matrix.dependencies }}" + +# - name: "Install Composer dependencies (default)" +# if: matrix.strategy != 'lowest' +# env: +# SYMFONY_REQUIRE: ${{ matrix.sf_version }} +# run: composer update --no-interaction --optimize-autoloader +# +# - name: "Install Composer dependencies (lowest)" +# if: matrix.strategy == 'lowest' +# env: +# SYMFONY_REQUIRE: ${{ matrix.sf_version }} +# run: composer update --no-interaction --prefer-stable --prefer-lowest --optimize-autoloader - name: "Run tests" env: - SYMFONY_DEPRECATIONS_HELPER: 'max[self]=2' - run: ./vendor/bin/simple-phpunit -v --testsuite doctrine + SYMFONY_DEPRECATIONS_HELPER: 'max[self]=3&max[indirect]=1' + run: ./vendor/bin/simple-phpunit -v diff --git a/.php-cs-fixer.php b/.php-cs-fixer.php index c78dec0..7236c5b 100644 --- a/.php-cs-fixer.php +++ b/.php-cs-fixer.php @@ -8,6 +8,7 @@ return (new PhpCsFixer\Config()) ->setRules([ '@Symfony' => true, + 'no_superfluous_phpdoc_tags' => false, ]) ->setFinder($finder) ; diff --git a/composer.json b/composer.json index 8744037..c775a39 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "require": { "php": "^7.4 || ^8.0", "geocoder-php/plugin": "^1.5", + "php-http/curl-client": "^2.3", "php-http/discovery": "^1.14", "symfony/console": "^4.4 || ^5.0 || ^6.0", "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", @@ -20,6 +21,8 @@ "willdurand/geocoder": "^4.6" }, "require-dev": { + "doctrine/annotations": "^1.11.1 || ^2.0", + "doctrine/doctrine-bundle": "^2.3", "doctrine/orm": "~2.8", "fakerphp/faker": "^1.20", "friendsofphp/php-cs-fixer": "^3.13", @@ -56,9 +59,10 @@ "nyholm/nsa": "^1.3", "nyholm/psr7": "^1.5", "nyholm/symfony-bundle-test": "dev-master", - "php-http/curl-client": "^2.2", + "php-http/mock-client": "^1.6", "php-http/message": "^1.13", "phpstan/phpstan": "^1.9.2", + "psr/http-client": "^1.0", "symfony/cache": "^4.4 || ^5.0 || ^6.0", "symfony/config": "^4.4 || ^5.0 || ^6.0", "symfony/phpunit-bridge": "^5.2 || ^6.0", @@ -83,7 +87,8 @@ }, "config": { "allow-plugins": { - "symfony/flex": true + "symfony/flex": true, + "php-http/discovery": false }, "sort-packages": true }, diff --git a/doc/doctrine.md b/doc/doctrine.md index f89894d..83c5afa 100644 --- a/doc/doctrine.md +++ b/doc/doctrine.md @@ -80,7 +80,7 @@ You have to indicate which provider to use to reverse geocode the address. Here - '@bazinga_geocoder.provider.acme' - '@Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver' tags: - - doctrine.event_subscriber + - { name: doctrine.event_listener, event: onFlush } ``` It is done! @@ -131,5 +131,5 @@ Then update your service configuration to register the `AttributeDriver`: - '@bazinga_geocoder.provider.acme' - '@Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver' tags: - - doctrine.event_subscriber + - { name: doctrine.event_listener, event: onFlush } ``` diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 970cd03..85c1a78 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -13,11 +13,6 @@ ./tests - ./tests/Doctrine - - - - ./tests/Doctrine diff --git a/src/BazingaGeocoderBundle.php b/src/BazingaGeocoderBundle.php index 0107005..e9aba85 100644 --- a/src/BazingaGeocoderBundle.php +++ b/src/BazingaGeocoderBundle.php @@ -24,8 +24,6 @@ class BazingaGeocoderBundle extends Bundle { /** - * {@inheritdoc} - * * @return void */ public function build(ContainerBuilder $container) @@ -37,9 +35,6 @@ public function build(ContainerBuilder $container) $container->addCompilerPass(new FactoryValidatorPass()); } - /** - * {@inheritdoc} - */ public function getPath(): string { return \dirname(__DIR__); diff --git a/src/Command/GeocodeCommand.php b/src/Command/GeocodeCommand.php index 28e0156..73f4edd 100644 --- a/src/Command/GeocodeCommand.php +++ b/src/Command/GeocodeCommand.php @@ -35,8 +35,6 @@ public function __construct(ProviderAggregator $geocoder) } /** - * {@inheritdoc} - * * @return void */ protected function configure() @@ -58,8 +56,6 @@ protected function configure() } /** - * {@inheritdoc} - * * @return int */ protected function execute(InputInterface $input, OutputInterface $output) diff --git a/src/DataCollector/GeocoderDataCollector.php b/src/DataCollector/GeocoderDataCollector.php index a98b19b..688932f 100644 --- a/src/DataCollector/GeocoderDataCollector.php +++ b/src/DataCollector/GeocoderDataCollector.php @@ -45,8 +45,6 @@ public function reset() } /** - * {@inheritdoc} - * * @return void */ public function collect(Request $request, Response $response, \Throwable $exception = null) @@ -117,9 +115,6 @@ public function addInstance(ProfilingPlugin $instance) $this->data['providers'][] = $instance->getName(); } - /** - * {@inheritdoc} - */ public function getName(): string { return 'geocoder'; diff --git a/src/DependencyInjection/Compiler/FactoryValidatorPass.php b/src/DependencyInjection/Compiler/FactoryValidatorPass.php index ed202d1..6f54fba 100644 --- a/src/DependencyInjection/Compiler/FactoryValidatorPass.php +++ b/src/DependencyInjection/Compiler/FactoryValidatorPass.php @@ -29,8 +29,6 @@ class FactoryValidatorPass implements CompilerPassInterface private static $factoryServiceIds = []; /** - * {@inheritdoc} - * * @return void */ public function process(ContainerBuilder $container) diff --git a/src/DependencyInjection/Compiler/ProfilerPass.php b/src/DependencyInjection/Compiler/ProfilerPass.php index 982bcd3..3ba985d 100644 --- a/src/DependencyInjection/Compiler/ProfilerPass.php +++ b/src/DependencyInjection/Compiler/ProfilerPass.php @@ -25,8 +25,6 @@ class ProfilerPass implements CompilerPassInterface { /** - * {@inheritdoc} - * * @return void */ public function process(ContainerBuilder $container) diff --git a/src/Doctrine/ORM/GeocoderListener.php b/src/Doctrine/ORM/GeocoderListener.php index 99bc52c..070d3a8 100644 --- a/src/Doctrine/ORM/GeocoderListener.php +++ b/src/Doctrine/ORM/GeocoderListener.php @@ -36,8 +36,6 @@ public function __construct(Provider $geocoder, DriverInterface $driver) } /** - * {@inheritdoc} - * * @return list */ public function getSubscribedEvents(): array @@ -52,7 +50,7 @@ public function getSubscribedEvents(): array */ public function onFlush(OnFlushEventArgs $args) { - $em = $args->getEntityManager(); + $em = method_exists($args, 'getObjectManager') ? $args->getObjectManager() : $args->getEntityManager(); $uow = $em->getUnitOfWork(); foreach ($uow->getScheduledEntityInsertions() as $entity) { diff --git a/src/Mapping/Driver/AnnotationDriver.php b/src/Mapping/Driver/AnnotationDriver.php index eb675f0..e2ae849 100644 --- a/src/Mapping/Driver/AnnotationDriver.php +++ b/src/Mapping/Driver/AnnotationDriver.php @@ -30,9 +30,6 @@ public function __construct(Reader $reader) $this->reader = $reader; } - /** - * {@inheritdoc} - */ public function isGeocodeable($object): bool { $reflection = ClassUtils::newReflectionObject($object); @@ -40,9 +37,6 @@ public function isGeocodeable($object): bool return (bool) $this->reader->getClassAnnotation($reflection, Annotations\Geocodeable::class); } - /** - * {@inheritdoc} - */ public function loadMetadataFromObject($object) { $reflection = ClassUtils::newReflectionObject($object); diff --git a/src/Mapping/Driver/AttributeDriver.php b/src/Mapping/Driver/AttributeDriver.php index 21c6861..2b1cbaa 100644 --- a/src/Mapping/Driver/AttributeDriver.php +++ b/src/Mapping/Driver/AttributeDriver.php @@ -22,9 +22,6 @@ */ final class AttributeDriver implements DriverInterface { - /** - * {@inheritdoc} - */ public function isGeocodeable($object): bool { if (PHP_VERSION_ID < 80000) { @@ -37,8 +34,6 @@ public function isGeocodeable($object): bool } /** - * {@inheritdoc} - * * @throws MappingException */ public function loadMetadataFromObject($object): ClassMetadata diff --git a/src/ProviderFactory/AbstractFactory.php b/src/ProviderFactory/AbstractFactory.php index 2fcf9d5..069ca66 100644 --- a/src/ProviderFactory/AbstractFactory.php +++ b/src/ProviderFactory/AbstractFactory.php @@ -32,7 +32,7 @@ abstract class AbstractFactory implements ProviderFactoryInterface protected ?ClientInterface $httpClient; - public function __construct(?ClientInterface $httpClient = null) + public function __construct(ClientInterface $httpClient = null) { $this->httpClient = $httpClient; } @@ -42,9 +42,6 @@ public function __construct(?ClientInterface $httpClient = null) */ abstract protected function getProvider(array $config): Provider; - /** - * {@inheritdoc} - */ public function createProvider(array $options = []): Provider { $this->verifyDependencies(); @@ -56,9 +53,6 @@ public function createProvider(array $options = []): Provider return $this->getProvider($config); } - /** - * {@inheritdoc} - */ public static function validate(array $options, $providerName) { static::verifyDependencies(); diff --git a/src/ProviderFactory/ChainFactory.php b/src/ProviderFactory/ChainFactory.php index a01b81f..77eb440 100644 --- a/src/ProviderFactory/ChainFactory.php +++ b/src/ProviderFactory/ChainFactory.php @@ -42,9 +42,6 @@ protected function getProvider(array $config): Provider return $provider; } - /** - * {@inheritdoc} - */ protected static function configureOptionResolver(OptionsResolver $resolver) { parent::configureOptionResolver($resolver); diff --git a/src/Validator/Constraint/Address.php b/src/Validator/Constraint/Address.php index c2e8fa2..36cfe12 100644 --- a/src/Validator/Constraint/Address.php +++ b/src/Validator/Constraint/Address.php @@ -26,15 +26,12 @@ class Address extends Constraint { public const INVALID_ADDRESS_ERROR = '2243aa07-2ea7-4eb7-962c-6a9586593f2c'; - /** - * @var string[] - */ protected const ERROR_NAMES = [ self::INVALID_ADDRESS_ERROR => 'INVALID_ADDRESS_ERROR', ]; /** - * @var string[] + * @var array * * @deprecated since BazingaGeocoderBundle 5.17, use const ERROR_NAMES instead */ diff --git a/tests/DependencyInjection/Compiler/AddProvidersPassTest.php b/tests/DependencyInjection/Compiler/AddProvidersPassTest.php index 9c95ef2..7144744 100644 --- a/tests/DependencyInjection/Compiler/AddProvidersPassTest.php +++ b/tests/DependencyInjection/Compiler/AddProvidersPassTest.php @@ -15,7 +15,7 @@ use Bazinga\GeocoderBundle\DependencyInjection\Compiler\AddProvidersPass; use Geocoder\Provider\BingMaps\BingMaps; use Geocoder\ProviderAggregator; -use Http\Client\Curl\Client; +use Http\Mock\Client; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; diff --git a/tests/Doctrine/ORM/GeocoderListenerTest.php b/tests/Doctrine/ORM/GeocoderListenerTest.php deleted file mode 100644 index 09b0813..0000000 --- a/tests/Doctrine/ORM/GeocoderListenerTest.php +++ /dev/null @@ -1,411 +0,0 @@ - - */ -final class GeocoderListenerTest extends OrmTestCase -{ - private EntityManager $em; - private GeocoderListener $listener; - - public static function setUpBeforeClass(): void - { - parent::setUpBeforeClass(); - - if (!class_exists(OrmTestCase::class)) { - /* - * We check for DoctrineTestCase because it is in the same package as OrmTestCase and we want to be able to - * fake OrmTestCase - */ - static::fail('Doctrine\Tests\OrmTestCase was not found.'); - } - } - - protected function setUp(): void - { - parent::setUp(); - - AnnotationRegistry::registerLoader('class_exists'); - - $conn = DriverManager::getConnection([ - 'driver' => 'pdo_sqlite', - 'memory' => true, - ]); - - $this->em = $this->getTestEntityManager($conn); - - $reader = new SimpleAnnotationReader(); - $reader->addNamespace('Bazinga\GeocoderBundle\Mapping\Annotations'); - $reader->addNamespace('Doctrine\ORM\Mapping'); - - $driver = new AnnotationDriver($reader); - $geocoder = Nominatim::withOpenStreetMapServer(new Client(), 'BazingaGeocoderBundle/Test'); - $this->listener = new GeocoderListener($geocoder, $driver); - - $this->em->getEventManager()->addEventSubscriber($this->listener); - - $sm = new SchemaTool($this->em); - $sm->createSchema([ - $this->em->getClassMetadata('Bazinga\GeocoderBundle\Tests\Doctrine\ORM\DummyWithProperty'), - $this->em->getClassMetadata('Bazinga\GeocoderBundle\Tests\Doctrine\ORM\DummyWithEmptyProperty'), - $this->em->getClassMetadata('Bazinga\GeocoderBundle\Tests\Doctrine\ORM\DummyWithGetter'), - $this->em->getClassMetadata('Bazinga\GeocoderBundle\Tests\Doctrine\ORM\DummyWithInvalidGetter'), - ]); - } - - public function testPersistForProperty(): void - { - $dummy = new DummyWithProperty(); - $dummy->address = 'Berlin, Germany'; - - $this->em->persist($dummy); - $this->em->flush(); - - self::assertNotNull($dummy->latitude); - self::assertNotNull($dummy->longitude); - - $clone = clone $dummy; - $dummy->address = 'Paris, France'; - - $this->em->persist($dummy); - $this->em->flush(); - - self::assertNotEquals($clone->latitude, $dummy->latitude); - self::assertNotEquals($clone->longitude, $dummy->longitude); - } - - public function testPersistForGetter(): void - { - $dummy = new DummyWithGetter(); - $dummy->setAddress('Berlin, Germany'); - - $this->em->persist($dummy); - $this->em->flush(); - - self::assertNotNull($dummy->getLatitude()); - self::assertNotNull($dummy->getLongitude()); - - $clone = clone $dummy; - $dummy->setAddress('Paris, France'); - - $this->em->persist($dummy); - $this->em->flush(); - - self::assertNotEquals($clone->getLatitude(), $dummy->getLatitude()); - self::assertNotEquals($clone->getLongitude(), $dummy->getLongitude()); - } - - public function testPersistForInvalidGetter(): void - { - $dummy = new DummyWithInvalidGetter(); - $dummy->setAddress('Berlin, Germany'); - - $this->em->persist($dummy); - - $this->expectException(\Exception::class); - - $this->em->flush(); - } - - public function testPersistForEmptyProperty(): void - { - $dummy = new DummyWithEmptyProperty(); - $dummy->address = ''; - - $this->em->persist($dummy); - $this->em->flush(); - - self::assertNull($dummy->latitude); - self::assertNull($dummy->longitude); - } - - public function testDoesNotGeocodeIfAddressNotChanged(): void - { - $this->em->getEventManager()->removeEventListener(Events::onFlush, $this->listener); - - $reader = new SimpleAnnotationReader(); - $reader->addNamespace('Bazinga\GeocoderBundle\Mapping\Annotations'); - $reader->addNamespace('Doctrine\ORM\Mapping'); - - $driver = new AnnotationDriver($reader); - - $client = new TrackedCurlClient(); - $geocoder = Nominatim::withOpenStreetMapServer($client, 'BazingaGeocoderBundle/Test'); - $listener = new GeocoderListener($geocoder, $driver); - - $this->em->getEventManager()->addEventSubscriber($listener); - - $dummy = new DummyWithProperty(); - $dummy->address = 'Frankfurt, Germany'; - - $this->em->persist($dummy); - $this->em->flush(); - - $dummy->latitude = 0; - $dummy->longitude = 0; - - $this->em->flush(); - - self::assertSame('Frankfurt, Germany', $dummy->address); - self::assertSame(0, $dummy->latitude); - self::assertSame(0, $dummy->longitude); - self::assertCount(1, $client->getResponses()); - } -} - -/** - * @Geocodeable - * - * @Entity - */ -class DummyWithProperty -{ - /** - * @Id - * - * @GeneratedValue - * - * @Column(type="integer") - */ - public $id; - - /** - * @Latitude - * - * @Column() - */ - public $latitude; - - /** - * @Longitude - * - * @Column - */ - public $longitude; - - /** - * @Address - * - * @Column - */ - public $address; -} - -/** - * @Geocodeable - * - * @Entity - */ -class DummyWithGetter -{ - /** - * @Id @GeneratedValue - * - * @Column(type="integer") - */ - private $id; - - /** - * @Latitude - * - * @Column - */ - private $latitude; - - /** - * @Longitude - * - * @Column - */ - private $longitude; - - /** - * @Column - */ - private $_address; - - public function setAddress($address) - { - $this->_address = $address; - } - - /** - * @Address - */ - public function getAddress() - { - return $this->_address; - } - - public function getLatitude() - { - return $this->latitude; - } - - public function setLatitude($latitude) - { - $this->latitude = $latitude; - } - - public function getLongitude() - { - return $this->longitude; - } - - public function setLongitude($longitude) - { - $this->longitude = $longitude; - } -} - -/** - * @Geocodeable - * - * @Entity - */ -class DummyWithInvalidGetter -{ - /** - * @Id @GeneratedValue - * - * @Column(type="integer") - */ - private $id; - - /** - * @Latitude - * - * @Column - */ - private $latitude; - - /** - * @Longitude - * - * @Column - */ - private $longitude; - - /** - * @Column - */ - private $_address; - - public function setAddress($address) - { - $this->_address = $address; - } - - /** - * @Address - */ - public function getAddress($requiredParameter) - { - return $this->_address; - } - - public function getLatitude() - { - return $this->latitude; - } - - public function setLatitude($latitude) - { - $this->latitude = $latitude; - } - - public function getLongitude() - { - return $this->longitude; - } - - public function setLongitude($longitude) - { - $this->longitude = $longitude; - } -} - -/** - * @Geocodeable - * - * @Entity - */ -class DummyWithEmptyProperty -{ - /** - * @Id @GeneratedValue - * - * @Column(type="integer") - */ - public $id; - - /** - * @Latitude - * - * @Column(nullable=true) - */ - public $latitude; - - /** - * @Longitude - * - * @Column(nullable=true) - */ - public $longitude; - - /** - * @Address - * - * @Column - */ - public $address; -} - -class TrackedCurlClient extends Client -{ - private $responses = []; - - public function sendRequest(RequestInterface $request): ResponseInterface - { - return $this->responses[] = parent::sendRequest($request); - } - - public function getResponses(): array - { - return $this->responses; - } -} diff --git a/tests/Functional/CustomTestKernel.php b/tests/Functional/CustomTestKernel.php index e3207a5..393172c 100644 --- a/tests/Functional/CustomTestKernel.php +++ b/tests/Functional/CustomTestKernel.php @@ -31,9 +31,6 @@ class CustomTestKernel extends TestKernel { private $warmupDir; - /** - * {@inheritdoc} - */ public function reboot(?string $warmupDir) { $this->shutdown(); diff --git a/tests/Functional/Fixtures/Entity/DummyWithEmptyProperty.php b/tests/Functional/Fixtures/Entity/DummyWithEmptyProperty.php new file mode 100644 index 0000000..5431a31 --- /dev/null +++ b/tests/Functional/Fixtures/Entity/DummyWithEmptyProperty.php @@ -0,0 +1,70 @@ +_address = $address; + } + + /** + * @Address + */ + #[Address] + public function getAddress() + { + return $this->_address; + } + + public function getLatitude() + { + return $this->latitude; + } + + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + public function getLongitude() + { + return $this->longitude; + } + + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } +} diff --git a/tests/Functional/Fixtures/Entity/DummyWithInvalidGetter.php b/tests/Functional/Fixtures/Entity/DummyWithInvalidGetter.php new file mode 100644 index 0000000..63d3e8c --- /dev/null +++ b/tests/Functional/Fixtures/Entity/DummyWithInvalidGetter.php @@ -0,0 +1,101 @@ +_address = $address; + } + + /** + * @Address + */ + #[Address] + public function getAddress($requiredParameter) + { + return $this->_address; + } + + public function getLatitude() + { + return $this->latitude; + } + + public function setLatitude($latitude) + { + $this->latitude = $latitude; + } + + public function getLongitude() + { + return $this->longitude; + } + + public function setLongitude($longitude) + { + $this->longitude = $longitude; + } +} diff --git a/tests/Functional/Fixtures/Entity/DummyWithProperty.php b/tests/Functional/Fixtures/Entity/DummyWithProperty.php new file mode 100644 index 0000000..eb88e3e --- /dev/null +++ b/tests/Functional/Fixtures/Entity/DummyWithProperty.php @@ -0,0 +1,72 @@ + + */ +final class GeocoderListenerTest extends KernelTestCase +{ + protected function tearDown(): void + { + $em = self::getContainer()->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->dropSchema($em->getMetadataFactory()->getAllMetadata()); + } + + protected static function getKernelClass(): string + { + return TestKernel::class; + } + + protected static function createKernel(array $options = []): KernelInterface + { + /** + * @var TestKernel $kernel + */ + $kernel = parent::createKernel($options); + $kernel->addTestBundle(DoctrineBundle::class); + $kernel->addTestBundle(BazingaGeocoderBundle::class); + $kernel->addTestCompilerPass(new PublicServicePass('|[Bb]azinga:*|')); + $kernel->addTestCompilerPass(new PublicServicePass('|[gG]eocoder:*|')); + if (defined(ConnectionFactory::class.'::DEFAULT_SCHEME_MAP')) { + $kernel->addTestConfig(static function (ContainerBuilder $container) { + $container->prependExtensionConfig('doctrine', [ + 'orm' => [ + 'report_fields_where_declared' => true, + ], + ]); + }); + } + $kernel->handleOptions($options); + + return $kernel; + } + + public function testPersistForProperty(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener_'.(PHP_VERSION_ID >= 80000 ? 'php8' : 'php7').'.yml'); + }]); + + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $httpClient = $container->get(Client::class); + $httpClient->on(new RequestMatcher(), function (RequestInterface $request) { + if ('https://nominatim.openstreetmap.org/search?format=jsonv2&q=Berlin%2C%20Germany&addressdetails=1&extratags=1&limit=5' === (string) $request->getUri()) { + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":159647018,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":62422,"lat":"52.5170365","lon":"13.3888599","category":"boundary","type":"administrative","place_rank":8,"importance":0.7875390282491362,"addresstype":"city","name":"Berlin","display_name":"Berlin, Deutschland","address":{"city":"Berlin","ISO3166-2-lvl4":"DE-BE","country":"Deutschland","country_code":"de"},"extratags":{"ele": "35", "email": "info@berlin.de", "place": "city", "capital": "yes", "website": "http://www.berlin.de", "de:place": "city", "ref:nuts": "DE3;DE30;DE300", "wikidata": "Q64", "wikipedia": "de:Berlin", "population": "3769962", "ref:LOCODE": "DEBER", "ref:nuts:1": "DE3", "ref:nuts:2": "DE30", "ref:nuts:3": "DE300", "state_code": "BE", "name:prefix": "Land und Kreisfreie Stadt", "linked_place": "city", "official_status": "Land", "contact:facebook": "http://www.facebook.com/Berlin", "name:prefix:city": "Kreisfreie Stadt", "openGeoDB:loc_id": "14356", "capital_ISO3166-1": "yes", "name:prefix:state": "Land", "source:population": "https://download.statistik-berlin-brandenburg.de/fa93e3bd19a2e885/a5ecfb2fff6a/SB_A01-05-00_2020h02_BE.pdf", "license_plate_code": "B", "official_status:de": "Land", "official_status:en": "State", "official_status:ru": "земля", "geographical_region": "Barnim;Berliner Urstromtal;Teltow;Nauener Platte", "blind:description:de": "Auf www.berlinfuerblinde.de gibt es einen kostenlosen Audioguide und weitere Informationen.", "de:regionalschluessel": "110000000000", "openGeoDB:postal_codes": "10178,10115,10117,10119,10179,10243,10245,10247,10249,10315,10317,10318,10319,10365,10367,10369,10405,10407,10409,10435,10437,10439,10551,10553,10555,10557,10559,10585,10587,10589,10623,10625,10627,10629,10707,10709,10711,10713,10715,10717,10719,10777,10", "report_problems:website": "https://ordnungsamt.berlin.de/", "TMC:cid_58:tabcd_1:Class": "Area", "openGeoDB:license_plate_code": "B", "TMC:cid_58:tabcd_1:LCLversion": "12.0", "openGeoDB:telephone_area_code": "030", "TMC:cid_58:tabcd_1:LocationCode": "266", "de:amtlicher_gemeindeschluessel": "11000000", "openGeoDB:community_identification_number": "11000000"},"boundingbox":["52.3382448","52.6755087","13.0883450","13.7611609"]}]'); + + return $response; + } + + if ('https://nominatim.openstreetmap.org/search?format=jsonv2&q=Paris%2C%20France&addressdetails=1&extratags=1&limit=5' === (string) $request->getUri()) { + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":115350921,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":7444,"lat":"48.8588897","lon":"2.3200410217200766","category":"boundary","type":"administrative","place_rank":15,"importance":0.8317101715588673,"addresstype":"suburb","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"suburb":"Paris","city_district":"Paris","city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"capital": "yes", "wikidata": "Q90", "ref:INSEE": "75056", "wikipedia": "fr:Paris", "population": "2187526", "ref:FR:MGP": "T1", "source:population": "INSEE 2020"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]},{"place_id":114827617,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":71525,"lat":"48.8534951","lon":"2.3483915","category":"boundary","type":"administrative","place_rank":12,"importance":0.8317101715588673,"addresstype":"city","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"rank": "0", "capital": "yes", "ref:nuts": "FR101", "wikidata": "Q90", "ref:INSEE": "75", "wikipedia": "fr:Paris", "is_capital": "country", "population": "2165423", "ref:nuts:3": "FR101", "linked_place": "city", "source:name:oc": "ieo-bdtopoc", "contact:website": "http://www.paris.fr", "population:date": "2019", "capital_ISO3166-1": "yes", "source:population": "INSEE 2022"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]},{"place_id":114994164,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":1641193,"lat":"48.8588897","lon":"2.3200410217200766","category":"boundary","type":"administrative","place_rank":14,"importance":0.4283953917728152,"addresstype":"city_district","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"city_district":"Paris","city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"wikidata": "Q2863958", "ref:INSEE": "751", "wikipedia": "fr:Arrondissement de Paris"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]}]'); + + return $response; + } + + self::fail(sprintf('Unexpected http call "%s %s".', $request->getMethod(), (string) $request->getUri())); + }); + + $dummy = new DummyWithProperty(); + $dummy->address = 'Berlin, Germany'; + + $em = $container->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); + + $em->persist($dummy); + $em->flush(); + + self::assertNotNull($dummy->latitude); + self::assertNotNull($dummy->longitude); + + $clone = clone $dummy; + $dummy->address = 'Paris, France'; + + $em->persist($dummy); + $em->flush(); + + self::assertNotEquals($clone->latitude, $dummy->latitude); + self::assertNotEquals($clone->longitude, $dummy->longitude); + } + + public function testPersistForGetter(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener_'.(PHP_VERSION_ID >= 80000 ? 'php8' : 'php7').'.yml'); + }]); + + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $httpClient = $container->get(Client::class); + $httpClient->on(new RequestMatcher(), function (RequestInterface $request) { + if ('https://nominatim.openstreetmap.org/search?format=jsonv2&q=Berlin%2C%20Germany&addressdetails=1&extratags=1&limit=5' === (string) $request->getUri()) { + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":159647018,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":62422,"lat":"52.5170365","lon":"13.3888599","category":"boundary","type":"administrative","place_rank":8,"importance":0.7875390282491362,"addresstype":"city","name":"Berlin","display_name":"Berlin, Deutschland","address":{"city":"Berlin","ISO3166-2-lvl4":"DE-BE","country":"Deutschland","country_code":"de"},"extratags":{"ele": "35", "email": "info@berlin.de", "place": "city", "capital": "yes", "website": "http://www.berlin.de", "de:place": "city", "ref:nuts": "DE3;DE30;DE300", "wikidata": "Q64", "wikipedia": "de:Berlin", "population": "3769962", "ref:LOCODE": "DEBER", "ref:nuts:1": "DE3", "ref:nuts:2": "DE30", "ref:nuts:3": "DE300", "state_code": "BE", "name:prefix": "Land und Kreisfreie Stadt", "linked_place": "city", "official_status": "Land", "contact:facebook": "http://www.facebook.com/Berlin", "name:prefix:city": "Kreisfreie Stadt", "openGeoDB:loc_id": "14356", "capital_ISO3166-1": "yes", "name:prefix:state": "Land", "source:population": "https://download.statistik-berlin-brandenburg.de/fa93e3bd19a2e885/a5ecfb2fff6a/SB_A01-05-00_2020h02_BE.pdf", "license_plate_code": "B", "official_status:de": "Land", "official_status:en": "State", "official_status:ru": "земля", "geographical_region": "Barnim;Berliner Urstromtal;Teltow;Nauener Platte", "blind:description:de": "Auf www.berlinfuerblinde.de gibt es einen kostenlosen Audioguide und weitere Informationen.", "de:regionalschluessel": "110000000000", "openGeoDB:postal_codes": "10178,10115,10117,10119,10179,10243,10245,10247,10249,10315,10317,10318,10319,10365,10367,10369,10405,10407,10409,10435,10437,10439,10551,10553,10555,10557,10559,10585,10587,10589,10623,10625,10627,10629,10707,10709,10711,10713,10715,10717,10719,10777,10", "report_problems:website": "https://ordnungsamt.berlin.de/", "TMC:cid_58:tabcd_1:Class": "Area", "openGeoDB:license_plate_code": "B", "TMC:cid_58:tabcd_1:LCLversion": "12.0", "openGeoDB:telephone_area_code": "030", "TMC:cid_58:tabcd_1:LocationCode": "266", "de:amtlicher_gemeindeschluessel": "11000000", "openGeoDB:community_identification_number": "11000000"},"boundingbox":["52.3382448","52.6755087","13.0883450","13.7611609"]}]'); + + return $response; + } + + if ('https://nominatim.openstreetmap.org/search?format=jsonv2&q=Paris%2C%20France&addressdetails=1&extratags=1&limit=5' === (string) $request->getUri()) { + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":115350921,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":7444,"lat":"48.8588897","lon":"2.3200410217200766","category":"boundary","type":"administrative","place_rank":15,"importance":0.8317101715588673,"addresstype":"suburb","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"suburb":"Paris","city_district":"Paris","city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"capital": "yes", "wikidata": "Q90", "ref:INSEE": "75056", "wikipedia": "fr:Paris", "population": "2187526", "ref:FR:MGP": "T1", "source:population": "INSEE 2020"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]},{"place_id":114827617,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":71525,"lat":"48.8534951","lon":"2.3483915","category":"boundary","type":"administrative","place_rank":12,"importance":0.8317101715588673,"addresstype":"city","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"rank": "0", "capital": "yes", "ref:nuts": "FR101", "wikidata": "Q90", "ref:INSEE": "75", "wikipedia": "fr:Paris", "is_capital": "country", "population": "2165423", "ref:nuts:3": "FR101", "linked_place": "city", "source:name:oc": "ieo-bdtopoc", "contact:website": "http://www.paris.fr", "population:date": "2019", "capital_ISO3166-1": "yes", "source:population": "INSEE 2022"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]},{"place_id":114994164,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":1641193,"lat":"48.8588897","lon":"2.3200410217200766","category":"boundary","type":"administrative","place_rank":14,"importance":0.4283953917728152,"addresstype":"city_district","name":"Paris","display_name":"Paris, Île-de-France, France métropolitaine, France","address":{"city_district":"Paris","city":"Paris","ISO3166-2-lvl6":"FR-75","state":"Île-de-France","ISO3166-2-lvl4":"FR-IDF","region":"France métropolitaine","country":"France","country_code":"fr"},"extratags":{"wikidata": "Q2863958", "ref:INSEE": "751", "wikipedia": "fr:Arrondissement de Paris"},"boundingbox":["48.8155755","48.9021560","2.2241220","2.4697602"]}]'); + + return $response; + } + + self::fail(sprintf('Unexpected http call "%s %s".', $request->getMethod(), (string) $request->getUri())); + }); + + $em = $container->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); + + $dummy = new DummyWithGetter(); + $dummy->setAddress('Berlin, Germany'); + + $em->persist($dummy); + $em->flush(); + + self::assertNotNull($dummy->getLatitude()); + self::assertNotNull($dummy->getLongitude()); + + $clone = clone $dummy; + $dummy->setAddress('Paris, France'); + + $em->persist($dummy); + $em->flush(); + + self::assertNotEquals($clone->getLatitude(), $dummy->getLatitude()); + self::assertNotEquals($clone->getLongitude(), $dummy->getLongitude()); + } + + public function testPersistForInvalidGetter(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener_'.(PHP_VERSION_ID >= 80000 ? 'php8' : 'php7').'.yml'); + }]); + + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $em = $container->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); + + $dummy = new DummyWithInvalidGetter(); + $dummy->setAddress('Berlin, Germany'); + + $em->persist($dummy); + + $this->expectException(\Exception::class); + + $em->flush(); + } + + public function testPersistForEmptyProperty(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener_'.(PHP_VERSION_ID >= 80000 ? 'php8' : 'php7').'.yml'); + }]); + + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $em = $container->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); + + $dummy = new DummyWithEmptyProperty(); + $dummy->address = ''; + + $em->persist($dummy); + $em->flush(); + + self::assertNull($dummy->latitude); + self::assertNull($dummy->longitude); + } + + public function testDoesNotGeocodeIfAddressNotChanged(): void + { + $kernel = self::bootKernel(['config' => static function (TestKernel $kernel) { + $kernel->addTestConfig(__DIR__.'/config/framework.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener.yml'); + $kernel->addTestConfig(__DIR__.'/config/listener_'.(PHP_VERSION_ID >= 80000 ? 'php8' : 'php7').'.yml'); + }]); + + $container = method_exists(__CLASS__, 'getContainer') ? self::getContainer() : $kernel->getContainer(); + + $httpRequests = 0; + $httpClient = $container->get(Client::class); + $httpClient->on(new RequestMatcher(), function (RequestInterface $request) use (&$httpRequests) { + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::any()) + ->method('getStatusCode') + ->willReturn(200); + + if ('https://nominatim.openstreetmap.org/search?format=jsonv2&q=Frankfurt%2C%20Germany&addressdetails=1&extratags=1&limit=5' === (string) $request->getUri() && 0 === $httpRequests) { + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":152571305,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":62400,"lat":"50.1106444","lon":"8.6820917","category":"boundary","type":"administrative","place_rank":12,"importance":0.6941325622496303,"addresstype":"city","name":"Frankfurt am Main","display_name":"Frankfurt am Main, Hessen, Deutschland","address":{"city":"Frankfurt am Main","state":"Hessen","ISO3166-2-lvl4":"DE-HE","country":"Deutschland","country_code":"de"},"extratags":{"ele": "112", "flag": "File:Flag of Frankfurt am Main.svg", "logo": "File:Frankfurt am Main logo.svg", "de:place": "city", "nickname": "Europastadt", "wikidata": "Q1794", "wikipedia": "de:Frankfurt am Main", "population": "701350", "ref:LOCODE": "DEFRA", "ref:nuts:3": "DE712", "border_type": "county", "name:prefix": "Stadt", "nickname:de": "Europastadt", "nickname:la": "Urbem Europaeam", "nickname:nl": "Bankfurt", "coat_of_arms": "File:Wappen Frankfurt am Main.svg", "linked_place": "city", "wikimedia_commons": "Category:Frankfurt am Main", "license_plate_code": "F", "de:regionalschluessel": "064120000000", "TMC:cid_58:tabcd_1:Class": "Area", "TMC:cid_58:tabcd_1:LCLversion": "9.00", "TMC:cid_58:tabcd_1:LocationCode": "414", "de:amtlicher_gemeindeschluessel": "06412000"},"boundingbox":["50.0153529","50.2271424","8.4727605","8.8004049"]},{"place_id":160849350,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":62523,"lat":"52.3412273","lon":"14.549452","category":"boundary","type":"administrative","place_rank":12,"importance":0.5626903004005709,"addresstype":"city","name":"Frankfurt (Oder)","display_name":"Frankfurt (Oder), Brandenburg, Deutschland","address":{"city":"Frankfurt (Oder)","state":"Brandenburg","ISO3166-2-lvl4":"DE-BB","country":"Deutschland","country_code":"de"},"extratags":{"ele": "28", "place": "city", "website": "https://www.frankfurt-oder.de/", "de:place": "city", "wikidata": "Q4024", "wikipedia": "de:Frankfurt (Oder)", "population": "61969", "ref:LOCODE": "DEFFO", "ref:nuts:3": "DE403", "name:prefix": "Kreisfreie Stadt", "linked_place": "town", "license_plate_code": "FF", "telephone_area_code": "0335", "de:regionalschluessel": "120530000000", "TMC:cid_58:tabcd_1:Class": "Area", "TMC:cid_58:tabcd_1:LCLversion": "8.00", "TMC:cid_58:tabcd_1:LocationCode": "415", "de:amtlicher_gemeindeschluessel": "12053000"},"boundingbox":["52.2528709","52.3980721","14.3948254","14.6013644"]}]'); + } else { + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[]'); + } + + ++$httpRequests; + + return $response; + }); + + $em = $container->get('doctrine.orm.entity_manager'); + + $tool = new SchemaTool($em); + $tool->createSchema($em->getMetadataFactory()->getAllMetadata()); + + $dummy = new DummyWithProperty(); + $dummy->address = 'Frankfurt, Germany'; + + $em->persist($dummy); + $em->flush(); + + $dummy->latitude = 0; + $dummy->longitude = 0; + + $em->flush(); + + self::assertSame('Frankfurt, Germany', $dummy->address); + self::assertSame(0, $dummy->latitude); + self::assertSame(0, $dummy->longitude); + self::assertSame(1, $httpRequests); + } +} diff --git a/tests/Functional/Helper/CacheHelperV8.php b/tests/Functional/Helper/CacheHelperV8.php index 5c3a40e..a8fd141 100644 --- a/tests/Functional/Helper/CacheHelperV8.php +++ b/tests/Functional/Helper/CacheHelperV8.php @@ -23,7 +23,7 @@ public function get(string $key, mixed $default = null): mixed { } - public function set(string $key, mixed $value, null|int|\DateInterval $ttl = null): bool + public function set(string $key, mixed $value, int|\DateInterval $ttl = null): bool { return true; } @@ -43,7 +43,7 @@ public function getMultiple(iterable $keys, mixed $default = null): iterable return []; } - public function setMultiple(iterable $values, null|int|\DateInterval $ttl = null): bool + public function setMultiple(iterable $values, int|\DateInterval $ttl = null): bool { return true; } diff --git a/tests/Functional/config/deprecated_httplug_client_option.yml b/tests/Functional/config/deprecated_httplug_client_option.yml index 6864ca3..c52ac75 100644 --- a/tests/Functional/config/deprecated_httplug_client_option.yml +++ b/tests/Functional/config/deprecated_httplug_client_option.yml @@ -5,8 +5,8 @@ bazinga_geocoder: acme: factory: Bazinga\GeocoderBundle\ProviderFactory\GoogleMapsFactory options: - httplug_client: '@test_http_client' + httplug_client: '@test_http_client' services: - test_http_client: - class: Http\Client\Curl\Client + test_http_client: + class: Http\Client\Curl\Client diff --git a/tests/Functional/config/framework.yml b/tests/Functional/config/framework.yml index 26f9bb5..d745002 100644 --- a/tests/Functional/config/framework.yml +++ b/tests/Functional/config/framework.yml @@ -1,2 +1,8 @@ framework: http_method_override: false + secret: 6825c711ef47cfc1530d322b62adac3e2c43844c + session: + storage_factory_id: session.storage.factory.mock_file + cookie_secure: auto + cookie_samesite: lax + handler_id: null diff --git a/tests/Functional/config/listener.yml b/tests/Functional/config/listener.yml new file mode 100644 index 0000000..6e6aa96 --- /dev/null +++ b/tests/Functional/config/listener.yml @@ -0,0 +1,11 @@ +bazinga_geocoder: + profiling: + enabled: false + providers: + acme: + factory: Bazinga\GeocoderBundle\ProviderFactory\NominatimFactory + options: + http_client: '@Http\Mock\Client' + +services: + Http\Mock\Client: ~ diff --git a/tests/Functional/config/listener_php7.yml b/tests/Functional/config/listener_php7.yml new file mode 100644 index 0000000..7d6903e --- /dev/null +++ b/tests/Functional/config/listener_php7.yml @@ -0,0 +1,43 @@ +doctrine: + dbal: + default_connection: default + connections: + default: + driver: pdo_sqlite + path: '%kernel.cache_dir%/test.sqlite' + orm: + auto_generate_proxy_classes: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + is_bundle: false + type: annotation + dir: '%kernel.project_dir%/tests/Functional/Fixtures/Entity' + prefix: 'Bazinga\GeocoderBundle\Tests\Functional\Fixtures\Entity' + alias: App + +bazinga_geocoder: + profiling: + enabled: false + providers: + acme: + factory: Bazinga\GeocoderBundle\ProviderFactory\NominatimFactory + options: + http_client: '@Http\Mock\Client' + root_url: 'https://nominatim.openstreetmap.org' + user_agent: 'geocoder-php test_suite' + +services: + Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver: + class: Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver + arguments: + - '@annotations.reader' + + Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener: + class: Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener + arguments: + - '@bazinga_geocoder.provider.acme' + - '@Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver' + tags: + - { name: doctrine.event_listener, event: onFlush } diff --git a/tests/Functional/config/listener_php8.yml b/tests/Functional/config/listener_php8.yml new file mode 100644 index 0000000..90efa68 --- /dev/null +++ b/tests/Functional/config/listener_php8.yml @@ -0,0 +1,42 @@ +doctrine: + dbal: + default_connection: default + connections: + default: + driver: pdo_sqlite + path: '%kernel.cache_dir%/test.sqlite' + orm: + auto_generate_proxy_classes: true + validate_xml_mapping: true + report_fields_where_declared: true + naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware + auto_mapping: true + mappings: + App: + is_bundle: false + type: attribute + dir: '%kernel.project_dir%/tests/Functional/Fixtures/Entity' + prefix: 'Bazinga\GeocoderBundle\Tests\Functional\Fixtures\Entity' + alias: App + +bazinga_geocoder: + profiling: + enabled: false + providers: + acme: + factory: Bazinga\GeocoderBundle\ProviderFactory\NominatimFactory + options: + http_client: '@Http\Mock\Client' + root_url: 'https://nominatim.openstreetmap.org' + user_agent: 'geocoder-php test_suite' + +services: + Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver: ~ + + Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener: + class: Bazinga\GeocoderBundle\Doctrine\ORM\GeocoderListener + arguments: + - '@bazinga_geocoder.provider.acme' + - '@Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver' + tags: + - { name: doctrine.event_listener, event: onFlush } diff --git a/tests/Mapping/Driver/AnnotationDriverTest.php b/tests/Mapping/Driver/AnnotationDriverTest.php index 818bbb5..d90de53 100644 --- a/tests/Mapping/Driver/AnnotationDriverTest.php +++ b/tests/Mapping/Driver/AnnotationDriverTest.php @@ -14,9 +14,9 @@ use Bazinga\GeocoderBundle\Mapping\Driver\AnnotationDriver; use Bazinga\GeocoderBundle\Mapping\Exception\MappingException; -use Doctrine\Common\Annotations\AnnotationRegistry; -use Doctrine\Common\Annotations\Reader; -use Doctrine\Common\Annotations\SimpleAnnotationReader; +use Bazinga\GeocoderBundle\Tests\Mapping\Driver\Fixtures\Dummy; +use Bazinga\GeocoderBundle\Tests\Mapping\Driver\Fixtures\Dummy2; +use Doctrine\Common\Annotations\AnnotationReader; use PHPUnit\Framework\TestCase; /** @@ -25,16 +25,10 @@ final class AnnotationDriverTest extends TestCase { private AnnotationDriver $driver; - private Reader $reader; protected function setUp(): void { - AnnotationRegistry::registerLoader('class_exists'); - - $this->reader = new SimpleAnnotationReader(); - $this->reader->addNamespace('Bazinga\GeocoderBundle\Mapping\Annotations'); - - $this->driver = new AnnotationDriver($this->reader); + $this->driver = new AnnotationDriver(new AnnotationReader()); } public function testLoadMetadata(): void @@ -60,28 +54,3 @@ public function testIsGeocodable(): void self::assertTrue($this->driver->isGeocodeable(new Dummy())); } } - -/** - * @Geocodeable - */ -class Dummy -{ - /** - * @Latitude - */ - public $latitude; - - /** - * @Longitude - */ - public $longitude; - - /** - * @Address - */ - public $address; -} - -class Dummy2 -{ -} diff --git a/tests/Mapping/Driver/AttributeDriverTest.php b/tests/Mapping/Driver/AttributeDriverTest.php index 1ad68f0..0df1450 100644 --- a/tests/Mapping/Driver/AttributeDriverTest.php +++ b/tests/Mapping/Driver/AttributeDriverTest.php @@ -12,12 +12,10 @@ namespace Bazinga\GeocoderBundle\Tests\Mapping\Driver; -use Bazinga\GeocoderBundle\Mapping\Annotations\Address; -use Bazinga\GeocoderBundle\Mapping\Annotations\Geocodeable; -use Bazinga\GeocoderBundle\Mapping\Annotations\Latitude; -use Bazinga\GeocoderBundle\Mapping\Annotations\Longitude; use Bazinga\GeocoderBundle\Mapping\Driver\AttributeDriver; use Bazinga\GeocoderBundle\Mapping\Exception\MappingException; +use Bazinga\GeocoderBundle\Tests\Mapping\Driver\Fixtures\Dummy; +use Bazinga\GeocoderBundle\Tests\Mapping\Driver\Fixtures\Dummy2; use PHPUnit\Framework\TestCase; /** @@ -44,7 +42,7 @@ protected function setUp(): void */ public function testLoadMetadata(): void { - $obj = new Dummy3(); + $obj = new Dummy(); $metadata = $this->driver->loadMetadataFromObject($obj); self::assertInstanceOf('ReflectionProperty', $metadata->addressProperty); @@ -58,9 +56,9 @@ public function testLoadMetadata(): void public function testLoadMetadataFromWrongObject(): void { $this->expectException(MappingException::class); - $this->expectExceptionMessage('The class '.Dummy4::class.' is not geocodeable'); + $this->expectExceptionMessage('The class '.Dummy2::class.' is not geocodeable'); - $this->driver->loadMetadataFromObject(new Dummy4()); + $this->driver->loadMetadataFromObject(new Dummy2()); } /** @@ -68,23 +66,6 @@ public function testLoadMetadataFromWrongObject(): void */ public function testIsGeocodable(): void { - self::assertTrue($this->driver->isGeocodeable(new Dummy3())); + self::assertTrue($this->driver->isGeocodeable(new Dummy())); } } - -#[Geocodeable()] -class Dummy3 -{ - #[Latitude()] - public $latitude; - - #[Longitude()] - public $longitude; - - #[Address()] - public $address; -} - -class Dummy4 -{ -} diff --git a/tests/Mapping/Driver/Fixtures/Dummy.php b/tests/Mapping/Driver/Fixtures/Dummy.php new file mode 100644 index 0000000..79a7391 --- /dev/null +++ b/tests/Mapping/Driver/Fixtures/Dummy.php @@ -0,0 +1,43 @@ +on($requestMatcher, function (RequestInterface $request) { + switch ((string) $request->getUri()) { + case 'https://nominatim.openstreetmap.org/search?format=jsonv2&q=Berlin%2C%20Germany&addressdetails=1&extratags=1&limit=5': + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[{"place_id":159647018,"licence":"Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright","osm_type":"relation","osm_id":62422,"lat":"52.5170365","lon":"13.3888599","category":"boundary","type":"administrative","place_rank":8,"importance":0.7875390282491362,"addresstype":"city","name":"Berlin","display_name":"Berlin, Deutschland","address":{"city":"Berlin","ISO3166-2-lvl4":"DE-BE","country":"Deutschland","country_code":"de"},"extratags":{"ele": "35", "email": "info@berlin.de", "place": "city", "capital": "yes", "website": "http://www.berlin.de", "de:place": "city", "ref:nuts": "DE3;DE30;DE300", "wikidata": "Q64", "wikipedia": "de:Berlin", "population": "3769962", "ref:LOCODE": "DEBER", "ref:nuts:1": "DE3", "ref:nuts:2": "DE30", "ref:nuts:3": "DE300", "state_code": "BE", "name:prefix": "Land und Kreisfreie Stadt", "linked_place": "city", "official_status": "Land", "contact:facebook": "http://www.facebook.com/Berlin", "name:prefix:city": "Kreisfreie Stadt", "openGeoDB:loc_id": "14356", "capital_ISO3166-1": "yes", "name:prefix:state": "Land", "source:population": "https://download.statistik-berlin-brandenburg.de/fa93e3bd19a2e885/a5ecfb2fff6a/SB_A01-05-00_2020h02_BE.pdf", "license_plate_code": "B", "official_status:de": "Land", "official_status:en": "State", "official_status:ru": "земля", "geographical_region": "Barnim;Berliner Urstromtal;Teltow;Nauener Platte", "blind:description:de": "Auf www.berlinfuerblinde.de gibt es einen kostenlosen Audioguide und weitere Informationen.", "de:regionalschluessel": "110000000000", "openGeoDB:postal_codes": "10178,10115,10117,10119,10179,10243,10245,10247,10249,10315,10317,10318,10319,10365,10367,10369,10405,10407,10409,10435,10437,10439,10551,10553,10555,10557,10559,10585,10587,10589,10623,10625,10627,10629,10707,10709,10711,10713,10715,10717,10719,10777,10", "report_problems:website": "https://ordnungsamt.berlin.de/", "TMC:cid_58:tabcd_1:Class": "Area", "openGeoDB:license_plate_code": "B", "TMC:cid_58:tabcd_1:LCLversion": "12.0", "openGeoDB:telephone_area_code": "030", "TMC:cid_58:tabcd_1:LocationCode": "266", "de:amtlicher_gemeindeschluessel": "11000000", "openGeoDB:community_identification_number": "11000000"},"boundingbox":["52.3382448","52.6755087","13.0883450","13.7611609"]}]'); + + return $response; + case 'https://nominatim.openstreetmap.org/search?format=jsonv2&q=Bifrost%2C%20Nine%20Realms&addressdetails=1&extratags=1&limit=5': + $response = $this->createMock(ResponseInterface::class); + $response->expects(self::once()) + ->method('getStatusCode') + ->willReturn(200); + $response->expects(self::once()) + ->method('getBody') + ->willReturn('[]'); + + return $response; + } + + self::fail(sprintf('Unexpected http call "%s %s".', $request->getMethod(), (string) $request->getUri())); + }); + + $geocoder = Nominatim::withOpenStreetMapServer($httpClient, 'BazingaGeocoderBundle/Test'); return new AddressValidator($geocoder); } From acee4784d83b87a330ed5dbc078fe804892997f3 Mon Sep 17 00:00:00 2001 From: Fran Moreno Date: Fri, 20 Oct 2023 05:50:33 +0200 Subject: [PATCH 4/8] Fix ignoring tests directory (#345) * Fix ignoring tests directory * Update phpstan-baseline.neon --- .gitattributes | 2 +- phpstan-baseline.neon | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 30da17f..c47225a 100644 --- a/.gitattributes +++ b/.gitattributes @@ -12,4 +12,4 @@ /phpspec.yml.dist export-ignore /phpunit.xml.dist export-ignore /spec/ export-ignore -/Tests/ export-ignore +/tests/ export-ignore diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 0d12c3f..e1c47c6 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -42,7 +42,7 @@ parameters: - message: "#^Cannot access offset 'options' on mixed\\.$#" - count: 6 + count: 4 path: src/DependencyInjection/BazingaGeocoderExtension.php - From f56082865e9fd76bb833a49f2e40f15122ee2151 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Thu, 2 Nov 2023 16:38:34 +0100 Subject: [PATCH 5/8] Allow using Symfony 7 and fix some deprecations --- composer.json | 16 ++++++++-------- src/Command/GeocodeCommand.php | 5 +---- src/DependencyInjection/Configuration.php | 4 +--- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/composer.json b/composer.json index c775a39..2cba9b9 100644 --- a/composer.json +++ b/composer.json @@ -15,9 +15,9 @@ "geocoder-php/plugin": "^1.5", "php-http/curl-client": "^2.3", "php-http/discovery": "^1.14", - "symfony/console": "^4.4 || ^5.0 || ^6.0", - "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0", - "symfony/options-resolver": "^4.4 || ^5.0 || ^6.0", + "symfony/console": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/framework-bundle": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/options-resolver": "^4.4 || ^5.0 || ^6.0 || ^7.0", "willdurand/geocoder": "^4.6" }, "require-dev": { @@ -63,11 +63,11 @@ "php-http/message": "^1.13", "phpstan/phpstan": "^1.9.2", "psr/http-client": "^1.0", - "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", - "symfony/yaml": "^4.4 || ^5.0 || ^6.0" + "symfony/cache": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/config": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^5.2 || ^6.0 || ^7.0", + "symfony/validator": "^4.4 || ^5.0 || ^6.0 || ^7.0", + "symfony/yaml": "^4.4 || ^5.0 || ^6.0 || ^7.0" }, "conflict": { "geocoder-php/nominatim-provider": "<5.0" diff --git a/src/Command/GeocodeCommand.php b/src/Command/GeocodeCommand.php index 73f4edd..12b0c50 100644 --- a/src/Command/GeocodeCommand.php +++ b/src/Command/GeocodeCommand.php @@ -55,10 +55,7 @@ protected function configure() ); } - /** - * @return int - */ - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { if ($input->getOption('provider')) { $this->geocoder->using($input->getOption('provider')); diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 817ec08..ca4d3da 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -30,10 +30,8 @@ public function __construct(bool $debug) /** * Generates the configuration tree builder. - * - * @return TreeBuilder The tree builder */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('bazinga_geocoder'); $rootNode = $treeBuilder->getRootNode(); From 18d6b759bb9fc044f6e67260e6a2f6d3156c672c Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Nov 2023 10:45:24 +0100 Subject: [PATCH 6/8] Regenerate the PHPStan baseline file --- phpstan-baseline.neon | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index e1c47c6..ac33fcc 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -140,6 +140,11 @@ parameters: count: 1 path: src/Doctrine/ORM/GeocoderListener.php + - + message: "#^Method Bazinga\\\\GeocoderBundle\\\\Plugin\\\\FakeIpPlugin\\:\\:handleQuery\\(\\) return type with generic interface Http\\\\Promise\\\\Promise does not specify its types\\: T$#" + count: 1 + path: src/Plugin/FakeIpPlugin.php + - message: "#^Parameter \\#1 \\$text of method Geocoder\\\\Query\\\\GeocodeQuery\\:\\:withText\\(\\) expects string, string\\|null given\\.$#" count: 1 @@ -150,6 +155,11 @@ parameters: count: 1 path: src/Plugin/FakeIpPlugin.php + - + message: "#^Method Bazinga\\\\GeocoderBundle\\\\Plugin\\\\ProfilingPlugin\\:\\:handleQuery\\(\\) return type with generic interface Http\\\\Promise\\\\Promise does not specify its types\\: T$#" + count: 1 + path: src/Plugin/ProfilingPlugin.php + - message: "#^Parameter \\#1 \\$accountId of class GeoIp2\\\\WebService\\\\Client constructor expects int, int\\|null given\\.$#" count: 1 From ba0babf71d7863494338231908d762a1a727fa66 Mon Sep 17 00:00:00 2001 From: Javier Eguiluz Date: Fri, 3 Nov 2023 11:20:46 +0100 Subject: [PATCH 7/8] Add Symfony 6.4 and 7.0 to the test matrix --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 23b255a..f6e5d39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,10 @@ jobs: sf_version: '6.0.*' - php: '8.2' sf_version: '6.2.*' + - php: '8.2' + sf_version: '6.4.*' + - php: '8.2' + sf_version: '7.0.*' steps: - name: "Setup PHP" uses: shivammathur/setup-php@v2 From 11754b7867ffef583fe7e96c607037295b6ace28 Mon Sep 17 00:00:00 2001 From: Tomas Date: Fri, 3 Nov 2023 13:48:25 +0200 Subject: [PATCH 8/8] Include PHP and Symfony version to CI job name --- .github/workflows/ci.yml | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f6e5d39..bfb1cf1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ concurrency: jobs: phpstan: - name: "PHPStan" + name: PHPStan runs-on: ubuntu-latest env: php-version: 8.2 @@ -55,7 +55,7 @@ jobs: run: vendor/bin/php-cs-fixer fix -v --dry-run --using-cache=no --format=checkstyle | cs2pr phpunit: - name: "PHPUnit" + name: PHPUnit (PHP ${{ matrix.php }}) (Symfony ${{ matrix.sf_version }}) (${{ matrix.dependencies }}) runs-on: ubuntu-latest strategy: max-parallel: 10 @@ -103,18 +103,6 @@ jobs: composer-options: "--optimize-autoloader" dependency-versions: "${{ matrix.dependencies }}" -# - name: "Install Composer dependencies (default)" -# if: matrix.strategy != 'lowest' -# env: -# SYMFONY_REQUIRE: ${{ matrix.sf_version }} -# run: composer update --no-interaction --optimize-autoloader -# -# - name: "Install Composer dependencies (lowest)" -# if: matrix.strategy == 'lowest' -# env: -# SYMFONY_REQUIRE: ${{ matrix.sf_version }} -# run: composer update --no-interaction --prefer-stable --prefer-lowest --optimize-autoloader - - name: "Run tests" env: SYMFONY_DEPRECATIONS_HELPER: 'max[self]=3&max[indirect]=1'