2

I miss the deprecated l() a lot. I am trying to construct the Drupal 8 equivalent of:

l(t('mylink'), 'admin/structure/types');

This call generates this HTML:

'<a href="/admin/structure/types">mylink</a>'

I want to know how to produce the same HTML (i.e. a string) using the Link class.

Please understand that I am not looking asking how to render the link. I need the string for other purposes than rendering. I fully aware of that usually, one wants to render the link. If that is what you want to do, you should look at the accepted answer to this question: How do I create a link?.

I am trying to generate an HTML link to'admin/structure/types'. But will appreciate an answer that explains how to do this for any valid internal path.

I've looked at a lot of examples, a most of the them end up saying one should use Link::fromTextAndUrl in some construct like this - without explaining how to get $routename from the path.:

Link::fromTextAndUrl(t('mylink'), Url::fromRoute($routeName));

However, by searching (as suggested by Jaypan) I was able to determine that the routename for this path was 'entity.node_type.collection'. This works as one would expect:

  $url = Url::fromRoute('entity.node_type.collection');
  $url_string = $url->toString();

I.e.: $url_string is now 'admin/structure/types'.

So I proceed to build a link object

$link = Link::fromTextAndUrl(t('mylink'), Url::fromRoute('entity.node_type.collection'));

Now: How to get an HTML string?

No Sssweat suggests:

$url = Url::fromRoute('book.admin');
$link_thingy = Link::fromTextAndUrl(t('mylink'), $url)->toString();

This step just make $link_thingy into an object. Close, but no cigar.

In an updated version of his answer, Jaypan says that treating this object like a string - example:

 print Link::fromTextAndUrl(t('mylink'), Url::fromRoute('user.login'))->toString();

outputs the HTML string. This works. This means you will have the HTML string when you use it in a context one would use a string. Therefore, I've accepted Jaypan's answer.

I've also tried out the kiamlaluno's answer, i.e.:

 $link_thingy = $link->toRenderable();

This produces a (huge) render array, not a string. A render array, however, is a very useful thing (see comments for why this is so), but it is not accepted as answer to this question because the question was: How to get an HTML string?

Environment: Clean install of Drupal 8.8.6 (core + devel). Ubuntu 20.04 LTS.

  • I've read your edit - I'm not sure exactly what you're looking for. Are you looking for an HTML string outputting the link? If so, $link->toString() will return a string, being an <a> tag linking to the URL. If you're not getting that, something is wrong on your system, or in your debugging. – Jaypan May 30 at 16:06
  • 2
    In Drupal 8 you rarely need the Link class. You build a render array containing '#type' => 'link' and don't render too early (yes, toString() is also rendering). Unless you need the link in a string for other purposes not connected with building page content. – 4k4 Jun 1 at 9:35
  • 2
    @FreeRadical, the problem with the question is that it is unclear why it is necessary to render the link and so is setting a bad example for normal site building where you should avoid rendering too early. This is bad for caching especially for internal links depending on database content. – 4k4 Jun 2 at 12:56
  • 1
    toString() leaks cache metadata and if you are not in a render context this metadata gets lost or you leak it in a place where it is causing errors, see for example drupal.stackexchange.com/questions/187086/… – 4k4 Jun 2 at 13:58
  • 1
    @leymannx , toString() of the URL is the critical part while rendering a link, so it doesn't help when you render it first. – 4k4 Jun 2 at 15:09
3

Getting the route can be tricky. Sometimes I'll just do a code search for the path.

Routes are defined in [MODULE].routing.yml files. You can often look for a route you need if you can guess the module that supplies it. For example, the user login page is provided by the user module, so I look in user.routing.yml, in which I find this for the user login route:

user.login:
  path: '/user/login'
  defaults:
    _form: '\Drupal\user\Form\UserLoginForm'
    _title: 'Log in'
  requirements:
    _user_is_logged_in: 'FALSE'
  options:
    _maintenance_access: TRUE

The route name here is user.login. Now, that route name can be used to do the following:

$url = Url::fromRoute('user.login');
// Get the URL string:
$url_string = $url->toString();
// Build a link:
$link = Link::fromTextAndUrl(t('Log In'), $url);
// Get the link string:
$link_string = $link->toString();

Note that $link->toString() returns an object of type Drupal\Core\GeneratedLink. This class has the magic method GeneratedLink::__toString(). This magic method is invoked when the object is treated like a string. That means you can treat the result of $link->toString() as if it were a string, even though it's an object. So in context where you can print values, this will work:

print Link::fromTextAndUrl(t('mylink'), Url::fromRoute('user.login'))->toString();
| improve this answer | |
  • Important to note that while most routes are defined in a .routing.yml file, not all. A few routes are defined in a PHP file, like the node.edit route for example. – No Sssweat May 30 at 18:10
  • 1
    Ahh good call, entity routes are defined in the entity definitions. Also, routes can be altered in a RouteSubscriber service as well, or added in hook_entity_type_alter() as new entity links. – Jaypan May 30 at 18:28
3

Link is the correct class to use in those cases. Drupal core itself uses it to get a link to shown in a page or a form, for example in ImageFormatter::settingsForm().

  $description_link = Link::fromTextAndUrl($this->t('Configure Image Styles'), Url::fromRoute('entity.image_style.collection'));
  $element['image_style'] = [
    '#title' => t('Image style'),
    '#type' => 'select',
    '#default_value' => $this->getSetting('image_style'),
    '#empty_option' => t('None (original image)'),
    '#options' => $image_styles,
    '#description' => $description_link->toRenderable() + [
      '#access' => $this->currentUser->hasPermission('administer image styles'),
    ],
  ];

Link::toRenderable() returns a render array representing the object.

As for getting the Url object passed as second argument to that method. Url::fromRoute() is normally the used method. Otherwise, there are other methods that can be helpful, such as:

  • Url::fromUserInput() when the relative path is entered by a user and the path can exist or not

  • Url::fromEntityUri() when the URI is for an entity, for example entity:node/1

  • Url::fromInternalUri() for a generic internal URL given, for example internal:/node/add (the page to create a new node) or internal:/ (the front page)

| improve this answer | |
1

Nothing new here than what the other answers provide. Maybe laying it out like this will make it clearer.

Drupal 7:

// Internal path.
$internal_link = l(t('Book admin'), 'admin/structure/book');

// External Uri.
$external_link = l(t('External link'), 'http://www.example.com/', array('external' => TRUE));

Drupal 8 (deprecated):

// Internal path (defined by a route in Drupal 8).
use Drupal\Core\Url;
$url = Url::fromRoute('book.admin');
$internal_link = \Drupal::l(t('Book admin'), $url);

// External Uri.
use Drupal\Core\Url;
$url = Url::fromUri('http://www.example.com/');
$external_link = \Drupal::l(t('External link'), $url);

\Drupal::l has also been deprecated (as of Drupal 8.0.0). Use \Drupal\Core\Link instead.

Drupal 8 & 9:

use Drupal\Core\Link;
use Drupal\Core\Url;
$url = Url::fromRoute('book.admin');
$internal_link = Link::fromTextAndUrl(t('Book admin'), $url)->toString();

Source: Change record l() and url() are removed in favor of a routing based URL generation API

| improve this answer | |
1

Answering the part added later to the question:

Please understand that I am not looking asking how to render the link. I need the string for other purposes than rendering. I fully aware of that usually, one wants to render the link.

URL and Link objects are first of all data objects and you can create and put data in them at any point while building content. But when rendered, which includes applying the method toString(), they produce not only the HTML string but also cache and attachments metadata. Normally this metadata bubbles up when executed in a render context in theming hooks and templates. Not when rendered too early, then the metadata gets lost or causes errors. For example TrustedResponseRedirect failing - how to prevent cache metadata?.

A safe way to render non page content, for example for mails, is the method renderPlain(). See https://drupal.stackexchange.com/a/245353/47547, which you can use for all render elements, not only for links.

| improve this answer | |
  • To achieve OP's sample would that be like this then? $url = \Drupal\Core\Url::fromUri('internal:/admin/structure/types'); $link = \Drupal\Core\Link::fromTextAndUrl('mylink', $url)->toRenderable(); $html = \Drupal::service('renderer')->renderPlain($link)->__toString();? In the end $html would return <a href="/admin/structure/types">mylink</a>. – leymannx Jun 2 at 21:06
  • 1
    Yes, this should work. But depending on the use case, which still is unclear, for mails I would build the entire render array before putting it in renderPlain() or for JSON responses extract the string and the metadata separately, see drupal.stackexchange.com/a/225963/47547 – 4k4 Jun 2 at 21:23
  • First: I learned a lot about link and url objects from all the answers I got from this question. I am glad it wasn't closed. Second: As for the use case, I learned enough to ask another question, where the use case is more explicit. Please see: drupal.stackexchange.com/q/294139/12076 – Free Radical Jun 3 at 7:17
  • This question and the one you've linked is from a D7 perspective. Starting with D8 you should separate building render arrays including embedded data objects and actually rendering the array or executing render methods of such data objects. The grace period where you could mix both should have ended now, but there seems to be an extension. Just checked the D9 release and the backward compatibility code is still present git.drupalcode.org/project/drupal/-/blob/9.0.0/core/lib/Drupal/… – 4k4 Jun 4 at 6:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.