From 7f3132ebe39831e78df46064e61fb94f2b29ab1c Mon Sep 17 00:00:00 2001 From: "Alexander M. Turek" Date: Sun, 24 May 2020 16:04:59 +0200 Subject: [PATCH] [FrameworkBundle] Fix MicroKernelTrait for php 8 --- .travis.yml | 2 - .../Kernel/MicroKernelTrait.php | 69 +++++++------ .../Tests/Kernel/MicroKernelTraitTest.php | 97 +++++++++++++++++++ 3 files changed, 138 insertions(+), 30 deletions(-) diff --git a/.travis.yml b/.travis.yml index 141ad74b08..73eec7a9eb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,6 @@ matrix: - php: nightly services: [memcached] fast_finish: true - allow_failures: - - php: nightly cache: directories: diff --git a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php index d2172dd6db..540c97672c 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php +++ b/src/Symfony/Bundle/FrameworkBundle/Kernel/MicroKernelTrait.php @@ -27,6 +27,9 @@ use Symfony\Component\Routing\RouteCollectionBuilder; * * @author Ryan Weaver * @author Fabien Potencier + * + * @method void configureRoutes(RoutingConfigurator $routes) + * @method void configureContainer(ContainerConfigurator $c) */ trait MicroKernelTrait { @@ -39,7 +42,7 @@ trait MicroKernelTrait * ->controller('App\Controller\AdminController::dashboard') * ; */ - //abstract protected function configureRoutes(RoutingConfigurator $routes); + //abstract protected function configureRoutes(RoutingConfigurator $routes): void; /** * Configures the container. @@ -58,7 +61,7 @@ trait MicroKernelTrait * * $c->parameters()->set('halloween', 'lot of fun'); */ - //abstract protected function configureContainer(ContainerConfigurator $c); + //abstract protected function configureContainer(ContainerConfigurator $c): void; /** * {@inheritdoc} @@ -87,8 +90,10 @@ trait MicroKernelTrait ], ]); + $kernelClass = false !== strpos(static::class, "@anonymous\0") ? parent::class : static::class; + if (!$container->hasDefinition('kernel')) { - $container->register('kernel', static::class) + $container->register('kernel', $kernelClass) ->addTag('controller.service_arguments') ->setAutoconfigured(true) ->setSynthetic(true) @@ -103,20 +108,22 @@ trait MicroKernelTrait $container->fileExists($this->getProjectDir().'/config/bundles.php'); try { + $configureContainer = new \ReflectionMethod($this, 'configureContainer'); + } catch (\ReflectionException $e) { + throw new \LogicException(sprintf('"%s" uses "%s", but does not implement the required method "protected function configureContainer(ContainerConfigurator $c): void".', get_debug_type($this), MicroKernelTrait::class), 0, $e); + } + + $configuratorClass = $configureContainer->getNumberOfParameters() > 0 && ($type = $configureContainer->getParameters()[0]->getType()) && !$type->isBuiltin() ? $type->getName() : null; + + if ($configuratorClass && !is_a(ContainerConfigurator::class, $configuratorClass, true)) { $this->configureContainer($container, $loader); return; - } catch (\TypeError $e) { - $file = $e->getFile(); - - if (0 !== strpos($e->getMessage(), sprintf('Argument 1 passed to %s::configureContainer() must be an instance of %s,', static::class, ContainerConfigurator::class))) { - throw $e; - } } // the user has opted into using the ContainerConfigurator /* @var ContainerPhpFileLoader $kernelLoader */ - $kernelLoader = $loader->getResolver()->resolve($file); + $kernelLoader = $loader->getResolver()->resolve($file = $configureContainer->getFileName()); $kernelLoader->setCurrentDir(\dirname($file)); $instanceof = &\Closure::bind(function &() { return $this->instanceof; }, $kernelLoader, $kernelLoader)(); @@ -133,12 +140,14 @@ trait MicroKernelTrait AbstractConfigurator::$valuePreProcessor = $valuePreProcessor; } - $container->setAlias(static::class, 'kernel')->setPublic(true); + $container->setAlias($kernelClass, 'kernel')->setPublic(true); }); } /** * @internal + * + * @return RouteCollection */ public function loadRoutes(LoaderInterface $loader) { @@ -149,28 +158,32 @@ trait MicroKernelTrait $collection = new RouteCollection(); try { - $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file)); + $configureRoutes = new \ReflectionMethod($this, 'configureRoutes'); + } catch (\ReflectionException $e) { + throw new \LogicException(sprintf('"%s" uses "%s", but does not implement the required method "protected function configureRoutes(RoutingConfigurator $routes): void".', get_debug_type($this), MicroKernelTrait::class), 0, $e); + } - foreach ($collection as $route) { - $controller = $route->getDefault('_controller'); + $configuratorClass = $configureRoutes->getNumberOfParameters() > 0 && ($type = $configureRoutes->getParameters()[0]->getType()) && !$type->isBuiltin() ? $type->getName() : null; - if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { - $route->setDefault('_controller', ['kernel', $controller[1]]); - } - } + if ($configuratorClass && !is_a(RoutingConfigurator::class, $configuratorClass, true)) { + trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class); - return $collection; - } catch (\TypeError $e) { - if (0 !== strpos($e->getMessage(), sprintf('Argument 1 passed to %s::configureRoutes() must be an instance of %s,', static::class, RouteCollectionBuilder::class))) { - throw $e; + $routes = new RouteCollectionBuilder($loader); + $this->configureRoutes($routes); + + return $routes->build(); + } + + $this->configureRoutes(new RoutingConfigurator($collection, $kernelLoader, $file, $file)); + + foreach ($collection as $route) { + $controller = $route->getDefault('_controller'); + + if (\is_array($controller) && [0, 1] === array_keys($controller) && $this === $controller[0]) { + $route->setDefault('_controller', ['kernel', $controller[1]]); } } - trigger_deprecation('symfony/framework-bundle', '5.1', 'Using type "%s" for argument 1 of method "%s:configureRoutes()" is deprecated, use "%s" instead.', RouteCollectionBuilder::class, self::class, RoutingConfigurator::class); - - $routes = new RouteCollectionBuilder($loader); - $this->configureRoutes($routes); - - return $routes->build(); + return $collection; } } diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php index 0addeed984..67b6425545 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -12,10 +12,18 @@ namespace Symfony\Bundle\FrameworkBundle\Tests\Kernel; use PHPUnit\Framework\TestCase; +use Psr\Log\NullLogger; +use Symfony\Bundle\FrameworkBundle\FrameworkBundle; +use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; use Symfony\Component\DependencyInjection\Loader\ClosureLoader; +use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\HttpKernel\HttpKernelInterface; +use Symfony\Component\HttpKernel\Kernel; +use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; require_once __DIR__.'/flex-style/src/FlexStyleMicroKernel.php'; @@ -77,4 +85,93 @@ class MicroKernelTraitTest extends TestCase self::assertSame('$ecret', $kernel->getContainer()->getParameter('kernel.secret')); } + + public function testAnonymousMicroKernel() + { + $kernel = new class('anonymous_kernel') extends MinimalKernel { + public function helloAction(): Response + { + return new Response('Hello World!'); + } + + protected function configureContainer(ContainerConfigurator $c): void + { + $c->extension('framework', [ + 'router' => ['utf8' => true], + ]); + $c->services()->set('logger', NullLogger::class); + } + + protected function configureRoutes(RoutingConfigurator $routes): void + { + $routes->add('hello', '/')->controller([$this, 'helloAction']); + } + }; + + $request = Request::create('/'); + $response = $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, false); + + $this->assertSame('Hello World!', $response->getContent()); + } + + public function testMissingConfigureContainer() + { + $kernel = new class('missing_configure_container') extends MinimalKernel { + protected function configureRoutes(RoutingConfigurator $routes): void + { + } + }; + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('"Symfony\Bundle\FrameworkBundle\Tests\Kernel\MinimalKernel@anonymous" uses "Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait", but does not implement the required method "protected function configureContainer(ContainerConfigurator $c): void".'); + + $kernel->boot(); + } + + public function testMissingConfigureRoutes() + { + $kernel = new class('missing_configure_routes') extends MinimalKernel { + protected function configureContainer(ContainerConfigurator $c): void + { + $c->extension('framework', [ + 'router' => ['utf8' => true], + ]); + } + }; + + $this->expectException(\LogicException::class); + $this->expectExceptionMessage('"Symfony\Bundle\FrameworkBundle\Tests\Kernel\MinimalKernel@anonymous" uses "Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait", but does not implement the required method "protected function configureRoutes(RoutingConfigurator $routes): void".'); + + $request = Request::create('/'); + $kernel->handle($request, HttpKernelInterface::MASTER_REQUEST, false); + } +} + +abstract class MinimalKernel extends Kernel +{ + use MicroKernelTrait; + + private $cacheDir; + + public function __construct(string $cacheDir) + { + parent::__construct('test', false); + + $this->cacheDir = sys_get_temp_dir().'/'.$cacheDir; + } + + public function registerBundles(): iterable + { + yield new FrameworkBundle(); + } + + public function getCacheDir(): string + { + return $this->cacheDir; + } + + public function getLogDir(): string + { + return $this->cacheDir; + } }