Welcome Guest, Not a member yet? Register   Sign In
Use of Closures in Views
#1

This is really more of an observation than a question, but I guess I was wondering if anyone else has found a use for this (or even discovered it in the first place), as it is apparently a side-effect of the way the loader passes data to views (plus the addition of closures in PHP 5.3+). Essentially, if I assign a closure to a variable which is then passed to my view, I can use the closure in the view, allowing me to use what might otherwise be more complicated functionality in my view or prevent the need to go through my data multiple times to prepare it for display.

Because I primarily use Bonfire's Template library rather than loading my views directly, the example below may have an issue or two (other than the simplification for the sake of an example), but the basic idea should work.

In the controller, I setup my data, then define a closure to handle some more complicated logic which I want available in my view, but don't necessarily want to reside directly in my view, then pass the data (with the closure) and name of the view to the loader:

PHP Code:
// $list = $query->result() returned from model
$data = ['list' => $list'listCount' => count($list)];

// Define a closure to build an img element based on the record's image_name field.
$data['imageHandler'] = function ($imageName$altText) {
    $imageDir '/path/to/public/image/dir/';
    if (empty($imageName)) {
        return "<img src='{$imageDir}placeholder.jpg' alt='image not available for {$altText}' />";
    }
    return "<img src='{$imageDir}{$imageName}' alt='{$altText}' />";
};

$this->load->view('list'$data); 


Then the relevant portion of the view looks something like this:

PHP Code:
<div class='examples'>
    <?php foreach ($list as $record) : ?>
    <div class='record'>
        <?php 
        
// Use the closure to generate the img element, echo the result.
        echo $imageHandler($record->image_name$record->alt_text); 
        echo $record->content
        ?>
    </div>
    <?php endforeach; ?>
</div> 

In theory I could go a step further and define the closure itself in another class or some other location to prevent placing code in the controller which generates HTML for the view, but this is, more or less, something I stumbled across while trying to cleanup one of the more complicated modules I developed fairly early on in my time with Bonfire/CodeIgniter/PHP.
Reply
#2

Interesting concept, but in my opinion this is the same as a helper function you would call from the view. Except that with a helper function you can reuse it in other views and it's easier to search where it's called in your code. But I guess there are situations where it's better to use a closure like you did.
CodeIgniter 4 tutorials (EN/FR) - https://includebeer.com
/*** NO support in private message - Use the forum! ***/
Reply
#3

