Score:3

How to create a custom content entity without canonical url?

ru flag

I've created a custom content entity with drush generate module-content-entity, which works great. However, I want my custom entities NOT to have a canonical url. Drush automatically creates those in the annotation of the entity class:

/**
 * Defines the chunk entity class.
 *
 * @ContentEntityType(
 *   id = "chunk",
 *   ...
 *   links = {
 *     "add-form" = "/admin/content/chunk/add/{chunk_type}",
 *     "add-page" = "/admin/content/chunk/add",
 *     "canonical" = "/chunk/{chunk}",
 *     "edit-form" = "/admin/content/chunk/{chunk}/edit",
 *     "delete-form" = "/admin/content/chunk/{chunk}/delete",
 *     "collection" = "/admin/content/chunk"
 *   },
 *   ...
 * )
 */

I've tried to remove the canonical entry from the annotation, but this resulted in numerous errors left and right, because a lot of core and contrib modules assume that canonical URLs always exist. In my case most of those errors are coming from other modules calling functions that end up in EntityBase::toUrl, which fails with a WSOD due the missing canonical entry.

Even though this seems to be mandatory (@see EntityTypeInterface), some others - e.g. Paragraphs module - somehow managed to remove the canonical link, without causing errors everywhere.

Can anybody give me some hints how to safely remove the canonical link from my custom content entity? I do not want to turn the canonical links into 403's or 404's, I'd prefer those links not ever being rendered at all (hide "View" primary tab, prevent links when rendering entity labels, etc).

Jaypan avatar
de flag
I don't know what errors you are getting, but I have created multiple entities without canonical links. I even have entities that have no links defined at all. They're only ever used in code, not on the front end.
4uk4 avatar
cn flag
"*a lot of core and contrib modules assume that canonical URLs always exist*" - Yes and no, No, because this [entity skeleton](https://www.drupal.org/docs/8/api/entity-api/creating-a-custom-content-entity#s-entity-skeleton) works. Yes, because the standard entity handlers assume certain properties in the entity definition. Solution: You need your own handlers.
ru flag
In my case most seem to be originating from `EntityBase::toLink / ::toUrl`, which is called by other modules and fails with an WSOD when removing the canonical entry from the annotation
4uk4 avatar
cn flag
Using these methods with a paragraph results in: *No link template 'canonical' found for the 'paragraph' entity type*
Score:1
tr flag

Best way to do it is to define your own html route_provider

In your @ContentEntityType definition:

"route_provider" = {
  "html" = "Drupal\chunk\Routing\ChunkHtmlRouteProvider",
}

And the /chunk/src/Routing/ChunkHtmlRouteProvider.php itself:

<?php

namespace Drupal\chunk\Routing;

use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Routing\AdminHtmlRouteProvider;

class ChunkHtmlRouteProvider extends AdminHtmlRouteProvider {

  protected function getCanonicalRoute(EntityTypeInterface $entity_type) {
    return $this->getEditFormRoute($entity_type);
  }

}
Score:0
ru flag

It is actually really easy, except removing all references to canonical from the boilerplate code I just needed to override one single function toUrl in my custom entity class:

src/Entity/MyContentEntity.php

/**
 * Defines the chunk entity class.
 * Note the missing "links = { canonical }" entry in the annotation
 *
 *
 * @ContentEntityType(
 *   id = "chunk",
 *   ...
 *   links = {
 *     "add-form" = "/admin/content/chunk/add/{chunk_type}",
 *     "add-page" = "/admin/content/chunk/add",
 *     "edit-form" = "/admin/content/chunk/{chunk}/edit",
 *     "delete-form" = "/admin/content/chunk/{chunk}/delete",
 *     "collection" = "/admin/content/chunk"
 *   },
 *   ...
 * )
 */
class Chunk extends ContentEntityBase implements ChunkInterface {
  /**
   * this prevents WSOD when 3rd party modules call $entity->toUrl
   */
  public function toUrl($rel = 'canonical', array $options = []) {
    if ($rel == 'canonical') {
      return Url::fromUri('route:<nolink>')->setOptions($options);
    }
    else {
      return parent::toUrl($rel, $options);
    }
  }
}

The other stuff here is only needed if the entity is based on an auto-generated boilerplate code from drush generate or similar:

src/Form/MyContentEntityForm.php

/**
 * The following change is only necessary if you use boilerplate code from "drush generate" or similar
 */
class ChunkForm extends ContentEntityForm {
  public function save(array $form, FormStateInterface $form_state) {
    ...
    // change the following line
    //$form_state->setRedirect('entity.chunk.canonical', ['chunk' => $entity->id()]);
    // to something of your choice
    $form_state->setRedirect('entity.chunk.edit-form', ['chunk' => $entity->id()]);
  }

}

my_module.links.task.yml:

# If existing, remove the following boilerplate code from "drush generate" or similar

entity.chunk.view:
  title: View
  route_name: entity.chunk.canonical
  base_route: entity.chunk.canonical
wranvaud avatar
us flag
Error: Class 'Drupal\my_content_entity\Entity\Url' not found
kiwimind avatar
gg flag
@wranvaud realise this is old, however thought it might be useful to point out that you will need to add `use Drupal\Core\Url;` to make sure it's included. Also worth pointing out that if you want to view the entity listing page rather than the edit form when creating a new entity, swap out `edit_form` for `collection` in `src/Form/MyContentEntityForm.php`.
tr flag
I usually override `toUrl` method to point to the edit link instead of route:<nolink>
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.