pax_global_header00006660000000000000000000000064140256415030014512gustar00rootroot0000000000000052 comment=9504165960a1f83cc1480e2be1dd0a0478561314 deprecations-0.5.3/000077500000000000000000000000001402564150300141775ustar00rootroot00000000000000deprecations-0.5.3/.github/000077500000000000000000000000001402564150300155375ustar00rootroot00000000000000deprecations-0.5.3/.github/workflows/000077500000000000000000000000001402564150300175745ustar00rootroot00000000000000deprecations-0.5.3/.github/workflows/ci.yml000066400000000000000000000030521402564150300207120ustar00rootroot00000000000000name: "Continuous Integration" on: pull_request: jobs: coding-standards: name: "Coding Standards" runs-on: "ubuntu-latest" strategy: matrix: php-version: - "7.4" steps: - name: "Checkout" uses: "actions/checkout@v2" - name: "Install PHP" uses: "shivammathur/setup-php@v2" with: coverage: "none" php-version: "${{ matrix.php-version }}" tools: "cs2pr" - name: "Cache dependencies installed with composer" uses: "actions/cache@v1" with: path: "~/.composer/cache" key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - name: "Install dependencies with composer" run: "composer install --no-interaction --no-progress --no-suggest" - name: "Run squizlabs/php_codesniffer" run: "vendor/bin/phpcs -q --no-colors --report=checkstyle | cs2pr" phpunit: name: PHPUnit runs-on: ubuntu-latest strategy: matrix: php-version: ['7.1', '7.2', '7.3', '7.4'] steps: - name: "Checkout" uses: "actions/checkout@v2" - name: Setup PHP uses: shivammathur/setup-php@v1 with: php-version: ${{ matrix.php-version }} - name: "Install dependencies with composer" run: "composer install --no-interaction --no-progress --no-suggest" - name: PHPUnit run: "vendor/bin/phpunit" deprecations-0.5.3/.gitignore000066400000000000000000000000421402564150300161630ustar00rootroot00000000000000vendor .phpcs-cache composer.lock deprecations-0.5.3/README.md000066400000000000000000000076501402564150300154660ustar00rootroot00000000000000# Doctrine Deprecations A small (side-effect free by default) layer on top of `trigger_error(E_USER_DEPRECATED)` or PSR-3 logging. - no side-effects by default, making it a perfect fit for libraries that don't know how the error handler works they operate under - options to avoid having to rely on error handlers global state by using PSR-3 logging - deduplicate deprecation messages to avoid excessive triggering and reduce overhead We recommend to collect Deprecations using a PSR logger instead of relying on the global error handler. ## Usage from consumer perspective: Enable Doctrine deprecations to be sent to a PSR3 logger: ```php \Doctrine\Deprecations\Deprecation::enableWithPsrLogger($logger); ``` Enable Doctrine deprecations to be sent as `@trigger_error($message, E_USER_DEPRECATED)` messages. ```php \Doctrine\Deprecations\Deprecation::enableWithTriggerError(); ``` If you only want to enable deprecation tracking, without logging or calling `trigger_error` then call: ```php \Doctrine\Deprecations\Deprecation::enableTrackingDeprecations(); ``` Tracking is enabled with all three modes and provides access to all triggered deprecations and their individual count: ```php $deprecations = \Doctrine\Deprecations\Deprecation::getTriggeredDeprecations(); foreach ($deprecations as $identifier => $count) { echo $identifier . " was triggered " . $count . " times\n"; } ``` ### Suppressing Specific Deprecations Disable triggering about specific deprecations: ```php \Doctrine\Deprecations\Deprecation::ignoreDeprecations("https://link/to/deprecations-description-identifier"); ``` Disable all deprecations from a package ```php \Doctrine\Deprecations\Deprecation::ignorePackage("doctrine/orm"); ``` ### Other Operations When used within PHPUnit or other tools that could collect multiple instances of the same deprecations the deduplication can be disabled: ```php \Doctrine\Deprecations\Deprecation::withoutDeduplication(); ``` Disable deprecation tracking again: ```php \Doctrine\Deprecations\Deprecation::disable(); ``` ## Usage from a library/producer perspective: When you want to unconditionally trigger a deprecation even when called from the library itself then the `trigger` method is the way to go: ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` If variable arguments are provided at the end, they are used with `sprintf` on the message. ```php \Doctrine\Deprecations\Deprecation::trigger( "doctrine/orm", "https://github.com/doctrine/orm/issue/1234", "message %s %d", "foo", 1234 ); ``` When you want to trigger a deprecation only when it is called by a function outside of the current package, but not trigger when the package itself is the cause, then use: ```php \Doctrine\Deprecations\Deprecation::triggerIfCalledFromOutside( "doctrine/orm", "https://link/to/deprecations-description", "message" ); ``` Based on the issue link each deprecation message is only triggered once per request. A limited stacktrace is included in the deprecation message to find the offending location. Note: A producer/library should never call `Deprecation::enableWith` methods and leave the decision how to handle deprecations to application and frameworks. ## Usage in PHPUnit tests There is a `VerifyDeprecations` trait that you can use to make assertions on the occurrence of deprecations within a test. ```php use Doctrine\Deprecations\PHPUnit\VerifyDeprecations; class MyTest extends TestCase { use VerifyDeprecations; public function testSomethingDeprecation() { $this->expectDeprecationWithIdentifier('https://github.com/doctrine/orm/issue/1234'); triggerTheCodeWithDeprecation(); } } ``` ## What is a deprecation identifier? An identifier for deprecations is just a link to any resource, most often a Github Issue or Pull Request explaining the deprecation and potentially its alternative. deprecations-0.5.3/composer.json000066400000000000000000000015751402564150300167310ustar00rootroot00000000000000{ "name": "doctrine/deprecations", "type": "library", "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", "homepage": "https://www.doctrine-project.org/", "license": "MIT", "require": { "php": "^7.1|^8.0" }, "require-dev": { "phpunit/phpunit": "^7.0|^8.0|^9.0", "psr/log": "^1.0", "doctrine/coding-standard": "^6.0|^7.0|^8.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" }, "autoload": { "psr-4": {"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"} }, "autoload-dev": { "psr-4": { "DeprecationTests\\": "test_fixtures/src", "Doctrine\\Foo\\": "test_fixtures/vendor/doctrine/foo" } } } deprecations-0.5.3/lib/000077500000000000000000000000001402564150300147455ustar00rootroot00000000000000deprecations-0.5.3/lib/Doctrine/000077500000000000000000000000001402564150300165145ustar00rootroot00000000000000deprecations-0.5.3/lib/Doctrine/Deprecations/000077500000000000000000000000001402564150300211345ustar00rootroot00000000000000deprecations-0.5.3/lib/Doctrine/Deprecations/Deprecation.php000066400000000000000000000173551402564150300241150ustar00rootroot00000000000000 */ private static $ignoredPackages = []; /** @var array */ private static $ignoredLinks = []; /** @var bool */ private static $deduplication = true; /** * Trigger a deprecation for the given package and identfier. * * The link should point to a Github issue or Wiki entry detailing the * deprecation. It is additionally used to de-duplicate the trigger of the * same deprecation during a request. * * @param mixed $args */ public static function trigger(string $package, string $link, string $message, ...$args): void { if (self::$type === self::TYPE_NONE) { return; } if (array_key_exists($link, self::$ignoredLinks)) { self::$ignoredLinks[$link]++; } else { self::$ignoredLinks[$link] = 1; } if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * Trigger a deprecation for the given package and identifier when called from outside. * * "Outside" means we assume that $package is currently installed as a * dependency and the caller is not a file in that package. When $package * is installed as a root package then deprecations triggered from the * tests folder are also considered "outside". * * This deprecation method assumes that you are using Composer to install * the dependency and are using the default /vendor/ folder and not a * Composer plugin to change the install location. The assumption is also * that $package is the exact composer packge name. * * Compared to {@link trigger()} this method causes some overhead when * deprecation tracking is enabled even during deduplication, because it * needs to call {@link debug_backtrace()} * * @param mixed $args */ public static function triggerIfCalledFromOutside(string $package, string $link, string $message, ...$args): void { if (self::$type === self::TYPE_NONE) { return; } $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); // first check that the caller is not from a tests folder, in which case we always let deprecations pass if (strpos($backtrace[1]['file'], DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR) === false) { $path = DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . $package . DIRECTORY_SEPARATOR; if (strpos($backtrace[0]['file'], $path) === false) { return; } if (strpos($backtrace[1]['file'], $path) !== false) { return; } } if (array_key_exists($link, self::$ignoredLinks)) { self::$ignoredLinks[$link]++; } else { self::$ignoredLinks[$link] = 1; } if (self::$deduplication === true && self::$ignoredLinks[$link] > 1) { return; } if (isset(self::$ignoredPackages[$package])) { return; } $message = sprintf($message, ...$args); self::delegateTriggerToBackend($message, $backtrace, $link, $package); } /** * @param array $backtrace */ private static function delegateTriggerToBackend(string $message, array $backtrace, string $link, string $package): void { if ((self::$type & self::TYPE_PSR_LOGGER) > 0) { $context = [ 'file' => $backtrace[0]['file'], 'line' => $backtrace[0]['line'], 'package' => $package, 'link' => $link, ]; self::$logger->notice($message, $context); } if (! ((self::$type & self::TYPE_TRIGGER_ERROR) > 0)) { return; } $message .= sprintf( ' (%s:%d called by %s:%d, %s, package %s)', self::basename($backtrace[0]['file']), $backtrace[0]['line'], self::basename($backtrace[1]['file']), $backtrace[1]['line'], $link, $package ); @trigger_error($message, E_USER_DEPRECATED); } /** * A non-local-aware version of PHPs basename function. */ private static function basename(string $filename): string { $pos = strrpos($filename, DIRECTORY_SEPARATOR); if ($pos === false) { return $filename; } return substr($filename, $pos + 1); } public static function enableTrackingDeprecations(): void { self::$type |= self::TYPE_TRACK_DEPRECATIONS; } public static function enableWithTriggerError(): void { self::$type |= self::TYPE_TRIGGER_ERROR; } public static function enableWithPsrLogger(LoggerInterface $logger): void { self::$type |= self::TYPE_PSR_LOGGER; self::$logger = $logger; } public static function withoutDeduplication(): void { self::$deduplication = false; } public static function disable(): void { self::$type = self::TYPE_NONE; self::$logger = null; self::$deduplication = true; foreach (self::$ignoredLinks as $link => $count) { self::$ignoredLinks[$link] = 0; } } public static function ignorePackage(string $packageName): void { self::$ignoredPackages[$packageName] = true; } public static function ignoreDeprecations(string ...$links): void { foreach ($links as $link) { self::$ignoredLinks[$link] = 0; } } public static function getUniqueTriggeredDeprecationsCount(): int { return array_reduce(self::$ignoredLinks, static function (int $carry, int $count) { return $carry + $count; }, 0); } /** * Returns each triggered deprecation link identifier and the amount of occurrences. * * @return array */ public static function getTriggeredDeprecations(): array { return self::$ignoredLinks; } } deprecations-0.5.3/lib/Doctrine/Deprecations/PHPUnit/000077500000000000000000000000001402564150300224235ustar00rootroot00000000000000deprecations-0.5.3/lib/Doctrine/Deprecations/PHPUnit/VerifyDeprecations.php000066400000000000000000000036631402564150300267510ustar00rootroot00000000000000 */ private $doctrineDeprecationsExpectations = []; /** @var array */ private $doctrineNoDeprecationsExpectations = []; public function expectDeprecationWithIdentifier(string $identifier): void { $this->doctrineDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } public function expectNoDeprecationWithIdentifier(string $identifier): void { $this->doctrineNoDeprecationsExpectations[$identifier] = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; } /** * @before */ public function enableDeprecationTracking(): void { Deprecation::enableTrackingDeprecations(); } /** * @after */ public function verifyDeprecationsAreTriggered(): void { foreach ($this->doctrineDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount > $expectation, sprintf( "Expected deprecation with identifier '%s' was not triggered by code executed in test.", $identifier ) ); } foreach ($this->doctrineNoDeprecationsExpectations as $identifier => $expectation) { $actualCount = Deprecation::getTriggeredDeprecations()[$identifier] ?? 0; $this->assertTrue( $actualCount === $expectation, sprintf( "Expected deprecation with identifier '%s' was triggered by code executed in test, but expected not to.", $identifier ) ); } } } deprecations-0.5.3/phpcs.xml000066400000000000000000000011321402564150300160330ustar00rootroot00000000000000 lib tests deprecations-0.5.3/phpunit.xml.dist000066400000000000000000000003361402564150300173540ustar00rootroot00000000000000 tests deprecations-0.5.3/test_fixtures/000077500000000000000000000000001402564150300171075ustar00rootroot00000000000000deprecations-0.5.3/test_fixtures/src/000077500000000000000000000000001402564150300176765ustar00rootroot00000000000000deprecations-0.5.3/test_fixtures/src/Foo.php000066400000000000000000000005641402564150300211370ustar00rootroot00000000000000oldFunc(); } public static function triggerDependencyWithDeprecationFromInside(): void { $bar = new Bar(); $bar->newFunc(); } } deprecations-0.5.3/test_fixtures/src/RootDeprecation.php000066400000000000000000000005651402564150300235160ustar00rootroot00000000000000oldFunc(); } } deprecations-0.5.3/test_fixtures/vendor/doctrine/foo/Baz.php000066400000000000000000000002601402564150300242210ustar00rootroot00000000000000oldFunc(); } } deprecations-0.5.3/tests/000077500000000000000000000000001402564150300153415ustar00rootroot00000000000000deprecations-0.5.3/tests/Doctrine/000077500000000000000000000000001402564150300171105ustar00rootroot00000000000000deprecations-0.5.3/tests/Doctrine/Deprecations/000077500000000000000000000000001402564150300215305ustar00rootroot00000000000000deprecations-0.5.3/tests/Doctrine/Deprecations/DeprecationTest.php000066400000000000000000000202411402564150300253350ustar00rootroot00000000000000setAccessible(true); $reflectionProperty->setValue([]); $reflectionProperty = new ReflectionProperty(Deprecation::class, 'ignoredLinks'); $reflectionProperty->setAccessible(true); $reflectionProperty->setValue([]); Deprecation::enableTrackingDeprecations(); } public function expectDeprecation(): void { if (method_exists(TestCase::class, 'expectDeprecation')) { parent::expectDeprecation(); } else { parent::expectException(Deprecated::class); } } public function expectDeprecationMessage(string $message): void { if (method_exists(TestCase::class, 'expectDeprecationMessage')) { parent::expectDeprecationMessage($message); } else { parent::expectExceptionMessage($message); } } public function expectErrorHandler(string $expectedMessage, string $identifier, int $times = 1): void { set_error_handler(function ($type, $message) use ($expectedMessage, $identifier, $times): void { $this->assertStringMatchesFormat( $expectedMessage, $message ); $this->assertEquals([$identifier => $times], Deprecation::getTriggeredDeprecations()); }); } public function testDeprecation(): void { Deprecation::enableWithTriggerError(); $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/1234'); $this->expectErrorHandler( 'this is deprecated foo 1234 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/1234, package doctrine/orm)', 'https://github.com/doctrine/deprecations/1234' ); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/1234', 'this is deprecated %s %d', 'foo', 1234 ); $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/1234', 'this is deprecated %s %d', 'foo', 1234 ); $this->assertEquals(2, Deprecation::getUniqueTriggeredDeprecationsCount()); } public function testDeprecationWithoutDeduplication(): void { Deprecation::enableWithTriggerError(); Deprecation::withoutDeduplication(); $this->expectErrorHandler( 'this is deprecated foo 2222 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/2222, package doctrine/orm)', 'https://github.com/doctrine/deprecations/2222' ); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/2222', 'this is deprecated %s %d', 'foo', 2222 ); $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); $this->expectErrorHandler( 'this is deprecated foo 2222 (DeprecationTest.php:%d called by TestCase.php:%d, https://github.com/doctrine/deprecations/2222, package doctrine/orm)', 'https://github.com/doctrine/deprecations/2222', 2 ); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/2222', 'this is deprecated %s %d', 'foo', 2222 ); $this->assertEquals(2, Deprecation::getUniqueTriggeredDeprecationsCount()); } public function testDeprecationResetsCounts(): void { try { Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/1234', 'this is deprecated %s %d', 'foo', 1234 ); } catch (Throwable $e) { Deprecation::disable(); $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); $this->assertEquals(['https://github.com/doctrine/deprecations/1234' => 0], Deprecation::getTriggeredDeprecations()); } } public function expectDeprecationMock(string $message, string $identifier, string $package): MockObject { $mock = $this->createMock(LoggerInterface::class); $mock->method('notice')->with($message, $this->callback(function ($context) use ($identifier, $package) { $this->assertEquals($package, $context['package']); $this->assertEquals($identifier, $context['link']); return true; })); return $mock; } public function testDeprecationWithPsrLogger(): void { $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/2222'); $mock = $this->expectDeprecationMock( 'this is deprecated foo 1234', 'https://github.com/doctrine/deprecations/2222', 'doctrine/orm' ); Deprecation::enableWithPsrLogger($mock); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/deprecations/2222', 'this is deprecated %s %d', 'foo', 1234 ); } public function testDeprecationWithIgnoredPackage(): void { Deprecation::enableWithTriggerError(); Deprecation::ignorePackage('doctrine/orm'); Deprecation::trigger( 'doctrine/orm', 'https://github.com/doctrine/orm/issue/1234', 'this is deprecated %s %d', 'foo', 1234 ); $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); $this->assertEquals(['https://github.com/doctrine/orm/issue/1234' => 1], Deprecation::getTriggeredDeprecations()); } public function testDeprecationIfCalledFromOutside(): void { Deprecation::enableWithTriggerError(); $this->expectErrorHandler( 'Bar::oldFunc() is deprecated, use Bar::newFunc() instead. (Bar.php:16 called by Foo.php:14, https://github.com/doctrine/foo, package doctrine/foo)', 'https://github.com/doctrine/foo' ); Foo::triggerDependencyWithDeprecation(); } public function testDeprecationIfCalledFromOutsideNotTriggeringFromInside(): void { Deprecation::enableWithTriggerError(); Foo::triggerDependencyWithDeprecationFromInside(); $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); } public function testDeprecationIfCalledFromOutsideNotTriggeringFromInsideClass(): void { Deprecation::enableWithTriggerError(); $baz = new Baz(); $baz->usingOldFunc(); $this->assertEquals(0, Deprecation::getUniqueTriggeredDeprecationsCount()); } public function testDeprecationCalledFromOutsideInRoot(): void { Deprecation::enableWithTriggerError(); $this->expectDeprecationWithIdentifier('https://github.com/doctrine/deprecations/4444'); $this->expectErrorHandler( 'this is deprecated foo 1234 (RootDeprecation.php:%d called by DeprecationTest.php:%d, https://github.com/doctrine/deprecations/4444, package doctrine/orm)', 'https://github.com/doctrine/deprecations/4444' ); RootDeprecation::run(); $this->assertEquals(1, Deprecation::getUniqueTriggeredDeprecationsCount()); } } deprecations-0.5.3/tests/Doctrine/Deprecations/VerifyDeprecationsTest.php000066400000000000000000000015121402564150300267050ustar00rootroot00000000000000expectDeprecationWithIdentifier('http://example.com'); Deprecation::trigger('doctrine/dbal', 'http://example.com', 'message'); } public function testExpectNoDeprecationWithIdentifier(): void { $this->expectNoDeprecationWithIdentifier('http://example.com'); Deprecation::trigger('doctrine/dbal', 'http://otherexample.com', 'message'); } }