|
2 | 2 |
|
3 | 3 | namespace Drupal\dkan_js_frontend\Controller; |
4 | 4 |
|
5 | | -use Drupal\Core\Controller\ControllerBase; |
| 5 | +use Drupal\Core\Cache\Cache; |
| 6 | +use Drupal\Core\Cache\CacheableMetadata; |
| 7 | +use Drupal\Core\Config\ConfigFactoryInterface; |
| 8 | +use Drupal\Core\Config\ImmutableConfig; |
6 | 9 | use Drupal\Core\DependencyInjection\ContainerInjectionInterface; |
7 | | -use Drupal\Core\Path\CurrentPathStack; |
| 10 | +use Drupal\Core\Http\Exception\CacheableNotFoundHttpException; |
| 11 | +use Drupal\Core\Render\RendererInterface; |
| 12 | +use Drupal\Core\Routing\RouteMatchInterface; |
| 13 | +use Drupal\dkan_js_frontend\Routing\RouteProvider; |
8 | 14 | use Drupal\dkan_metastore\Exception\MissingObjectException; |
9 | | -use Drupal\dkan_metastore\MetastoreService; |
| 15 | +use Drupal\dkan_metastore\NodeWrapper\NodeDataFactory; |
10 | 16 | use Symfony\Component\DependencyInjection\ContainerInterface; |
11 | | -use Symfony\Component\HttpFoundation\RequestStack; |
12 | | -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
| 17 | +use Symfony\Component\HttpFoundation\Request; |
13 | 18 |
|
14 | 19 | /** |
15 | | - * The Page controller. |
| 20 | + * Page controller. |
| 21 | + * |
| 22 | + * Routes defined in the dkan_js_frontend.config.routes configuration use this |
| 23 | + * controller. |
| 24 | + * |
| 25 | + * For datastore routes, we check if the datastore identifier is valid, and if |
| 26 | + * not throw an exception signaling a 404 response. |
16 | 27 | */ |
17 | | -class Page extends ControllerBase implements ContainerInjectionInterface { |
| 28 | +class Page implements ContainerInjectionInterface { |
18 | 29 |
|
19 | 30 | /** |
20 | | - * Metastore service. |
21 | | - */ |
22 | | - private MetastoreService $metastoreService; |
23 | | - |
24 | | - /** |
25 | | - * The request stack. |
| 31 | + * Config for dkan_js_frontend. |
| 32 | + * |
| 33 | + * @var \Drupal\Core\Config\ImmutableConfig |
26 | 34 | */ |
27 | | - protected RequestStack $requestStack; |
| 35 | + protected readonly ImmutableConfig $frontendConfig; |
28 | 36 |
|
29 | 37 | /** |
30 | | - * The current path. |
| 38 | + * Node data factory service. |
| 39 | + * |
| 40 | + * @var \Drupal\dkan_metastore\NodeWrapper\NodeDataFactory |
31 | 41 | */ |
32 | | - protected CurrentPathStack $currentPath; |
| 42 | + protected readonly NodeDataFactory $nodeDataFactory; |
33 | 43 |
|
34 | 44 | /** |
35 | | - * Inherited. |
36 | | - * |
37 | | - * {@inheritdoc} |
| 45 | + * {@inheritDoc} |
38 | 46 | */ |
39 | 47 | public static function create(ContainerInterface $container) { |
40 | 48 | return new static( |
41 | | - $container->get('dkan.metastore.service'), |
42 | | - $container->get('path.current'), |
43 | | - $container->get('request_stack'), |
| 49 | + $container->get('config.factory'), |
| 50 | + $container->get('dkan.metastore.metastore_item_factory'), |
44 | 51 | ); |
45 | 52 | } |
46 | 53 |
|
47 | 54 | /** |
48 | 55 | * Constructor. |
| 56 | + * |
| 57 | + * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory |
| 58 | + * Config factory service. |
| 59 | + * @param \Drupal\dkan_metastore\NodeWrapper\NodeDataFactory $nodeDataFactory |
| 60 | + * Node data factory service. |
49 | 61 | */ |
50 | | - public function __construct(MetastoreService $service, CurrentPathStack $current_path, RequestStack $request_stack) { |
51 | | - $this->metastoreService = $service; |
52 | | - $this->currentPath = $current_path; |
53 | | - $this->requestStack = $request_stack; |
| 62 | + public function __construct( |
| 63 | + ConfigFactoryInterface $configFactory, |
| 64 | + NodeDataFactory $nodeDataFactory, |
| 65 | + ) { |
| 66 | + $this->frontendConfig = $configFactory->get('dkan_js_frontend.config'); |
| 67 | + $this->nodeDataFactory = $nodeDataFactory; |
54 | 68 | } |
55 | 69 |
|
56 | 70 | /** |
57 | | - * Returns a render-able array. |
58 | | - */ |
59 | | - public function content() { |
60 | | - // Checking for 404 prevents an infinite loop. |
61 | | - if ($this->requestStack->getCurrentRequest()->query->get('_exception_statuscode') !== 404) { |
62 | | - $this->handleInvalidDatasetId(); |
63 | | - } |
64 | | - |
65 | | - return [ |
66 | | - '#theme' => 'page__dkan_js_frontend', |
67 | | - ]; |
68 | | - } |
69 | | - |
70 | | - /** |
71 | | - * If a dataset with an invalid ID is being requested, throw a 404 error. |
| 71 | + * Make a renderable page. |
| 72 | + * |
| 73 | + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match |
| 74 | + * Route match for this request. |
| 75 | + * @param \Symfony\Component\HttpFoundation\Request $request |
| 76 | + * This request. |
| 77 | + * |
| 78 | + * @return array |
| 79 | + * Render array. |
| 80 | + * |
| 81 | + * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException |
| 82 | + * Throws a not-found exception for dataset routes which have an invalid |
| 83 | + * identifier. |
72 | 84 | */ |
73 | | - protected function handleInvalidDatasetId() { |
74 | | - // Path should always have leading slash. |
75 | | - // @see \Symfony\Component\HttpFoundation\Request::getPathInfo() |
76 | | - // Match any path that equals or begins with /dataset/[ID]. |
77 | | - $dataset_path_match = '/^\/dataset\/(?P<id>[^\/]+)/'; |
| 85 | + public function content(RouteMatchInterface $route_match, Request $request) { |
| 86 | + $cacheable_metadata = (new CacheableMetadata()) |
| 87 | + // This is the default but let's make it explicit. |
| 88 | + ->setCacheMaxAge(Cache::PERMANENT) |
| 89 | + // Allow cache invalidation if we change config for this module. |
| 90 | + ->addCacheableDependency($this->frontendConfig) |
| 91 | + // Allow cache invalidation per NodeDataFactory. In practice, this is any |
| 92 | + // change to any data nodes. This is a wide net to allow for reasonably |
| 93 | + // easy cache invalidation. |
| 94 | + ->addCacheTags(NodeDataFactory::getCacheTags()); |
78 | 95 |
|
79 | | - $path = $this->currentPath->getPath(); |
80 | | - |
81 | | - if (preg_match($dataset_path_match, $path, $matches)) { |
| 96 | + // Check for valid dataset identifier on the dataset/api routes. |
| 97 | + // Checking for 404 prevents an infinite loop from the 404 we might cause |
| 98 | + // later. |
| 99 | + // @todo Make the dataset and dataset API routes their own controller and |
| 100 | + // config. |
| 101 | + if ( |
| 102 | + in_array($route_match->getRouteName(), [ |
| 103 | + RouteProvider::ROUTE_PREFIX . 'dataset', |
| 104 | + RouteProvider::ROUTE_PREFIX . 'datasetapi', |
| 105 | + ]) && |
| 106 | + ($request->query->get('_exception_statuscode') !== 404) |
| 107 | + ) { |
82 | 108 | try { |
83 | | - $this->metastoreService->get('dataset', $matches['id']); |
| 109 | + // Does this dataset exist? |
| 110 | + // @todo Figure out a way to find if the identifier exists without |
| 111 | + // triggering the lifecycle loading of tertiary nodes, etc. |
| 112 | + $dataset_item = $this->nodeDataFactory->getInstance( |
| 113 | + $route_match->getRawParameter('id') ?? '' |
| 114 | + ); |
| 115 | + // Allow cache invalidation if our dataset item changes at all. |
| 116 | + // @todo Ideally DKAN would give us an easy way to also get |
| 117 | + // cacheability metadata from other related nodes, such as |
| 118 | + // distribution. |
| 119 | + $cacheable_metadata->addCacheableDependency($dataset_item); |
84 | 120 | } |
| 121 | + // Handle if the dataset does not exist. |
85 | 122 | catch (MissingObjectException) { |
86 | | - throw new NotFoundHttpException(); |
| 123 | + // Throw an exception that tells Drupal send back a 404. Also use the |
| 124 | + // same caching, so that we won't query the DB about a given known-bad |
| 125 | + // identifier until after the cache is invalidated. |
| 126 | + throw new CacheableNotFoundHttpException($cacheable_metadata); |
87 | 127 | } |
88 | 128 | } |
| 129 | + |
| 130 | + $build = [ |
| 131 | + '#theme' => 'page__dkan_js_frontend', |
| 132 | + ]; |
| 133 | + $cacheable_metadata->applyTo($build); |
| 134 | + |
| 135 | + return $build; |
89 | 136 | } |
90 | 137 |
|
91 | 138 | } |
0 commit comments