Why the need for a snippet controller


#1

Kirby has controllers but not snippet controllers (which Bricks has). Anyway, I will not talk about Bricks in this post but instead the need for snippet controllers in general.

Why a snippet controller

Normal controllers are used in templates. A template can contain like 10 snippets or more. To have the logic for all the snippets inside a big controller can be a bit messy after a while. Sure, you could lift things out and put them into a plugin instead, or you can bundle them to your snippets instead of your templates.

I now have a real live example for you. Don’t mind what this code does, it’s a breadcrumb snippet.

Snippet - Without snippet controller

It might look ok, but it’s still mixing logic and presentation quite a bit. If this snippet would be larger it would probably be even more messy and you need to do something about it.

<div class="breadcrumbs">
  <a href="<?php echo url('bricks-ui'); ?>">Bricks</a>
  <?php if( ! empty( $brick ) ) : ?>
    <div class="breadcrumb-arrow"></div>
    <a href="<?php echo url('bricks-ui/' . $brick); ?>"><?php echo $brick; ?></a>

    <?php if( ! empty( $filename ) ) : ?>
      <div class="breadcrumb-arrow"></div>
        <a href="<?php echo url('bricks-ui/' . $brick . '?filename=' . $filename); ?>">
          <?php echo $filename; ?>
        </a>
    <?php endif; ?>
  <?php endif; ?>
</div>

Snippet controller

Now you may say that it’s even more code in just the controller than it’s in the snippet that contains logic. While that is true, it separates logic from presentation. I don’t need to open and close the php-tags all the time because I only work with the logic in here.

It’s probably possible to make this code much more optimized

<?php
return function($site, $pages, $page, $args) {
  $data = array();
  $steps = array('home', 'brick', 'filename');

  foreach( $steps as $step ) {
    $data[$step]['title'] = $args[$step];
    switch($step) {
      case 'home':
        $data[$step]['url'] = url('bricks-ui');
        break;
      case 'brick':
        $data[$step]['url'] = url('bricks-ui') . '/' . $args['brick'];
        break;
      case 'filename':
        $data[$step]['url'] = url('bricks-ui') . '/' . $args['brick'] . '?filename=' . $args['filename'];
        break;
    }
    $data[$step] = (object)$data[$step];
  }

  return array('data' => $data);
};

Snippet - With using the snippet controller

It’s about half the size and I keep things more DRY. Now this is very small example so the gain might not be very big but the more stuff you need the bigger the gain will be to separate logic from presentation.

<div class="breadcrumbs">
  <?php foreach($data as $key => $item) : ?>

    <?php if( ! empty( $item->title ) && $key != 'home' ) : ?>
      <div class="breadcrumb-arrow"></div>
    <?php endif; ?>
    
    <a href="<?php echo $item->url; ?>"><?php echo $item->title; ?></a>
  <?php endforeach; ?>
</div>

If you want to know more about how to use this approach read more about how it works in Bricks snippet controllers.

Update

I’ve updated both the snippet and controller because the code could be improved. Thanks @texnixe for the push.


#2

While I see where a snippet controller would be useful, e.g. for a form that you want to put on every page, I would never use the above controller code in a controller for a snippet or template.


#3

@texnixe why not? I’d really like to hear your reasoning. While the example is certainly not a particularly good use case (short examples for a forum post rarely are), the goal of keeping the snippets free of non-display logic, is something I strongly agree with. Your example with a form that should show up on multiple pages is something I’ve had to deal with before and having a snippet controller would have been helpful.

On the other hand - let me join the shameless self-promotion :wink: - with plugins like Bricks and Constructs readily available, maybe it is better to keep snippets as plain and simple as they are and use a higher order concept for higher order problems.


#4

I agree it might not be the best example and that’s why I added:

It’s probably possible to make this code much more optimized

@texnixe Would you mix logic and presentation in this case, or just make some heavily improvements on it? I don’t want to go back to mix logic and presentation again, but I will probably try to improve it much more.

Re-use of snippets

Another gain I’ve discovered is that it’s possible to re-use the same snippets for different data output. Then it only depends on the controller how the data looks like. I add a real case example of it, hopefully it’s looking decent:

<div>
  <h2>
    <?php echo $title; ?>
    <span class="count">[<?php echo $count; ?>]</span>
  </h2>
  <div class="list <?php echo $type; ?>">
    <ul>
      <?php foreach( $data as $item ) : ?>
        <li<?php echo $item['class']; ?>>
          <a href="<?php echo $item['url']; ?>"><?php echo $item['name']; ?></a>
        </li>
      <?php endforeach; ?>
    </ul>
  </div>
</div>

Every output above comes from a controller so it’s quite generic.

@lord-executor Thanks! :slight_smile:


#5

In the example above, the controller removes some display logic just to introduce some different logic. Ok, you remove the repeated div and a elements. But in fact, you can’t get rid of foreach loops and if statements in your templates and snippets. If I wanted to get rid of doubling the div and a, I’d probably use another snippet instead of using all this logic in the controller that does not really make the code more easily understandable. But that’s just my take on it.

As I already said above, I like the idea of snippet controllers for e.g. cases like forms or other examples where it simply would not make sense to repeat the logic in each controller.


