class AttributeRouteDiscoveryTest

Same name and namespace in other branches
  1. 11.x core/tests/Drupal/Tests/Core/Routing/AttributeRouteDiscoveryTest.php \Drupal\Tests\Core\Routing\AttributeRouteDiscoveryTest

Tests \Drupal\Core\Routing\AttributeRouteDiscovery.

Attributes

#[CoversClass(AttributeRouteDiscovery::class)] #[Group('Routing')]

Hierarchy

Expanded class hierarchy of AttributeRouteDiscoveryTest

File

core/tests/Drupal/Tests/Core/Routing/AttributeRouteDiscoveryTest.php, line 25

Namespace

Drupal\Tests\Core\Routing
View source
class AttributeRouteDiscoveryTest extends UnitTestCase {
  
  /**
   * The discovered route collection.
   */
  protected RouteCollection $routeCollection;
  
  /**
   * {@inheritdoc}
   */
  protected function setUp() : void {
    parent::setUp();
    $event = new RouteBuildEvent(new RouteCollection());
    $namespaces = new \ArrayObject([
      'Drupal\\router_test' => $this->root . '/core/modules/system/tests/modules/router_test_directory/src',
    ]);
    $discovery = new AttributeRouteDiscovery($namespaces);
    $discovery->onRouteBuild($event);
    $this->routeCollection = $event->getRouteCollection();
  }
  
  /**
   * @legacy-covers ::onRouteBuild
   */
  public function testOnRouteBuildWithArrayNamespaceDirectories() : void {
    $event = new RouteBuildEvent(new RouteCollection());
    $namespaces = new \ArrayObject([
      'Drupal\\router_test' => [
        $this->root . '/core/modules/system/tests/modules/router_test_directory/missing',
        $this->root . '/core/modules/system/tests/modules/router_test_directory/src',
      ],
    ]);
    $discovery = new AttributeRouteDiscovery($namespaces);
    $discovery->onRouteBuild($event);
    $route = $event->getRouteCollection()
      ->get('router_test.method_attribute');
    $this->assertNotNull($route);
    $this->assertSame('/test_method_attribute', $route->getPath());
    $this->assertSame(TestAttributes::class . '::attributeMethod', $route->getDefault('_controller'));
  }
  
  /**
   * @legacy-covers ::onRouteBuild
   */
  public function testOnRouteBuild() : void {
    $this->assertNotEmpty($this->routeCollection);
    $route1 = $this->routeCollection
      ->get('router_test.method_attribute');
    $this->assertNotNull($route1);
    $this->assertSame('/test_method_attribute', $route1->getPath());
    $this->assertSame(TestAttributes::class . '::attributeMethod', $route1->getDefault('_controller'));
    $this->assertSame("TRUE", $route1->getRequirement('_access'));
    $this->assertSame($route1, $this->routeCollection
      ->get('router_test.alias_test'));
    $route2 = $this->routeCollection
      ->get('router_test.class_invoke');
    $this->assertNotNull($route2);
    $this->assertSame('/test_class_attribute', $route2->getPath());
    $this->assertSame(TestClassAttribute::class, $route2->getDefault('_controller'));
    $this->assertSame("TRUE", $route2->getRequirement('_access'));
    $this->assertSame($route2, $this->routeCollection
      ->get(TestClassAttribute::class . '::__invoke'));
    $route3 = $this->routeCollection
      ->get('router_test.method_attribute_other');
    $this->assertNotNull($route3);
    $this->assertSame('/test_method_attribute-other-path', $route3->getPath());
    $this->assertSame(TestAttributes::class . '::attributeMethod', $route3->getDefault('_controller'));
    $this->assertSame("TRUE", $route3->getRequirement('_access'));
    $formRoute = $this->routeCollection
      ->get('router_test.form_route');
    $this->assertNotNull($formRoute);
    $this->assertSame('/test-form-route', $formRoute->getPath());
    $this->assertSame(TestRouteAttributeForm::class, $formRoute->getDefault('_form'));
    $this->assertSame('router_test.form_route', $this->routeCollection
      ->getAlias(TestRouteAttributeForm::class)
      ->getId());
    $this->assertNull($this->routeCollection
      ->get('router_test.invalid_controller_route'));
    // Since Route attributes on form class methods are not supported, add a
    // virtual form class that has a Route attribute on a method to confirm
    // there that there is an AssertionError thrown.
    vfsStream::setup('router_test_invalid');
    vfsStream::create([
      'src' => [
        'Form' => [
          'TestRouteInvalidAttributeForm.php' => '',
        ],
      ],
    ]);
    file_put_contents('vfs://router_test_invalid/src/Form/TestRouteInvalidAttributeForm.php', <<<'EOF'
    <?php
    
    declare(strict_types=1);
    
    namespace Drupal\router_test_invalid\Form;
    
    use Drupal\Core\Form\FormBase;
    use Drupal\Core\Form\FormStateInterface;
    use Symfony\Component\Routing\Attribute\Route;
    
    class TestRouteInvalidAttributeForm extends FormBase {
    
      public function getFormId(): string {
        return 'invalid_router_test';
      }
    
      public function buildForm(array $form, FormStateInterface $form_state): array {
        return $form;
      }
    
      public function submitForm(array &$form, FormStateInterface $form_state): void {
      }
    
      #[Route(
        path: '/invalid-form-method-route',
        name: 'router_test.invalid_form_method_route',
      )]
      public function invalidFormMethodRoute(): array {
        return [];
      }
    
    }
    