Generally speaking, I avoid creating new helper functions because they're defined globally. If I have enough use for a particular function or set of functions in multiple views, I might create a small library for them and load the library as needed. In most cases, though, I'm thinking of using this for situations in which I have to do something a little more complicated than what I would normally want done inside my view, but the code may be very specific to the view in question (of course, in the long run, I might find that I'm defining very similar functions in multiple places and decide to refactor to a more generalized function I could use in all of them).

In theory, I could even use a closure to call a helper function to prevent creating a dependency on the helper function in my view. I think my own simplification in the example is probably making a portion of the purpose of using the closures less clear. Since I'm using Bonfire, my imageHandler closure is actually calling Bonfire's Assets library to get the base path for the image(s) and to generate the img elements, so I'm isolating the use of the library from the view as well as handling the insertion of a placeholder image when an image isn't provided. So, the imageHandler is something closer to the following:

PHP Code:
public function imageHandler($imageName$displayName)
{
    if (empty($imageName)) {

        return Assets::Image(
            img_path() . 'path/placeholder.jpg',
            ['alt' => "Placeholder, image not available for {$displayName}"]
        );
    }
    return Assets::Image(
        img_path() . "path/{$imageName}",
        ['alt' => $displayName]
    );

This is still a bit of a simplification, but I don't want to diverge too much from the previous example. I'm hoping it's sufficient to just say that I'm trying to keep my view from containing more logic than necessary and from directly calling whatever functions I use in that logic, regardless of whether those functions are defined in a helper or a library. After all, I could just as easily generate similar output by loading the html_helper and calling img(img_path() . "path/{$imageName}", false, ['alt' => $displayName]);
Reply
#4

I think that is a really neat. I must admit that I didn't know you could do that - assigning a function to a variable like that. It is actually looks quite amazing and I will be trying it out. A great way to clean up views, which although I usually can keep non-basic logic out, there are always the odd view that I think, eurrgh, could have done that a bit better.

Just reading about closures and anonymous funtions on php.net, have just found out about variable functions too although don't know when I would need them they look really exciting too: http://php.net/manual/en/functions.varia...ctions.php

I wish I had done computing at Uni, might not be such a dunce now. So much to learn, so little spare time :-(

Paul.
Reply
#5

Don't see anything wrong with that personally. Then again, if I only needed a helper function on a single view, I've been known to create the function at the top of the view file and completely break the separation that should be there. Smile

My tact lately, is a heavier use of objects. In a project I'm working on currently, I have a Album class that does nothing except represent a single album of music. It does, however, have a few helper methods like  image() and imageURL().  

When the model returns the $list (from your example) it returns an array of Track instances. These are passed directly to the view. Then, in the view, I just have to call the imageURL() method. It's not quite the same thing you've done here, but pretty close in concept, since it will provided a default image if none exists.
Reply
#6

(This post was last modified: 09-25-2015, 07:03 AM by jLinux.)

Actually, I did know about this, I use it when I want to pass variables to views but the values haven't yet been defined, or can get changed once the page is already loaded (EG: ajax related)

PHP Code:
<?php
public function set_common_view_vars($var)
{
 
   $partition self::$CI->Partition_model->get_current_partition();

 
   $vars = array(
 
       'page_title'            => App_model::setting('application_name') . (
 
           is_null(self::$CI->template_lib->get_page_title())
 
               ' | ' self::$CI->template_lib->get_page_title()
 
               ''),
 
       'username'              => self::$CI->Accounts_model->username,
 
       'full_name'             => (count($name) > ucwords(implode(' '$name)) : "Authenticated User"),
 
       'page_header'           => $var,
 
       'can_change_password'   => self::$CI->account_lib->can_change_password(),
 
       'application_name'      => App_model::setting('application_name'),
 
       'current_partition_id'  => ($partition $partition->partition_id NULL),
 
       'current_partition'     => ($partition $partition NULL),
 
       // Anonymous Function
 
       'partition_list'        => function() {
 
           return self::$CI->Partition_model->get_partitions();
 
       },
 
       // Anonymous Function
 
       'notifications'         => function() {
 
           return App_lib::generate_notifications();
 
       },
 
       // Anonymous Function
 
       'sidebar'               => function() {
 
           return self::$CI->template_lib->generate_sidebar();
 
       },
 
       // Anonymous Function
 
       'breadcrumbs'           => function() {
 
           return self::$CI->template_lib->generate_breadcrumb();
 
       },
 
       // Closure
 
       'partition_pages'       => function() use($partition) {
 
           if( ! $partition )
 
               return FALSE;

 
           $pages self::$CI->Page_model->get_pages_in_groups($partition->partition_id);

 
           //die('Pages 1: <pre>'. print_r($pages, TRUE));

 
           if(isset($pages['_']))
 
           {
 
               $top $pages['_'];
 
               unset($pages['_']);
 
               //sort($pages);
 
               $pages array_merge(['_' => $top],$pages);
 
           }

 
           return $pages;
 
       },
 
       // Anonymous Function
 
       'bookmarks'             => function() {
 
           return self::$CI->Accounts_model->get_my_bookmarks();
 
       }
 
   );

 
   self::$CI->load->vars($vars);

Reply
#7

While a neat concept, views should not contain logic or be calling functions.
Reply
#8

(09-24-2015, 06:57 AM)spjonez Wrote: While a neat concept, views should not contain logic or be calling functions.

Not sure I can agree with that. If you take your security seriously, then views frequently call functions like htmlspecialchars, or other escaping functions. In addition, other format-related functions are called all of the time, like date(), etc.

In my view, the controller collects the data and passes it to the view. It's up to the view how to format and display it. And that frequently requires format-related functions.
Reply
#9

(09-24-2015, 06:57 AM)spjonez Wrote: While a neat concept, views should not contain logic or be calling functions.

While this is an ideal which one should look to attain in their views, at some point someone has to look around and realize that some views must do one or the other, or you have to back-door the situation by using a template language which allows you to specify logic (and sometimes function calls) without technically including it directly in the view.

In fact, this ultimate goal is one of the reasons I cited for using this functionality in the first place, as I was faced with one of three other methods of handling this particular task:
  • Looping through my data in the controller to modify it for display before passing it to the view, where the view would loop through the data again to generate the HTML in which it is displayed.
  • Putting all of the logic contained in these closures into the view itself to avoid the second loop.
  • Putting the logic into helper functions or library methods which would be called from the view.
How exactly does one accomplish even a simple list without logic or function calls in their view? At some point you're going to need at least a loop.
Reply
#10

(This post was last modified: 09-24-2015, 04:01 PM by spjonez.)

(09-24-2015, 07:20 AM)kilishan Wrote: In my view, the controller collects the data and passes it to the view. It's up to the view how to format and display it. And that frequently requires format-related functions.

Separating logic from views is not always convenient but it does provide value long term. Complex SQL queries are faster as a single recordset which often requires PHP loops to process a flat array and turn it into a nested array. You can do formatting at the same time so you aren't adding a lot of overhead by pre-processing. IMO views are presentation only and should compile from raw JSON. Using views in this nature allows you to swap to virtually any templating language with minimal effort. You have better separation of logic in your application (traditional MVC). You can create two way data binds by passing JSON back and forth instead of compiled HTML. You can do client side templating to reduce bandwidth. Some of the newer frameworks are moving in this direction with web components.

(09-24-2015, 07:29 AM)mwhitney Wrote: While this is an ideal which one should look to attain in their views, at some point someone has to look around and realize that some views must do one or the other, or you have to back-door the situation by using a template language which allows you to specify logic (and sometimes function calls) without technically including it directly in the view.

It requires more work and planning up front but I feel the benefits are worth it.

(09-24-2015, 07:29 AM)mwhitney Wrote: How exactly does one accomplish even a simple list without logic or function calls in their view? At some point you're going to need at least a loop.

You will yes. Do you use an ORM? I don't so more often than not I'm looping my query results anyway.
Reply




Theme © iAndrew 2016 - Forum software by © MyBB