#6

Please don’t go back to 2000 year…

The controller contain the business logic then in the snippets contain business view, don’t mix it is the best way to give the code clean and the design much clean as possible.

Sure sometimes we can do it directly on the snippet (because we use php) but isn’t a good way for it.


#7

I also like the idea of a snippet controller. I am working on a project where I need to check if the image exists, and if so make it into a background image in a news feed gallery. It would be nice to keep the php logic out of the snippet for each image.


#8

Another way of keeping the main logic stuff out of a snippet is the use of functions…


#9

@lukehatfield Nowdays, I usually do it like @texnixe said. I create custom plugins like this:

core-routes
core-tags
core-queries

I prefix them with core- simply to separate my custom plugins from not custom ones. Just like I don’t like to hack Kirby core, I don’t like to hack publicly released plugins, so a separation make sense.

I could bundle all functions in a single plugin, but I like it this way. You could call it custom- as a prefix if that’s better. I hope it helps.


#10

Thank you @texnixe and @jenstornell. Slightly off topic, but I’m interested if you bring those custom plugins over to new projects via the kirby cli @jenstornell. I thought that would be a slick way to get up and running with commonly used custom features quickly for each new project.


#11

My Kirby CLI stopped working when I switched from WAMP to XAMPP (Windows) and I never got it to work. I would probably try to go with Composer if some bundle kind of setup is needed.


#12

I just simplified a code on one of my sites. Here is what came out of it.

In header.php

I like to keep the html elements in the snippets when it’s “clean”. I did not want an if statement around the meta description, so I moved that to the plugin.

<title><?= theme::seoTitle($page); ?></title>
<?= theme::seoDescription($page); ?>

In a custom plugin

Static functions make less “noise” in templates and snippets, because they can be called directly. I could use page methods as well, but maybe they will change in Kirby 3 (who knows), so this may be more future proof.

class theme {
    public static function seoTitle($page) {
        if($page->seo_title()->isNotEmpty()) return $page->seo_title();
        if($page->page_title()->isNotEmpty()) return $page->page_title();
        return $page->title();
    }

    public static function seoDescription($page) {
        if($page->seo_description()->isNotEmpty())
            return sprintf('<meta name="description" content="%s">', $page->seo_description());
    }
}

#13

I love your techniques for getting cleaner code in the snippets @jenstornell . I will adopt this method for returning html.

What if you needed to return a php variable to the snippet instead of html? I guess you could just assign the variable to the return from the function. Would that be your approach?

For example, in a snippet that has html for a news item, I’d like to use an image as a background if it exists, and print this out in the element’s style attribute. I don’t want to use the standard kirby image condition check inside the opening <li> tag because I like seeing my php logic in a multi-line format for readability.

Currently I’ve placed this multi-line php check at the top of the snippet, as its essentially controller logic, and belongs with this snippet instead of the template that calls it. I’d rather do this php logic in a function like you reference, but need a returned variable instead of returned html. Here’s my current snippet, with controller logic on top…

<!-- site/snippets/news-item.php -->
<php>
    // check if background image for this news item exists
    if($image = $newsItem->image()) {
        // set variable with image url
        $backgroundUrl = $image->url();
    } else {
        // flag variable as false so won't set background in style attribute below
        $backgroundUrl = false;
    }
</php>

<!-- if image exists for this news item, set it as the background image using the e() kirby helper (https://getkirby.com/docs/cheatsheet/helpers/e). -->
<li class="news-item" style="<?php e($backgroundUrl, 'background-image: url(' . $backgroundUrl . ')' ?>">
    ...code here...
</li>

Instead of the above, with snippet controller logic at the top of the snippet, would you recommend something like the following using theme functions from a custom plugin?

<!-- site/snippets/news-item.php -->
<?php $backgroundUrl = theme::newsItemBackgroundUrlCheck($newsItem) ?>

<li class="news-item" style="<?php e($backgroundUrl, 'background-image: url(' . $backgroundUrl . ')' ?>">
    ...code here...
</li>

It seems like if we could have a snippet controller, it could make this snippet a lot cleaner, essentially eliminating the entire <?php $backgroundUrl = theme::newsItemBackgroundUrlCheck($newsItem) ?> line.

Thanks for your help!


#14

In this case it’s hard to say. It’s not that much extra logic there, so moving it to a helper function could be to overdo things. I think you need to try different things and see where it make the most sense in your workflow.

It could go in snippet, controller or plugin function. If you do the same thing more than two times, you should probably use a function to not repeat yourself.

You can also try to use shorthand if statements, if you have not already. Below is another example, not based on your exact problem.

I tend to do it like this sometimes:

<?php $active = ($page->isActive()) ? ' class="active"' : ''; ?>

<div<?= $active; ?>>
Hello world!
</div>

It may not be super beautiful, but it’s quick.

In your case with style, I would probably try to make the li-element a bit shorter, but it’s a personal preference I think.

Something like this:

<li class="news-item"<?= $style; ?>>

or possibly this:

<li class="news-item" style="<?= $style; ?>">

If there should always be a style, I would use the latter, else the previous one.