    EOF);
    $event = new RouteBuildEvent(new RouteCollection());
    $namespaces = new \ArrayObject([
      'Drupal\\router_test_invalid' => vfsStream::url('router_test_invalid/src'),
    ]);
    $additionalClassLoader = new ClassLoader();
    $additionalClassLoader->addPsr4("Drupal\\router_test_invalid\\", vfsStream::url('router_test_invalid/src'));
    $additionalClassLoader->register(TRUE);
    $discovery = new AttributeRouteDiscovery($namespaces);
    $this->expectException(\AssertionError::class);
    $this->expectExceptionMessage('Route attributes can not target methods on class Drupal\\router_test_invalid\\Form\\TestRouteInvalidAttributeForm. Use the attribute on the form class itself.');
    $discovery->onRouteBuild($event);
  }
  
  /**
   * Tests all supported route properties.
   *
   * @legacy-covers ::onRouteBuild
   * @legacy-covers ::addRoute
   */
  public function testAllRouteProperties() : void {
    $route = $this->routeCollection
      ->get('router_test.all_properties');
    $this->assertNotNull($route);
    $this->assertSame('/test_all_properties/{parameter}', $route->getPath());
    $this->assertSame(TestAttributes::class . '::allProperties', $route->getDefault('_controller'));
    $this->assertSame('Test all properties', $route->getDefault('_title'));
    $this->assertSame('1', $route->getDefault('parameter'));
    $this->assertSame('TRUE', $route->getRequirement('_access'));
    $this->assertSame('\\d+', $route->getRequirement('parameter'));
    $options = $route->getOptions();
    $this->assertTrue($options['_admin_route']);
    $this->assertTrue($options['utf8']);
    $this->assertSame(RouteCompiler::class, $options['compiler_class']);
    $this->assertSame('{subdomain}.example.com', $route->getHost());
    $this->assertSame([
      'GET',
      'POST',
    ], $route->getMethods());
    $this->assertSame([
      'https',
    ], $route->getSchemes());
    $this->assertSame($route, $this->routeCollection
      ->get('router_test.all_properties_alias'));
    $this->expectUserDeprecationMessage('Since drupal/core X.0.0: The "router_test.all_properties_deprecated" route is deprecated.');
    $this->assertSame($route, $this->routeCollection
      ->get('router_test.all_properties_deprecated'));
    // Auto-generated class::method alias.
    $this->assertSame($route, $this->routeCollection
      ->get(TestAttributes::class . '::allProperties'));
  }
  
  /**
   * Tests that class-only #[Route] registers a route for invokable controllers.
   */
  public function testClassOnlyRouteWithInvoke() : void {
    $route = $this->routeCollection
      ->get('router_test.class_only');
    $this->assertNotNull($route);
    $this->assertSame('/test_class_attribute_class_only', $route->getPath());
    $this->assertSame(TestClassAttributeClassOnly::class, $route->getDefault('_controller'));
    $this->assertSame('TRUE', $route->getRequirement('_access'));
    // Aliases for invokable controllers must also be registered.
    $this->assertSame($route, $this->routeCollection
      ->get(TestClassAttributeClassOnly::class . '::__invoke'));
  }
  
  /**
   * Tests that invalid controller classes do not break route discovery.
   */
  public function testInvalidClasses() : void {
    $this->assertNotNull($this->routeCollection
      ->get('router_test.method_attribute'));
    $this->assertNotNull($this->routeCollection
      ->get('router_test.class_invoke'));
    $this->assertNull($this->routeCollection
      ->get('router_test.missing_dependency'));
  }
  
  /**
   * Tests that a method inherits class-level globals.
   */
  public function testClassGlobalsInheritance() : void {
    // The route name is the class prefix + method name.
    $route = $this->routeCollection
      ->get('router_test.class_inherits');
    $this->assertNotNull($route);
    // Path is prefixed with the class path.
    $this->assertSame('/test_class_attribute/inherits', $route->getPath());
    // Controller is automatically configured.
    $this->assertSame(TestClassAttribute::class . '::inherits', $route->getDefault('_controller'));
    // Everything else is inherited.
    $this->assertSame('from_class', $route->getDefault('default_a'));
    $this->assertSame('Class title', $route->getDefault('_title'));
    $this->assertSame('TRUE', $route->getRequirement('_access'));
    $this->assertSame('from_class', $route->getOption('option_a'));
    $this->assertSame(RouteCompiler::class, $route->getOption('compiler_class'));
    $this->assertSame([
      'GET',
    ], $route->getMethods());
    $this->assertSame([
      'http',
    ], $route->getSchemes());
  }
  
  /**
   * Tests that method-level properties correctly merge with class globals.
   */
  public function testClassGlobalsMerging() : void {
    // The route name is the class prefix + method name.
    $route = $this->routeCollection
      ->get('router_test.class_overrides');
    $this->assertNotNull($route);
    // Path is prefixed with the class path.
    $this->assertSame('/test_class_attribute/overrides/{id}', $route->getPath());
    // Controller is automatically configured.
    $this->assertSame(TestClassAttribute::class . '::overrides', $route->getDefault('_controller'));
    // Defaults: method key overrides class key, class-only key is inherited.
    $this->assertSame('from_method', $route->getDefault('default_a'));
    $this->assertSame('from_method', $route->getDefault('default_b'));
    $this->assertSame('Class title', $route->getDefault('_title'));
    // Requirements: access is inherited, id key is added.
    $this->assertSame('TRUE', $route->getRequirement('_access'));
    $this->assertSame('\\d+', $route->getRequirement('id'));
    // Options: method key overrides class key, method-only key is added.
    $this->assertSame('from_method', $route->getOption('option_a'));
    $this->assertSame('from_method', $route->getOption('option_b'));
    // Host: method overrides class (class had no host set).
    $this->assertSame('method.example.com', $route->getHost());
    // Methods: union of class ['GET'] and method ['POST'].
    $methods = $route->getMethods();
    $this->assertContains('GET', $methods);
    $this->assertContains('POST', $methods);
    $this->assertCount(2, $methods);
    // Schemes: union of class ['http'] and method ['https'].
    $schemes = $route->getSchemes();
    $this->assertContains('http', $schemes);
    $this->assertContains('https', $schemes);
    $this->assertCount(2, $schemes);
  }

}

