1

I have several custom routes defined on the routes.php and, as usual, I am resolving the value of the template's entry in the Twig template when it is not defined (which is when it doesn't match a Craft Route) but I'd like to do it somewhere else before it reaches the template because I want to remove the logic in Twig.

Can I add a method to be executed before last Craft's renderTemplate($template, $variables) in which I would resolve and add $entry to $variables when it's not defined? What's the way to go?

Thank you :)

4
  • Would it be an option for you to create a custom Controller in the first place? These things are usually done via Controller route instead of template route Jun 6 '18 at 17:35
  • @RobinSchambach Yes, I am open to using a Controller, but I don't know how would I make Craft to redirect my custom routes requests to the controller... Jun 6 '18 at 17:37
  • @RobinSchambach Oh, I think you mean I won't be defining the routes on routes.php... I'm going to read about Controller routing. Thanks Jun 6 '18 at 17:39
  • You can define the route in your routes.php as well, it's actually the same, it doesn't matter where you define the route, as long as you define it. Jun 6 '18 at 18:15
5

Alright, if you didn't already create a custom plugin I suggest you to visit Pluginfactory.io to create the boilerplate. Let's assume your plugin handle is awesome-plugin, just for testing purposes.

You create a controller with the name RouteController that contains the function actionMyAwesomeRoute

In summary:

handle: awesome-plugin
controller: RouteController
function: actionMyAwesomeRoute

All routes in Craft are defined via plugin-handle/controller-name/function-name without the "required" parts - and in snake-case

That means your route will be awesome-plugin/route/my-awesome-route (remove "Controller" and "action")

That being said: you can register your routes in your Plugins main init function

Defining the routes

public function init(){

    Event::on(
        UrlManager::class,
        UrlManager::EVENT_REGISTER_SITE_URL_RULES,
        function(RegisterUrlRulesEvent $event) {
            // easy route for demonstration
            $event->rules[] = [
                'pattern' => '/some-nice-url',
                'route'   => 'awesome-plugin/route/my-awesome-route'
            ];
            // when you visit www.example.com/some-nice-url your function will be executed

            // more complex example if you are not interested
            // skip that and don't think about it
            $event->rules[] = [
                'pattern' => 'my-fancy-route/<action:>/<id:\d+>/<siteHandle:\w+>', 
                'route' => 'awesome-plugin/route/<action>', 
                'defaults' => [
                    'id' => null, 
                    'siteHandle' => null, 
                    'action' => 'index'
                ]
            ];
            // you can visit this route via www.example.com/my-fancy-route
            // this way, your actionIndex will be called
            // when you do www.example.com/my-fancy-route/foo
            // Craft tries to call the actionFoo in your Controller
            // www.example.com/my-fancy-route/bar -> actionBar

            // when you do www.example.com/my-fancy-route/edit/5
            // your actionEdit will be called and Craft passes 5 as a parameter

            // www.example.com/my-fancy-route/save-entry/9/siteOne
            // will call actionSaveEntry(9, 'siteOne');

        }
    }
}

Your Controller

public function actionMyAwesomeRoute(){
    $variables = // define whatever you want

    return $this->renderTemplate('path/to/template', $variables);
}

More complex examples, skip that if you don't need it

// www.example.com/my-fancy-route/show-entry/9/siteOne
public function actionShowEntry($id = null, $siteHandle = null){
    // $id will be 9, $siteHandle 'siteOne' since those parameters
    // are passed via URL, otherwise they are both null
    $entry = Craft::$app->getElements()->getElementById($id, $siteHandle);

    return $this->renderTemplate('path/to/template',  ['entry' => $entry]);
}

Just a little bit more complex stuff

You could actually use the default route and inject it with another parameter as well. So When you have an existing route to an entry like news/2018/feb/awesome-news/ you can as well visit this entry with news/2018/feb/awesome-news/fooBar

$event->rules[] = [
    'pattern' => '/<route:\S+>/<something:\S+>',
    'route'   => 'awesome-plugin/route/catch-all'
];

And in your Controller

public function actionCatchAll($route, $something){
    $entry = Craft::$app->getElements()->getElementByUri($route);
    if ($entry instanceof Entry) {
        $section = $event->getSection();
        $siteSettings = $section->getSiteSettings();
        foreach ($siteSettings as $site) {
            if ((int) $site->siteId === (int) $entry->siteId) {
                $templatePath = $site->template; // the template path for the entry
                return $this->renderTemplate(
                    $templatePath,
                        [
                            'entry'     => $entry,
                            'whatever'  => $something
                        ]
                    );
            }
        }
    }

    return Craft::$app->runAction(Craft::$app->getRequest()->getFullPath());
}
4
  • Great, I got it, the explanation is perfect for my level. Yes I had created a custom plugin but I had only created template variables and services and thought of Controllers somehow else. Will come back when I test this with any question. Thank you again. Jun 6 '18 at 18:06
  • 2
    It became a wall of text, just pick the information you need and ignore the rest or keep it in mind if you ever need it^^ I thought it could be a compilation of useful information Jun 6 '18 at 18:08
  • Here I define a custom route and add my template variable for those routes, but the variable is informed only for the routes that match my custom route. I would like that variable to be informed also for all the site requests that are handled by Craft. I have tried using the same approximation (adding a rule that matches all routes) but Craft catches the entry requests before my Controller rules are reached. Is it possible to add extra variables to requests handled by Craft or override Craft's? Thank you Jun 10 '18 at 10:56
  • 1
    You need to add a global somewhere. If it's really for all requests, no matter what you can do it in your plugins init function Craft::$app->getView()->getTwig()->addGlobal('foo', $bar). Then you can access {{ foo }} Jun 10 '18 at 11:00

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.