Presenters and linking without Request

5 years ago

Member | 100

Code is meta. This is blunt as I can be, if you still don't understand what am I proposing ask me directly. Will try my best to explain it.


Common Problem: Generate emails from latte templates is still difficult and messy (using lot of anti-patterns).

Meta Problem: Generating link in CLI requires mocking http request and presenter Request. Using UI macro {link} in CLI requires extra work. By CLI I mean anything but environment that has data for HttpRequest available.

There are several points that are in dire need of refactoring and in sum would allow for clean and practical solution to both problems.


  • Generating application URLs without presenter in a clean way. CLI, tests, links without request.
  • Simplification of {link} macro logic.
  • @mkoubik Integrations of Latte into other frameworks wouldn't have to reimplement the {link} macro.
  • Link caching


Hereby I propose refactoring all related functionality by inverting dependencies into multiple classes.

  1. Splitting Presenter::createRequest to RequestFactory
  2. Extracting LinkGenerator from Presenter::link
  3. Refactoring PresenterComponent::link to use LinkGenerator.
  4. Refactoring {link} macro
  5. There should be enough code for own package nette/application-link with {link} macro. optional
Splitting Presenter::createRequest to RequestFactory

Similar to Presenter::createTemplate but much harder.
Should use context object to carry dependencies on Presenter or component. It might help to offload Presenter and component logic to such contexts.
Context can be in later stage created from ILinkContext.

Extracting LinkGenerator from PresenterComponent::link
class LinkGenerator {
    function __constructor(IRouter, ILinkSetup);
    function link(...[, ILinkContext $ctx = NULL]) {
        if ($ctx === NULL) $ctx = new AbsoluteLinkContext;
class HttpRequestLinkSetup implements ILinkSetup {
    function __constructor(HttpRequest);
class ConstantLinkSetup ... {
    static function fromUrl($url);
    function __construct(/* pass [<protocol>://]<host>[:<port>][/<path>] */);
// ... Presenter, Control and AbsoluteLinkContext

LinkContext would be responsible for determining absolute/relative url. It could have space for additional logic that does not fit LinkGenerator.
LinkGenerator will be available as service in DI.

Refactoring PresenterComponent::link
class PresenterComponent {
    function link(...) {
        $this->linkGenerator->link(..., new PresenterContext($this));
Refactoring {link} macro

{link} macro would require template to have $_linkGenerator and $_linkContext to work. With optional $_presenterLinkContext for {plink} functionality. Contexts can be generated in BC manner

# {link}
isset($_linkContext) ? $_linkContext : new ControlLinkContext($_control)
# {plink}
isset($_presenterLinkContext) ? $_presenterLinkContext: new PresenterLinkContext(isset($_presenter) ? $_presenter : $_control->getPresenter()))
ILinkContext purpose – sub-topic to discuss – light vs. heavy

Complexity of ILinkContext will depend on result of Presenter::createRequest and Presenter::link refactoring. Their main purpose is to pass necessary information for building links to LinkGenerator.

They could just pass $relativePathEnabled, $presenter or $control. Or carry logic that utilizes these parameters so LinkGenerator does not have to know about them.

I came up with them as a solution for passing information that would unify use of link without explicit need of $_presenter or $_component in template. In result {plink} and {link} would be reduced only to different contexts. AbsoluteLinkContext would be complementary as solution (not only) for email templates which would no longer need $_presenter in template with {link} macros.

Work done by others (might not be related)

EDIT: Added explanation of ILinkContext Thanks Filip!

EDIT2: Expanded a bit about Presenter::createRequest.

Last edited by mishak (2014-06-16 03:01)

5 years ago

David Grudl
Nette Core | 6790

Generating link in CLI requires mocking http request and presenter.

Are you sure?…/IRouter.php#L36

5 years ago

Member | 100

Yes. IRouter is not the URL factory Presenter::createRequest is. $refUrl is filled by presenter. You can look at it as a hidden factory and dependency. Hidden factory would be the $refUrl setup. Hidden dependency is the HttpRequest used for creating $refUrl and relative urls.

By extracting it into interface first step of refactoring is done. IRefUrlFactory with implementation for HttpRequest and for plain URL (non-browser, could be used as fallback).
But you will still have one HttpRequest dependency. That would be solved by ILinkSetup.

So ILinkSetup will have to provide at least BaseUrl (string) and RefUrl (Url object).

Last edited by mishak (2014-06-16 03:45)