Members

Title Sort descending Deprecated Modifiers Object type Summary Overriden Title Overrides
AttributeRouteDiscoveryTest::$routeCollection protected property The discovered route collection.
AttributeRouteDiscoveryTest::setUp protected function Overrides UnitTestCase::setUp
AttributeRouteDiscoveryTest::testAllRouteProperties public function Tests all supported route properties.
AttributeRouteDiscoveryTest::testClassGlobalsInheritance public function Tests that a method inherits class-level globals.
AttributeRouteDiscoveryTest::testClassGlobalsMerging public function Tests that method-level properties correctly merge with class globals.
AttributeRouteDiscoveryTest::testInvalidClasses public function Tests that invalid controller classes do not break route discovery.
AttributeRouteDiscoveryTest::testOnRouteBuild public function @legacy-covers ::onRouteBuild
DrupalTestCaseTrait::$root protected property The Drupal root directory.
DrupalTestCaseTrait::checkErrorHandlerOnTearDown public function Checks the test error handler after test execution. 1
DrupalTestCaseTrait::getDrupalRoot Deprecated protected static function Returns the Drupal root directory. 1
DrupalTestCaseTrait::setDebugDumpHandler public static function Registers the dumper CLI handler when the DebugDump extension is enabled.
DrupalTestCaseTrait::setUpRoot final protected function Ensure that the $root property is set initially.
RandomGeneratorTrait::getRandomGenerator protected function Gets the random generator for the utility methods.
RandomGeneratorTrait::randomMachineName protected function Generates a unique random string containing letters and numbers.
RandomGeneratorTrait::randomObject public function Generates a random PHP object.
RandomGeneratorTrait::randomString public function Generates a pseudo-random string of ASCII characters of codes 32 to 126.
UnitTestCase::getClassResolverStub protected function Returns a stub class resolver.
UnitTestCase::getConfigFactoryStub public function Returns a stub config factory that behaves according to the passed array.
UnitTestCase::getContainerWithCacheTagsInvalidator protected function Sets up a container with a cache tags invalidator.
UnitTestCase::getStringTranslationStub public function Returns a stub translation manager that just returns the passed string.
UnitTestCase::setupMockIterator protected function Set up a traversable class mock to return specific items when iterated.

Buggy or inaccurate documentation? Please file an issue. Need support? Need help programming? Connect with the Drupal community.