diff --git a/.gitattributes b/.gitattributes index a4b0856..6533989 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ .github export-ignore .travis.yml export-ignore phpunit.xml.dist export-ignore +symfony.lock export-ignore diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..499cf8f --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,49 @@ +name: "Tests" + +on: + push: + paths: + - 'src/**' + - 'tests/**' + - 'composer.json' + branches: + - master + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + tests: + strategy: + fail-fast: false + matrix: + include: + - php: '7.4' + symfony: 4.4.* + - php: '8.1' + symfony: 6.4.* + - php: '8.3' + symfony: 7.0.* + name: PHP ${{ matrix.php }} SYMFONY ${{ matrix.symfony }} + runs-on: ubuntu-20.04 + + steps: + - name: "Checkout" + uses: "actions/checkout@v3" + + - name: "Install PHP" + uses: "shivammathur/setup-php@v2" + with: + coverage: "none" + extensions: curl, mbstring, pdo, pdo_sqlite, sqlite + php-version: ${{ matrix.php }} + tools: composer + + - name: "Install dependencies" + run: composer update --ansi --no-interaction + env: + SYMFONY_REQUIRE: ${{ matrix.symfony }} + - name: "Run tests" + run: "php vendor/bin/phpunit" diff --git a/.gitignore b/.gitignore index aec80a4..72bb2dd 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /composer.lock /vendor/* /var/* +.phpunit.result.cache diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9895819..0000000 --- a/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -language: php - -sudo: false - -branches: - only: - - master - -matrix: - include: - - php: 7.2 - env: SYMFONY_VERSION="3.4.*" - - php: 7.3 - env: SYMFONY_VERSION="4.4.*" - - php: 7.4 - env: SYMFONY_VERSION="5.1.*" - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - phpenv config-rm xdebug.ini || echo "xdebug not available" - - composer self-update - - if [ "$SYMFONY_VERSION" != "" ]; then composer require --no-update symfony/symfony:${SYMFONY_VERSION}; fi; - -install: - - composer install --no-interaction - -script: - - php vendor/bin/phpunit diff --git a/README.md b/README.md index 5401e9a..6142922 100644 --- a/README.md +++ b/README.md @@ -35,31 +35,6 @@ return [ ] ``` -For Symfony 3.4 - -```php -load($this->getRootDir().'/config/config_'.$this->getEnvironment().'.yml'); - } -} -``` - 3. Base configuration to enable the datadog client in your `config.yml` ``` diff --git a/composer.json b/composer.json index 55a3745..972fa77 100644 --- a/composer.json +++ b/composer.json @@ -20,21 +20,37 @@ "psr-4": { "Okvpn\\Bundle\\DatadogBundle\\Tests\\": "tests/" } }, "require": { - "php":">=7.2", - "symfony/framework-bundle": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/yaml": "^3.4 || ^4.0 || ^5.0 || ^6.0", + "php":">=7.4", + "symfony/framework-bundle": "^4.4 || ^5.4 || ^6.0 || ^7.0", "graze/dog-statsd": "^0.4 || ^1.0" }, "require-dev": { "ext-pdo_sqlite": "*", - "doctrine/doctrine-bundle": "^1.6.10 || ^2.0.6", - "doctrine/orm": "2.5.11 || 2.6.x-dev as 2.6.3 || ^2.7.0", - "phpunit/phpunit": "^6.2 || ^8.5", - "symfony/browser-kit": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/console": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/security-bundle": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/phpunit-bridge": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/var-dumper": "^3.4 || ^4.0 || ^5.0 || ^6.0", - "symfony/monolog-bundle": "^3.2.0" - } + "doctrine/doctrine-bundle": "^2.6", + "doctrine/orm": "^2.7", + "phpunit/phpunit": "^8.5 || ^9.0 || ^10.0", + "symfony/browser-kit": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/console": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/security-bundle": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/phpunit-bridge": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/var-dumper": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^4.4 || ^5.4 || ^6.0 || ^7.0", + "symfony/flex": "^1.10 || ^2.0", + "symfony/monolog-bundle": "^3.2" + }, + "config": { + "allow-plugins": { + "symfony/flex": true + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + }, + "symfony": { + "allow-contrib": false + } + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/src/Client/ClientWrapper.php b/src/Client/ClientWrapper.php index 503c8da..d38ed2b 100644 --- a/src/Client/ClientWrapper.php +++ b/src/Client/ClientWrapper.php @@ -81,7 +81,7 @@ public function event($title, $text, array $metadata = [], array $tags = []) */ public function getOption(string $option, $default = null) { - return isset($this->options[$option]) ? $this->options[$option] : $default; + return $this->options[$option] ?? $default; } /** diff --git a/src/Client/DatadogDns.php b/src/Client/DatadogDns.php new file mode 100644 index 0000000..f53611d --- /dev/null +++ b/src/Client/DatadogDns.php @@ -0,0 +1,13 @@ +getParameter('okvpn_datadog.logging') || false === $container->getParameter('okvpn_datadog.profiling')) { return; diff --git a/src/DependencyInjection/CompilerPass/SqlLoggerPass.php b/src/DependencyInjection/CompilerPass/SqlLoggerPass.php index 8421750..2bba0da 100644 --- a/src/DependencyInjection/CompilerPass/SqlLoggerPass.php +++ b/src/DependencyInjection/CompilerPass/SqlLoggerPass.php @@ -22,7 +22,7 @@ public function __construct(array $connections) /** * {@inheritdoc} */ - public function process(ContainerBuilder $container) + public function process(ContainerBuilder $container): void { if (false === $container->hasDefinition('okvpn_datadog.logger.sql')) { return; diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 68152bb..266daad 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -17,12 +17,10 @@ class Configuration implements ConfigurationInterface /** * {@inheritdoc} */ - public function getConfigTreeBuilder() + public function getConfigTreeBuilder(): TreeBuilder { $treeBuilder = new TreeBuilder('okvpn_datadog'); - $rootNode = \method_exists($treeBuilder, 'getRootNode') ? - $treeBuilder->getRootNode() : - $treeBuilder->root('okvpn_datadog'); + $rootNode = $treeBuilder->getRootNode(); $rootNode->children() ->arrayNode('handle_exceptions') diff --git a/src/DependencyInjection/OkvpnDatadogExtension.php b/src/DependencyInjection/OkvpnDatadogExtension.php index 14510c1..725f57b 100644 --- a/src/DependencyInjection/OkvpnDatadogExtension.php +++ b/src/DependencyInjection/OkvpnDatadogExtension.php @@ -14,7 +14,7 @@ class OkvpnDatadogExtension extends Extension /** * {@inheritdoc} */ - public function load(array $configs, ContainerBuilder $container) + public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); diff --git a/src/Logging/ErrorBag.php b/src/Logging/ErrorBag.php index d615b64..36146e6 100644 --- a/src/Logging/ErrorBag.php +++ b/src/Logging/ErrorBag.php @@ -35,7 +35,7 @@ public function flush(): void public function rootError(): ?array { - return isset($this->errors[0]) ? $this->errors[0] : null; + return $this->errors[0] ?? null; } public function getErrors(): array diff --git a/src/Logging/Watcher/DefaultWatcher.php b/src/Logging/Watcher/DefaultWatcher.php index b30dd45..711a9e1 100644 --- a/src/Logging/Watcher/DefaultWatcher.php +++ b/src/Logging/Watcher/DefaultWatcher.php @@ -49,7 +49,7 @@ public function watch(): array $context = []; if ($this->tokenStorage instanceof TokenStorageInterface) { $token = $this->tokenStorage->getToken(); - if (null !== $token) { + if (null !== $token) { $context['token'] = method_exists($token, '__toString') ? $token->__toString() : $token->serialize(); } } diff --git a/src/Services/ExceptionHashService.php b/src/Services/ExceptionHashService.php index 748664d..575f4d8 100644 --- a/src/Services/ExceptionHashService.php +++ b/src/Services/ExceptionHashService.php @@ -36,7 +36,6 @@ public function hash(\Throwable $exception): string } } - $hash = sha1($hash); - return $hash; + return sha1($hash); } } diff --git a/symfony.lock b/symfony.lock new file mode 100644 index 0000000..75033a6 --- /dev/null +++ b/symfony.lock @@ -0,0 +1,125 @@ +{ + "doctrine/doctrine-bundle": { + "version": "2.11", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.10", + "ref": "e025a6cb69b195970543820b2f18ad21724473fa" + }, + "files": [ + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "phpunit/phpunit": { + "version": "10.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "9.6", + "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" + }, + "files": [ + ".env.test", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/console": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "5.3", + "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" + }, + "files": [ + "bin/console" + ] + }, + "symfony/flex": { + "version": "2.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" + }, + "files": [ + ".env" + ] + }, + "symfony/framework-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "7.0", + "ref": "de6e1b3e2bbbe69e36262d72c3f3db858b1ab391" + }, + "files": [ + "config/packages/cache.yaml", + "config/packages/framework.yaml", + "config/preload.php", + "config/routes/framework.yaml", + "config/services.yaml", + "public/index.php", + "src/Controller/.gitignore", + "src/Kernel.php" + ] + }, + "symfony/monolog-bundle": { + "version": "3.9999999", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.7", + "ref": "213676c4ec929f046dfde5ea8e97625b81bc0578" + }, + "files": [ + "config/packages/monolog.yaml" + ] + }, + "symfony/phpunit-bridge": { + "version": "6.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.3", + "ref": "01dfaa98c58f7a7b5a9b30e6edb7074af7ed9819" + }, + "files": [ + ".env.test", + "bin/phpunit", + "phpunit.xml.dist", + "tests/bootstrap.php" + ] + }, + "symfony/routing": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.2", + "ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6" + }, + "files": [ + "config/packages/routing.yaml", + "config/routes.yaml" + ] + }, + "symfony/security-bundle": { + "version": "7.0", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.0", + "ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48" + }, + "files": [ + "config/packages/security.yaml" + ] + } +} diff --git a/tests/Functional/App/Controller/AppDatadogController.php b/tests/Functional/App/Controller/AppDatadogController.php index 1a8d6c8..b11b283 100644 --- a/tests/Functional/App/Controller/AppDatadogController.php +++ b/tests/Functional/App/Controller/AppDatadogController.php @@ -4,6 +4,7 @@ namespace Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Controller; +use Doctrine\Persistence\ManagerRegistry; use Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Entity\DatadogUser; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\Controller; @@ -39,6 +40,11 @@ public function entity() class AppDatadogController extends AbstractController { use AppDatadogControllerTrait; + + public function getDoctrine(): ManagerRegistry + { + return $this->container->get('doctrine'); + } } } else { class AppDatadogController extends Controller diff --git a/tests/Functional/App/Entity/DatadogUser.php b/tests/Functional/App/Entity/DatadogUser.php index 23081f2..3347b28 100644 --- a/tests/Functional/App/Entity/DatadogUser.php +++ b/tests/Functional/App/Entity/DatadogUser.php @@ -9,6 +9,7 @@ /** * @ORM\Entity */ +#[ORM\Entity] class DatadogUser { /** @@ -18,6 +19,9 @@ class DatadogUser * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ + #[ORM\Column('id', type: 'integer')] + #[ORM\Id] + #[ORM\GeneratedValue('IDENTITY')] private $id; /** @@ -25,6 +29,7 @@ class DatadogUser * * @ORM\Column(type="string", length=64) */ + #[ORM\Column('username', type: 'string', length: 64)] private $username; /** diff --git a/tests/Functional/App/OkvpnKernel.php b/tests/Functional/App/OkvpnKernel.php index 5e5a8df..77352ea 100644 --- a/tests/Functional/App/OkvpnKernel.php +++ b/tests/Functional/App/OkvpnKernel.php @@ -7,28 +7,45 @@ use Symfony\Component\Config\Loader\LoaderInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; use Symfony\Component\Routing\RouteCollectionBuilder; -trait OkvpnKernelTrait -{ +if (Kernel::VERSION_ID >= 50000) { + trait OkvpnKernelTrait + { + public function loadRoutes() + { + $routes = new RouteCollection(); - /** - * @param LoaderInterface $loader - * @return RouteCollection - */ - public function loadRoutes(LoaderInterface $loader) + $routes->add('index', $this->createRoute("/", "app.controller.base_controller::index")); + $routes->add("exception", $this->createRoute('/exception', "app.controller.base_controller::exception")); + $routes->add("entity", $this->createRoute('/entity', "app.controller.base_controller::entity")); + return $routes; + } + + private function createRoute(string $path, string $controller): Route + { + return new Route($path, ['_controller' => $controller]); + } + } +} else { + trait OkvpnKernelTrait { - $routes = new RouteCollectionBuilder($loader); + public function loadRoutes(LoaderInterface $loader) + { + $routes = new RouteCollectionBuilder($loader); - $routes->add('/', "app.controller.base_controller:index"); - $routes->add('/exception', "app.controller.base_controller:exception"); - $routes->add('/entity', "app.controller.base_controller:entity"); + $routes->add('/', "app.controller.base_controller:index"); + $routes->add('/exception', "app.controller.base_controller:exception"); + $routes->add('/entity', "app.controller.base_controller:entity"); - return $routes->build(); + return $routes->build(); + } } } + class OkvpnKernel extends Kernel { use OkvpnKernelTrait; @@ -58,13 +75,13 @@ public function registerContainerConfiguration(LoaderInterface $loader) $container->addObjectResource($this); $container->loadFromExtension('framework', [ 'router' => [ - 'resource' => self::VERSION_ID > 40000 ? AppKernelRouting::class . '::loadRoutes' : 'kernel:loadRoutes', + 'resource' => AppKernelRouting::class . '::loadRoutes', 'type' => 'service', ], ]); }); - if (Kernel::MAJOR_VERSION > 5) { + if (Kernel::VERSION_ID >= 60000) { $loader->load(__DIR__.'/config6.yml'); } else { $loader->load(__DIR__.'/config.yml'); diff --git a/tests/Functional/App/config6.yml b/tests/Functional/App/config6.yml new file mode 100644 index 0000000..0b4626b --- /dev/null +++ b/tests/Functional/App/config6.yml @@ -0,0 +1,75 @@ +framework: + secret: test + test: true + default_locale: en + profiler: { collect: true } + session: + storage_factory_id: session.storage.factory.mock_file + +doctrine: + dbal: + driver: 'pdo_sqlite' + path: '%kernel.project_dir%/test.db' + orm: + auto_generate_proxy_classes: '%kernel.debug%' + naming_strategy: doctrine.orm.naming_strategy.underscore + auto_mapping: true + mappings: + App: + is_bundle: false + dir: '%kernel.project_dir%/Entity' + prefix: 'Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Entity' + +monolog: + handlers: + nested: + type: stream + path: "%kernel.logs_dir%/%kernel.environment%.log" + level: debug + +security: + providers: + in_memory: + memory: ~ + firewalls: + main: + lazy: true + provider: in_memory + +okvpn_datadog: + profiling: true + namespace: app + dedup_keep_time: 5 + handle_exceptions: + skip_instanceof: + - 'Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Command\DemoDatadogExceptionInterface' + skip_capture: + - 'UnderflowException' + skip_wildcard: + - '*entity aliases failed*' + +parameters: + request_listener.http_port: 80 + request_listener.https_port: 443 + +services: + okvpn_datadog.client_test_decorator: + class: Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Client\DebugDatadogClient + decorates: okvpn_datadog.client + public: true + arguments: ['@okvpn_datadog.client_test_decorator.inner'] + + app.command.exception_command: + class: Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Command\DatadogExceptionCommand + arguments: ['@logger'] + tags: + - { name: console.command } + + app.controller.base_controller: + class: Okvpn\Bundle\DatadogBundle\Tests\Functional\App\Controller\AppDatadogController + public: true + calls: + - [setContainer, ['@service_container']] + + Okvpn\Bundle\DatadogBundle\Tests\Functional\App\AppKernelRouting: + tags: [routing.route_loader] diff --git a/tests/Functional/IntegrationTest.php b/tests/Functional/IntegrationTest.php index f0ecf48..df5198e 100644 --- a/tests/Functional/IntegrationTest.php +++ b/tests/Functional/IntegrationTest.php @@ -6,7 +6,6 @@ use Doctrine\ORM\EntityManagerInterface; use Doctrine\ORM\Tools\SchemaTool; -use Symfony\Bundle\FrameworkBundle\Client; use Symfony\Bundle\FrameworkBundle\Console\Application; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; @@ -16,7 +15,7 @@ class IntegrationTest extends WebTestCase { - /** @var Client|KernelBrowser */ + /** @var KernelBrowser */ private $client; /** @@ -87,10 +86,10 @@ public function testHandleConsoleException() $this->runCommand('app:exception'); } catch (\Throwable $exception) {} - list($title, $desc) = $this->getClientDecorator()->getLastEvent(); + [$title, $desc] = $this->getClientDecorator()->getLastEvent(); self::assertNotEmpty($this->getClientDecorator()->getRecords()); - self::assertContains('Call to undefined function function_do_not_exists', $desc); + self::assertContainsStr('Call to undefined function function_do_not_exists', $desc); } public function testDeduplicationLogger() @@ -103,12 +102,12 @@ public function testDeduplicationLogger() $this->client->request('GET', '/exception'); } catch (\Exception $exception) {} - list($title, $desc) = $this->getClientDecorator()->getLastEvent(); + [$title, $desc] = $this->getClientDecorator()->getLastEvent(); $desc = $desc ? $this->processDatadogArtifact($desc) : $desc; switch ($i) { case 0: self::assertNotEmpty($this->getClientDecorator()->getRecords()); - self::assertContains('GET /exception HTTP/1.1', $desc, 'Request details must be save in log'); + self::assertContainsStr('GET /exception HTTP/1.1', $desc, 'Request details must be save in log'); sleep(1); break; case 1: @@ -136,11 +135,11 @@ public function testFilterException(string $filterOption, bool $isSkip) } - list($title, $desc) = $this->getClientDecorator()->getLastEvent(); + [$title, $desc] = $this->getClientDecorator()->getLastEvent(); self::assertSame($isSkip, empty($desc)); } - public function filterExceptionDataProvider() + public static function filterExceptionDataProvider() { yield 'Filter by instanceof' => ['skip_instanceof', true]; @@ -271,4 +270,13 @@ protected function getClientDecorator() { return $this->client->getContainer()->get('okvpn_datadog.client_test_decorator'); } + + protected static function assertContainsStr(string $needle, $haystack, string $message = '') + { + if (method_exists(__CLASS__, 'assertStringContainsString')) { + self::assertStringContainsString($needle, $haystack, $message); + } else { + self::assertContains($needle, $haystack, $message); + } + } }