From 829bca240d2471c8526fe9029ade3ae234b1aade Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sun, 6 Aug 2017 15:38:20 +0200 Subject: [PATCH 1/3] Trigger a deprecation when using an internal class/trait/interface --- .../Component/Debug/DebugClassLoader.php | 96 ++++++++++++------- .../Debug/Tests/DebugClassLoaderTest.php | 32 +++++-- .../Debug/Tests/Fixtures/InternalClass.php | 11 +++ .../Tests/Fixtures/InternalInterface.php | 10 ++ .../Debug/Tests/Fixtures/InternalTrait.php | 10 ++ .../Debug/Tests/Fixtures/InternalTrait2.php | 10 ++ 6 files changed, 125 insertions(+), 44 deletions(-) create mode 100644 src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php create mode 100644 src/Symfony/Component/Debug/Tests/Fixtures/InternalInterface.php create mode 100644 src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait.php create mode 100644 src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index 2b13c02cf92c5..d668bfddc4f44 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -27,6 +27,7 @@ class DebugClassLoader private $classLoader; private $isFinder; private static $caseCheck; + private static $internal = array(); private static $final = array(); private static $finalMethods = array(); private static $deprecated = array(); @@ -166,10 +167,14 @@ public function loadClass($class) } $parent = get_parent_class($class); + $doc = $refl->getDocComment(); + if (preg_match('#\n \* @internal(?: .+?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { + self::$internal[$name] = true; + } // Not an interface nor a trait if (class_exists($name, false)) { - if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + if (preg_match('#\n \* @final(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { self::$final[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; } @@ -203,50 +208,73 @@ public function loadClass($class) if (in_array(strtolower($refl->getShortName()), self::$php7Reserved)) { @trigger_error(sprintf('The "%s" class uses the reserved name "%s", it will break on PHP 7 and higher', $name, $refl->getShortName()), E_USER_DEPRECATED); - } elseif (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { + } + if (preg_match('#\n \* @deprecated (.*?)\r?\n \*(?: @|/$)#s', $refl->getDocComment(), $notice)) { self::$deprecated[$name] = preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]); + } + + // Don't trigger deprecations for classes in the same vendor + if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { + $len = 0; + $ns = ''; } else { - // Don't trigger deprecations for classes in the same vendor - if (2 > $len = 1 + (strpos($name, '\\', 1 + strpos($name, '\\')) ?: strpos($name, '_'))) { - $len = 0; - $ns = ''; - } else { - switch ($ns = substr($name, 0, $len)) { - case 'Symfony\Bridge\\': - case 'Symfony\Bundle\\': - case 'Symfony\Component\\': - $ns = 'Symfony\\'; - $len = strlen($ns); - break; + switch ($ns = substr($name, 0, $len)) { + case 'Symfony\Bridge\\': + case 'Symfony\Bundle\\': + case 'Symfony\Component\\': + $ns = 'Symfony\\'; + $len = strlen($ns); + break; + } + } + + foreach (call_user_func(function () use ($name, $parent) { + if (isset(self::$internal[$parent])) { + yield 'class' => $parent; + } + + foreach (class_implements($name, false) as $interface) { + if (isset(self::$internal[$interface])) { + yield 'interface' => $interface; } } - if (!$parent || strncmp($ns, $parent, $len)) { - if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { - @trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + foreach (class_uses($name, false) as $use) { + if (isset(self::$internal[$use])) { + yield 'trait' => $use; } + } + }) as $type => $internalUse) { + if (strncmp($ns, $internalUse, $len)) { + @trigger_error(sprintf('The "%s" %s is considered internal. It may change without further notice. You should not use it from "%s".', $internalUse, $type, $name), E_USER_DEPRECATED); + } + } - $parentInterfaces = array(); - $deprecatedInterfaces = array(); - if ($parent) { - foreach (class_implements($parent) as $interface) { - $parentInterfaces[$interface] = 1; - } + if (!$parent || strncmp($ns, $parent, $len)) { + if ($parent && isset(self::$deprecated[$parent]) && strncmp($ns, $parent, $len)) { + @trigger_error(sprintf('The "%s" class extends "%s" that is deprecated %s', $name, $parent, self::$deprecated[$parent]), E_USER_DEPRECATED); + } + + $parentInterfaces = array(); + $deprecatedInterfaces = array(); + if ($parent) { + foreach (class_implements($parent) as $interface) { + $parentInterfaces[$interface] = 1; } + } - foreach ($refl->getInterfaceNames() as $interface) { - if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { - $deprecatedInterfaces[] = $interface; - } - foreach (class_implements($interface) as $interface) { - $parentInterfaces[$interface] = 1; - } + foreach ($refl->getInterfaceNames() as $interface) { + if (isset(self::$deprecated[$interface]) && strncmp($ns, $interface, $len)) { + $deprecatedInterfaces[] = $interface; + } + foreach (class_implements($interface) as $interface) { + $parentInterfaces[$interface] = 1; } + } - foreach ($deprecatedInterfaces as $interface) { - if (!isset($parentInterfaces[$interface])) { - @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); - } + foreach ($deprecatedInterfaces as $interface) { + if (!isset($parentInterfaces[$interface])) { + @trigger_error(sprintf('The "%s" %s "%s" that is deprecated %s', $name, $refl->isInterface() ? 'interface extends' : 'class implements', $interface, self::$deprecated[$interface]), E_USER_DEPRECATED); } } } diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index f1e3fb7c611b5..9a873a6ba740c 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -312,6 +312,24 @@ class_exists(__NAMESPACE__.'\\Fixtures\\ExtendedFinalMethod', true); $this->assertSame($xError, $lastError); } + + public function testInternalsUse() + { + $deprecations = array(); + set_error_handler(function ($type, $msg) use (&$deprecations) { $deprecations[] = $msg; }); + $e = error_reporting(E_USER_DEPRECATED); + + class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true); + + error_reporting($e); + restore_error_handler(); + + $this->assertSame($deprecations, array( + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', + )); + } } class ClassLoader @@ -335,22 +353,12 @@ public function findFile($class) eval('namespace '.__NAMESPACE__.'; class TestingStacking { function foo() {} }'); } elseif (__NAMESPACE__.'\TestingCaseMismatch' === $class) { eval('namespace '.__NAMESPACE__.'; class TestingCaseMisMatch {}'); - } elseif (__NAMESPACE__.'\Fixtures\CaseMismatch' === $class) { - return $fixtureDir.'CaseMismatch.php'; } elseif (__NAMESPACE__.'\Fixtures\Psr4CaseMismatch' === $class) { return $fixtureDir.'psr4'.DIRECTORY_SEPARATOR.'Psr4CaseMismatch.php'; } elseif (__NAMESPACE__.'\Fixtures\NotPSR0' === $class) { return $fixtureDir.'reallyNotPsr0.php'; } elseif (__NAMESPACE__.'\Fixtures\NotPSR0bis' === $class) { return $fixtureDir.'notPsr0Bis.php'; - } elseif (__NAMESPACE__.'\Fixtures\DeprecatedInterface' === $class) { - return $fixtureDir.'DeprecatedInterface.php'; - } elseif (__NAMESPACE__.'\Fixtures\FinalClass' === $class) { - return $fixtureDir.'FinalClass.php'; - } elseif (__NAMESPACE__.'\Fixtures\FinalMethod' === $class) { - return $fixtureDir.'FinalMethod.php'; - } elseif (__NAMESPACE__.'\Fixtures\ExtendedFinalMethod' === $class) { - return $fixtureDir.'ExtendedFinalMethod.php'; } elseif ('Symfony\Bridge\Debug\Tests\Fixtures\ExtendsDeprecatedParent' === $class) { eval('namespace Symfony\Bridge\Debug\Tests\Fixtures; class ExtendsDeprecatedParent extends \\'.__NAMESPACE__.'\Fixtures\DeprecatedClass {}'); } elseif ('Test\\'.__NAMESPACE__.'\DeprecatedParentClass' === $class) { @@ -363,6 +371,10 @@ public function findFile($class) eval('namespace Test\\'.__NAMESPACE__.'; class Float {}'); } elseif ('Test\\'.__NAMESPACE__.'\ExtendsFinalClass' === $class) { eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsFinalClass extends \\'.__NAMESPACE__.'\Fixtures\FinalClass {}'); + } elseif ('Test\\'.__NAMESPACE__.'\ExtendsInternals' === $class) { + eval('namespace Test\\'.__NAMESPACE__.'; class ExtendsInternals extends \\'.__NAMESPACE__.'\Fixtures\InternalClass implements \\'.__NAMESPACE__.'\Fixtures\InternalInterface { + use \\'.__NAMESPACE__.'\Fixtures\InternalTrait; + }'); } } } diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php new file mode 100644 index 0000000000000..ecb2944f51cf5 --- /dev/null +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php @@ -0,0 +1,11 @@ + Date: Sun, 6 Aug 2017 18:47:36 +0200 Subject: [PATCH 2/3] Fixes --- .../Doctrine/Form/ChoiceList/IdReader.php | 2 +- .../Component/Debug/DebugClassLoader.php | 26 ++++--------------- .../Debug/Tests/DebugClassLoaderTest.php | 2 +- .../Debug/Tests/Fixtures/InternalClass.php | 2 +- .../Tests/Fixtures/InternalInterface.php | 2 +- .../Debug/Tests/Fixtures/InternalTrait.php | 2 +- .../Debug/Tests/Fixtures/InternalTrait2.php | 2 +- .../ParserCache/ParserCacheAdapter.php | 2 +- .../Form/ChoiceList/ArrayChoiceList.php | 2 +- .../Factory/CachingFactoryDecorator.php | 4 +-- .../Validator/Context/ExecutionContext.php | 3 +-- .../Context/ExecutionContextFactory.php | 3 +-- .../Violation/ConstraintViolationBuilder.php | 3 +-- 13 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php index cd966ea986f79..4140629d934b6 100644 --- a/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php +++ b/src/Symfony/Bridge/Doctrine/Form/ChoiceList/IdReader.php @@ -20,7 +20,7 @@ * * @author Bernhard Schussek * - * @internal This class is meant for internal use only. + * @internal */ class IdReader { diff --git a/src/Symfony/Component/Debug/DebugClassLoader.php b/src/Symfony/Component/Debug/DebugClassLoader.php index d668bfddc4f44..43ab3a78bf551 100644 --- a/src/Symfony/Component/Debug/DebugClassLoader.php +++ b/src/Symfony/Component/Debug/DebugClassLoader.php @@ -168,8 +168,8 @@ public function loadClass($class) $parent = get_parent_class($class); $doc = $refl->getDocComment(); - if (preg_match('#\n \* @internal(?: .+?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { - self::$internal[$name] = true; + if (preg_match('#\n \* @internal(?:( .+?)\.?)?\r?\n \*(?: @|/$)#s', $doc, $notice)) { + self::$internal[$name] = isset($notice[1]) ? preg_replace('#\s*\r?\n \* +#', ' ', $notice[1]) : ''; } // Not an interface nor a trait @@ -228,25 +228,9 @@ public function loadClass($class) } } - foreach (call_user_func(function () use ($name, $parent) { - if (isset(self::$internal[$parent])) { - yield 'class' => $parent; - } - - foreach (class_implements($name, false) as $interface) { - if (isset(self::$internal[$interface])) { - yield 'interface' => $interface; - } - } - - foreach (class_uses($name, false) as $use) { - if (isset(self::$internal[$use])) { - yield 'trait' => $use; - } - } - }) as $type => $internalUse) { - if (strncmp($ns, $internalUse, $len)) { - @trigger_error(sprintf('The "%s" %s is considered internal. It may change without further notice. You should not use it from "%s".', $internalUse, $type, $name), E_USER_DEPRECATED); + foreach (array_merge(array($parent), class_implements($name, false), class_uses($name, false)) as $use) { + if (isset(self::$internal[$use]) && strncmp($ns, $use, $len)) { + @trigger_error(sprintf('The "%s" %s is considered internal%s. It may change without further notice. You should not use it from "%s".', $use, class_exists($use, false) ? 'class' : (interface_exists($use, false) ? 'interface' : 'trait'), self::$internal[$use], $name), E_USER_DEPRECATED); } } diff --git a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php index 9a873a6ba740c..a53c17b0e4983 100644 --- a/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php +++ b/src/Symfony/Component/Debug/Tests/DebugClassLoaderTest.php @@ -325,7 +325,7 @@ class_exists('Test\\'.__NAMESPACE__.'\\ExtendsInternals', true); restore_error_handler(); $this->assertSame($deprecations, array( - 'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', + 'The "Symfony\Component\Debug\Tests\Fixtures\InternalClass" class is considered internal since version 3.4. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', 'The "Symfony\Component\Debug\Tests\Fixtures\InternalInterface" interface is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', 'The "Symfony\Component\Debug\Tests\Fixtures\InternalTrait" trait is considered internal. It may change without further notice. You should not use it from "Test\Symfony\Component\Debug\Tests\ExtendsInternals".', )); diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php index ecb2944f51cf5..745fa462162f5 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalClass.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @internal this class is internal. + * @internal since version 3.4. */ class InternalClass { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalInterface.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalInterface.php index a806ca98eb86f..dd79f501e83c9 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalInterface.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalInterface.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @internal this interface is internal. + * @internal */ interface InternalInterface { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait.php index 81ecf4c884979..7bb4635cc4ba7 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @internal this trait is internal. + * @internal */ trait InternalTrait { diff --git a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php index 9f18a358cd092..c14a1d6dfba93 100644 --- a/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php +++ b/src/Symfony/Component/Debug/Tests/Fixtures/InternalTrait2.php @@ -3,7 +3,7 @@ namespace Symfony\Component\Debug\Tests\Fixtures; /** - * @internal this trait is internal but should not trigger a deprecation as it's used in the same vendor. + * @internal */ trait InternalTrait2 { diff --git a/src/Symfony/Component/ExpressionLanguage/ParserCache/ParserCacheAdapter.php b/src/Symfony/Component/ExpressionLanguage/ParserCache/ParserCacheAdapter.php index 2867aa3d4841b..a3e227d0a5ccd 100644 --- a/src/Symfony/Component/ExpressionLanguage/ParserCache/ParserCacheAdapter.php +++ b/src/Symfony/Component/ExpressionLanguage/ParserCache/ParserCacheAdapter.php @@ -18,7 +18,7 @@ /** * @author Alexandre GESLIN * - * @internal This class should be removed in Symfony 4.0. + * @internal and will be removed in Symfony 4.0. */ class ParserCacheAdapter implements CacheItemPoolInterface { diff --git a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php index a7c282849263b..50658c3911f1c 100644 --- a/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php +++ b/src/Symfony/Component/Form/ChoiceList/ArrayChoiceList.php @@ -185,7 +185,7 @@ public function getValuesForChoices(array $choices) * corresponding values * @param array $structuredValues The values indexed by the original keys * - * @internal Must not be used by user-land code + * @internal */ protected function flatten(array $choices, $value, &$choicesByValues, &$keysByValues, &$structuredValues) { diff --git a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php index 6580e661d4d66..d3460ecacad94 100644 --- a/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php +++ b/src/Symfony/Component/Form/ChoiceList/Factory/CachingFactoryDecorator.php @@ -48,7 +48,7 @@ class CachingFactoryDecorator implements ChoiceListFactoryInterface * * @return string The SHA-256 hash * - * @internal Should not be used by user-land code. + * @internal */ public static function generateHash($value, $namespace = '') { @@ -71,7 +71,7 @@ public static function generateHash($value, $namespace = '') * @param array $array The array to flatten * @param array $output The flattened output * - * @internal Should not be used by user-land code + * @internal */ private static function flatten(array $array, &$output) { diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index 1dc982c2719ff..0ffdcb82a03b5 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -30,8 +30,7 @@ * * @see ExecutionContextInterface * - * @internal You should not instantiate or use this class. Code against - * {@link ExecutionContextInterface} instead. + * @internal since version 3.5. Code against ExecutionContextInterface instead. */ class ExecutionContext implements ExecutionContextInterface { diff --git a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php index 8182c41f8c1ca..32f004b5d0e9e 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContextFactory.php @@ -19,8 +19,7 @@ * * @author Bernhard Schussek * - * @internal You should not instantiate or use this class. Code against - * {@link ExecutionContextFactoryInterface} instead. + * @internal version 2.5. Code against ExecutionContextFactoryInterface instead. */ class ExecutionContextFactory implements ExecutionContextFactoryInterface { diff --git a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php index bf887a08ec2b8..ff4d295cf31ae 100644 --- a/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php +++ b/src/Symfony/Component/Validator/Violation/ConstraintViolationBuilder.php @@ -22,8 +22,7 @@ * * @author Bernhard Schussek * - * @internal You should not instantiate or use this class. Code against - * {@link ConstraintViolationBuilderInterface} instead. + * @internal since version 2.5. Code against ConstraintViolationBuilderInterface instead. */ class ConstraintViolationBuilder implements ConstraintViolationBuilderInterface { From b62dfb26d361cf1f5c8ca787339d64970d58ab51 Mon Sep 17 00:00:00 2001 From: Guilhem Niot Date: Sun, 6 Aug 2017 19:16:52 +0200 Subject: [PATCH 3/3] Update ExecutionContext.php --- src/Symfony/Component/Validator/Context/ExecutionContext.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Validator/Context/ExecutionContext.php b/src/Symfony/Component/Validator/Context/ExecutionContext.php index 0ffdcb82a03b5..9c50a4f0f685b 100644 --- a/src/Symfony/Component/Validator/Context/ExecutionContext.php +++ b/src/Symfony/Component/Validator/Context/ExecutionContext.php @@ -30,7 +30,7 @@ * * @see ExecutionContextInterface * - * @internal since version 3.5. Code against ExecutionContextInterface instead. + * @internal since version 2.5. Code against ExecutionContextInterface instead. */ class ExecutionContext implements ExecutionContextInterface { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy