So, I was working on a module in which I was trying to render a link inside a translatable string, while doing so lead me into some pretty interesting issues. To solve those issues I went on a searching rampage, and in all those searching I found some very interesting information, all of which I am going to share in this article. This article will not only be helpful to those who may face a similar issue in the future but it will also help increase the reader's knowledge on how links are handled in drupal 8. So, what are we waiting for, let's roll.Let me begin with what I was trying to achieve. My module have a configuration form, I was trying to define some markup that appears before the form itself, as we know this can be easily achieved through the #prefix property.
$form['#prefix'] = t('This is example linktext');
Inside this markup I wanted to render an internal link. Now, that is where the issues started emerging. At first I tried the following code:
$form['#prefix'] = t('This is example @linktext', array('@linktext' => Link::fromTextAndUrl(t('Admin System'), '/admin/example')));
Now, what am I doing here, let me explain. There are two ways we can generate links in drupal 8, one way is to use the LinkGenerator service, another way is to use the the Link class. I went with the latter in my above code. I know there will be a couple of questions going around in your mind such as what's the difference between the two and when should you use which? But before I answer these questions for you, let's first understand how urls are handled in drupal 8.The Drupal\Core\Url class represents the urls in drupal 8, it comes with all sorts of different methods using which you can turn almost anything into a Url. fromRoute() and fromUri() are its most important methods, the former takes a route name, route parameters (if needed for that route), and an array of options while the latter takes in an internal or external URL to create a new instance of Url. You should use the fromUri() method if you are dealing with dynamically obtained data, however if you have to hardcode, in that case fromRoute() is the best option, since it takes a route name meaning you can later change the actual path behind that route without affecting your code.So, now that we know how we can create Url objects, let me answer the above two questions. I think the best way to answer these questions would be through examples.Let's first generate a link using the LinkGenerator service, to do so we will have to first create a url instance using one of the methods of the Url class, afterwards we will pass this url object to the generate method of the LinkGenerator service along with the text string for our link. Following is the code for all the theory that I just explained:
$exampleurl = Url::fromRoute('example_route', ['example_param_name' => $example_param_value]);
$examplelink = \Drupal::service('link_generator')->generate('Example link', $exampleurl);
We can then directly print $examplelink since it implements the __toString() method. Now let's generate a link using the Link class, following is the code for it:
$exampleurl = Url::fromRoute('example.route');
$examplelink = Link::fromTextAndUrl('My link', $exampleurl);
As easily evident, we first create the url object and then pass it along with the text string to the fromTextAndUrl method of the Link class. We now have $examplelink as a Link object whose toRenderable() returns a render array of the #type => 'link'. On the backend, at render time, it will also use the LinkGenerator service to transform that render array into a link string.I guess the difference between the two methods must be pretty clear for you by now, in case its not then let me further explain it for you. Unlike, the Link class fromTextAndUrl method which generates a render array, the LinkGenerator service directly generates a string meaning it cannot be altered later in the process. So, if the link that you are creating is going to be altered by other modules/themes, then in that case you should definitely go with the Link class else go with the LinkGenerator service.With all this explanation out of our way, let's get back to my code. So as you know I was trying to render an internal link inside a translatable string using the following code:
$form['#prefix'] = t('This is example @linktext', array('@linktext' => Link::fromTextAndUrl(t('Admin System'), 'admin/example')));
But, when I would run the code, it would give me an error. The reason why it was giving me an error was because I had passed a string as the second argument to Drupal\Core\Link::fromTextAndUrl(), instead of passing an instance of Drupal\Core\Url. So, to resolve the error I did some changes to my code and after the changes the code now looked like this:
$form['#prefix'] = t('This is example @linktext', array('@linktext' => Link::fromTextAndUrl(t('Admin System'), Url::fromRoute('example.route'))));
As easily evident from the code I am passing a url instance to the fromTextAndUrl method using the fromRoute method of the Url class. But, it was still giving me an error, can you tell why? As I said above, unlike the LinkGenerator service, the Link class fromTextAndUrl method returns a render array. So, instead of using the Link class, I used the LinkGenerator service and Bam! my issue was solved.
$form['#prefix'] = t('This is example @linktext', array('@linktext' => \Drupal::service('link_generator')->generate('My link', Url::fromRoute('example.route'))));
Just to keep the code short here, I have used the static method of service instantiation, you should only opt for this option if you cannot inject the service properly.With, that said, I will wrap up the article, thank you for reading.
Jibran Ullah
Sr. Drupal site Builder/ Drupal Themer/Drupal Module Developer