Reproduce steps:
- Install drupal8.6 and group;1.x-dev and commerce
- Create group_content:commerce_promotion plugin use these code
/**
* Provides a content enabler for nodes.
*
* @GroupContentEnabler(
* id = "group_promotion",
* label = @Translation("Group promotion"),
* description = @Translation("Adds promotion to groups both publicly and privately."),
* entity_type_id = "commerce_promotion",
* entity_access = TRUE,
* reference_label = @Translation("Title"),
* reference_description = @Translation("The title of the promotion to add to the group"),
* )
*/
class GroupPromotion extends GroupContentEnablerBase {
}
- Create a group_content-commerce_promotion, when click save, the site crash
The website encountered an unexpected error. Please try again later.</br></br><em class="placeholder">Drupal\Core\Entity\Exception\UndefinedLinkTemplateException</em>: No link template 'canonical' found for the 'commerce_promotion' entity type in <em class="placeholder">Drupal\Core\Entity\Entity->toUrl()</em> (line <em class="placeholder">224</em> of <em class="placeholder">core/lib/Drupal/Core/Entity/Entity.php</em>). <pre class="backtrace">group_content_entity_submit(Array, Object)
call_user_func_array('group_content_entity_submit', Array) (Line: 111)
Drupal\Core\Form\FormSubmitter->executeSubmitHandlers(Array, Object) (Line: 51)
Drupal\Core\Form\FormSubmitter->doSubmitForm(Array, Object) (Line: 589)
Drupal\Core\Form\FormBuilder->processForm('commerce_promotion_add_form', Array, Object) (Line: 318)
Drupal\Core\Form\FormBuilder->buildForm('commerce_promotion_add_form', Object) (Line: 48)
Drupal\Core\Entity\EntityFormBuilder->getForm(Object, 'add', Array) (Line: 361)
Drupal\group\Entity\Controller\GroupContentController->createForm(Object, 'group_promotion')
call_user_func_array(Array, Array) (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 582)
Drupal\Core\Render\Renderer->executeInRenderContext(Object, Object) (Line: 124)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext(Array, Array) (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->Drupal\Core\EventSubscriber\{closure}() (Line: 151)
Symfony\Component\HttpKernel\HttpKernel->handleRaw(Object, 1) (Line: 68)
Symfony\Component\HttpKernel\HttpKernel->handle(Object, 1, 1) (Line: 67)
Drupal\simple_oauth\HttpMiddleware\BasicAuthSwap->handle(Object, 1, 1) (Line: 57)
Drupal\Core\StackMiddleware\Session->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\KernelPreHandle->handle(Object, 1, 1) (Line: 99)
Drupal\page_cache\StackMiddleware\PageCache->pass(Object, 1, 1) (Line: 78)
Drupal\page_cache\StackMiddleware\PageCache->handle(Object, 1, 1) (Line: 41)
Drupal\jsonapi\StackMiddleware\FormatSetter->handle(Object, 1, 1) (Line: 47)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle(Object, 1, 1) (Line: 38)
Drupal\webprofiler\StackMiddleware\WebprofilerMiddleware->handle(Object, 1, 1) (Line: 52)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle(Object, 1, 1) (Line: 23)
Stack\StackedHttpKernel->handle(Object, 1, 1) (Line: 665)
Drupal\Core\DrupalKernel->handle(Object) (Line: 19)
</pre>
The error caused here:
group.module
if ($entity->access('view')) {
$form_state->setRedirectUrl($entity->toUrl());
}
Let's see the code of $entity->toUrl() (\Drupal\Core\Entity\Entity).
public function toUrl($rel = 'canonical', array $options = []) {
if ($this->id() === NULL) {
throw new EntityMalformedException(sprintf('The "%s" entity cannot have a URI as it does not have an ID', $this->getEntityTypeId()));
}
// The links array might contain URI templates set in annotations.
$link_templates = $this->linkTemplates();
// Links pointing to the current revision point to the actual entity. So
// instead of using the 'revision' link, use the 'canonical' link.
if ($rel === 'revision' && $this instanceof RevisionableInterface && $this->isDefaultRevision()) {
$rel = 'canonical';
}
if (isset($link_templates[$rel])) {
$route_parameters = $this->urlRouteParameters($rel);
$route_name = "entity.{$this->entityTypeId}." . str_replace(['-', 'drupal:'], ['_', ''], $rel);
$uri = new Url($route_name, $route_parameters);
}
else {
$bundle = $this->bundle();
// A bundle-specific callback takes precedence over the generic one for
// the entity type.
$bundles = $this->entityTypeBundleInfo()->getBundleInfo($this->getEntityTypeId());
if (isset($bundles[$bundle]['uri_callback'])) {
$uri_callback = $bundles[$bundle]['uri_callback'];
}
elseif ($entity_uri_callback = $this->getEntityType()->getUriCallback()) {
$uri_callback = $entity_uri_callback;
}
// Invoke the callback to get the URI. If there is no callback, use the
// default URI format.
if (isset($uri_callback) && is_callable($uri_callback)) {
$uri = call_user_func($uri_callback, $this);
}
else {
throw new UndefinedLinkTemplateException("No link template '$rel' found for the '{$this->getEntityTypeId()}' entity type");
}
}
// Pass the entity data through as options, so that alter functions do not
// need to look up this entity again.
$uri
->setOption('entity_type', $this->getEntityTypeId())
->setOption('entity', $this);
// Display links by default based on the current language.
// Link relations that do not require an existing entity should not be
// affected by this entity's language, however.
if (!in_array($rel, ['collection', 'add-page', 'add-form'], TRUE)) {
$options += ['language' => $this->language()];
}
$uri_options = $uri->getOptions();
$uri_options += $options;
return $uri->setOptions($uri_options);
}
If the entity don't have 'canonical' or bundle's uri_callback, then an Exception will be throw out.
Comments
Comment #2
lawxen commentedComment #3
lawxen commentedChange to Just catch UndefinedLinkTemplateException, not all exception
Comment #4
kristiaanvandeneyndeI don't think this is a problem in Group, but rather Commerce.
Every entity should support all methods on Entity or override them to adapt their behavior to make sense. Entity::toUrl() being no exception to that rule. So in my book it's up to Commerce to either:
That said, some quick googling has shown that some core maintainers feel not every entity should have a canonical URL. A statement I myself agree with. So I'd rather get some feedback from core maintainers on the subject and see a fix go into core (in Entity::toUrl() perhaps) or an advisory being issued urging maintainers to make sure the toUrl() method works on their entities.
See #2402533-83: Provide File::createFileUrl() as a replacement for the deprecated File:url() implementation for instance.
Comment #5
kristiaanvandeneyndeComment #6
berdirI don't understand what you would want to change/fix in core?
UndefinedLinkTemplateException is a documented exception in core that is thrown when calling that with an unsupported rel.
That's why there is a hasLinkTemplate() that should be used instead of just catching the exception.
Comment #7
alexpottThere are quite a few entities that have no URL and that's fine. Anything that works with generic entities needs to code with that in mind. As the interface for ::toUrl() states:
Yes the doc might be better but I don't want to try and work out what the URL representation of a content moderation state content entity should be.
Comment #8
bojanz commentedI agree with #6 and #7.
Many Commerce entities have no collection route. Group should fall back to the edit-form route if collection is missing.
Comment #9
kristiaanvandeneyndeIt's the signature of
Entity::toUrl()that bothers and concerns me:If it's okay for entities to not have a canonical link template, then why do we default to it?
You'd expect $entity->toUrl() to work on any entity if core is providing the defaults for you, right?
Comment #10
kristiaanvandeneyndeRe #6 caseylau made a good point on Slack:
Does every piece of code that wants to call
$entity->toUrl()on generic entities now have to both check whether the canonical link template exists or whether the entity type has a uri callback that will handle said template? Seems like a DX nightmare, to be honest.Re #7 I'm not arguing all entities should have a canonical URL, I'm saying core seems to indicate that they do. I also agree some content entities would be far better off without a canonical URL.
Comment #11
lawxen commented@Berdir hasLinkTemplate() is not enough, entity type itself and bundle can define their uri_callback, toUrl() has written code on these check, client user make these duplication checks maybe not appropriate.
Comment #12
kristiaanvandeneyndeActually, I was adamant on the fact that I was informed a long while ago canonical links are required. I found it! From
EntityTypeInterface::getLinkTemplates():That won't necessarily fix my use case (trying to redirect to a page), but I knew I had read somewhere that canonical link templates were required.
Comment #13
kristiaanvandeneyndeMoving this to core with the following suggestion:
default_link, to sit neatly belowlinksComment #14
avpadernoComment #15
avpadernoEntityInterface::url()is deprecated;EntityInterface::toUrl()should be instead used.Comment #23
acbramley commentedThis came up in the BSI daily triage. Is this still applicable on the latest versions of core and group?
Comment #24
kristiaanvandeneyndeThis was fixed in core already.
The docs now state:
And toUrl() has been changed to no longer default to 'canonical'
Will dig up the issue that fixed this.
Comment #26
kristiaanvandeneyndeRelated issues: