Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "template helper"

Extra Number Helpers

Here are some extra helpers that I don't think are in symfony anywhere. They would fit rather nicely in the 'Number' helper group.

Example

<!-- someTemplateSuccess.php -->
<?php use_helper('NumberPlus') ?>
<table>
  <thead>
    <tr>
      <th>Name</th>
      <th>Phone</th>
      <th>Credit Card</th>
      <th>Exp.</th>
      <th>Income</th>
      <th>SSN</th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($users as $user): ?>
      <tr>
        <td><?php echo $user->getName() ?></td>
        <td><?php echo format_phone($user->getPhone()) ?></td>
        <td><?php echo mask_credit_card($user->getCc()) ?></td>
        <td><?php echo format_exp($user->getExp()) ?></td>
        <td><?php echo format_usd($user->getIncome()) ?></td>
        <td><?php echo format_ssn($user->getSsn()) ?></td>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>

The Code

<?php
 
/**
 * Formats a number by injecting nonnumeric characters in a specified format
 * into the string in the positions they appear in the format.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo format_string('1234567890', '(000) 000-0000');
 *  // => (123) 456-7890
 *
 *  echo format_string('1234567890', '000.000.0000');
 *  // => 123.456.7890
 * </code>
 *
 * @param string the string to format
 * @param string the format to apply
 * @return the formatted string
 */
function format_string($string, $format)
{
   if ($format == '' || $string == '') return $string;
 
   $result = '';
   $fpos = 0;
   $spos = 0;
   while ((strlen($format) - 1) >= $fpos)
   {
       if (is_alphanumeric(substr($format, $fpos, 1)))
       {
           $result .= substr($string, $spos, 1);
           $spos++;
       }
       else
       {
           $result .= substr($format, $fpos, 1);
       }
 
       $fpos++;
   }
 
   return $result;
}
 
/**
 * Transforms a number by masking characters in a specified mask format,
 * and ignoring characters that should be injected into the string without
 * matching a character from the original string (defaults to space).
 *
 * <strong>Example:</strong>
 * <code>
 *  echo mask_string('1234567812345678', '************0000');
 *  // => ************5678
 *
 *  echo mask_string('1234567812345678', '**** **** **** 0000');
 *  // => **** **** **** 5678
 *
 *  echo mask_string('1234567812345678', '**** - **** - **** - 0000', ' -');
 *  // => **** - **** - **** - 5678
 * </code>
 *
 * @param string the string to transform
 * @param string the mask format
 * @param string a string (defaults to a single space) containing characters to ignore in the format
 * @return string the masked string
 */
function mask_string($string, $format, $ignore = ' ')
{
    if ($format == '' || $string == '') return $string;
 
    $result = '';
    $fpos = 0;
    $spos = 0;
    while ((strlen($format) - 1) >= $fpos)
    {
        if (is_alphanumeric(substr($format, $fpos, 1)))
        {
            $result .= substr($string, $spos, 1);
            $spos++;
        }
        else
        {
            $result .= substr($format, $fpos, 1);
 
            if (strpos($ignore, substr($format, $fpos, 1)) === false) $spos++;
        }
 
        $fpos++;
    }
 
    return $result;
}
 
/**
 * Formats a phone number.
 *
 * @param string the unformatted phone number to format
 * @param string the format to use, defaults to '(000) 000-0000'
 * @return string the formatted string
 *
 * @see format_string
 */
function format_phone($string, $format = '(000) 000-0000')
{
    return format_string($string, $format);
}
 
/**
 * Formats a variable length phone number, using a standard format.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo smart_format_phone('1234567');
 *  // => 123-4567
 *
 *  echo smart_format_phone('1234567890');
 *  // => (123) 456-7890
 *
 *  echo smart_format_phone('91234567890');
 *  // => 9 (123) 456-7890
 *
 *  echo smart_format_phone('123456');
 *  // => 123456
 * </code>
 *
 * @param string the unformatted phone number to format
 *
 * @see format_string
 */
function smart_format_phone($string)
{
    switch (strlen($string))
    {
        case 7:
            return format_string($string, '000-0000');
        case 10:
            return format_string($string, '(000) 000-0000');
        case 11:
            return format_string($string, '0 (000) 000-0000');
        default:
            return $string;
    }
}
 
/**
 * Formats a U.S. Social Security Number.
 *
 * <strong>Example:</strong>
 * <code>
 *  echo format_ssn('123456789');
 *  // => 123-45-6789
 * </code>
 *
 * @param string the unformatted ssn to format
 * @param string the format to use, defaults to '000-00-0000'
 *
 * @see format_string
 */
function format_ssn($string, $format = '000-00-0000')
{
    return format_string($string, $format);
}
 
/**
 * Formats a credit card expiration string. Expects 4-digit string (MMYY).
 *
 * @param string the unformatted expiration string to format
 * @param string the format to use, defaults to '00-00'
 *
 * @see format_string
 */
function format_exp($string, $format = '00-00')
{
    return format_string($string, $format);
}
 
/**
 * Formats (masks) a credit card.
 *
 * @param string the unformatted credit card number to format
 * @param string the format to use, defaults to '**** **** **** 0000'
 *
 * @see mask_string
 */
function mask_credit_card($string, $format = '**** **** **** 0000')
{
    return mask_string($string, $format);
}
 
/**
 * Formats a USD currency value with two decimal places and a dollar sign.
 *
 * @param string the unformatted amount to format
 * @param string the format to use, defaults to '$%0.2f'
 *
 * @see sprintf
 */
function format_usd($money, $dollar = true, $format = '%0.2f')
{
    return ($dollar ? '$' : '') . sprintf($format, $money);
}
 
/**
 * Determines if a string has only alpha/numeric characters.
 *
 * @param string the string to check as alpha/numeric
 *
 * @see is_numeric
 * @see preg_match
 */
function is_alphanumeric($string)
{
    return preg_match('/[0-9a-zA-Z]/', $string);
}

NOTE: Place this in something like myproj/apps/myapp/lib/helper/NumberPlusHelper.php

by Stephen Riesenberg on 2007-03-16, tagged helper  number  template 
(5 comments)

Complimentary Filters

A common modification to a CRUD module is filtering records on the list page. There are probably several nice ways to do this, including drop-downs, forms, etc. depending on the situation. While some of these approaches are nice, they are often not minimal... We like minimal!

Discussion

Say you have a book table, with a related author and genre. You want to filter the list of books by those related tables with links, paginate your list, and make it sortable via links in the header of the table.

To do this traditionally, you'll need lots of nasty little if statements in your template code, to check for all of these parameters (author_id, genre_id, title, num_pages, page, etc.) and combine them all together into a meaningful uri for your link_to function (something like "book/list?author_id=10&genre_id=5&sort=title&page=5"). We still want that link in the end, but generating it... How about a better way!

Followup Example

<!-- listSuccess.php -->
<?php use_helpers('Filter', 'Pagination') ?>
<h2>Books</h2>
<h3>Filter By</h3>
<table class="filters">
  <tbody>
    <tr>
      <th>Author:</th>
      <td><?php echo filter_navigation(objects_for_filter($authors), 'author_id', $author_id) ?></td>
    </tr>
    <tr>
      <th>Genre:</th>
      <td><?php echo filter_navigation(objects_for_filter($genres), 'genre_id', $genre_id) ?></td>
    </tr>
  </tbody>
</table>
<hr />
<table class="list">
  <thead>
    <tr>
      <th><?php link_to_unless($sort == 'id', 'Id', filter_url('sort', 'id')) ?></th>
      <th><?php link_to_unless($sort == 'title', 'Title', filter_url('sort', 'title')) ?></th>
      <th><?php link_to_unless($sort == 'author_id', 'Author', filter_url('sort', 'author_id')) ?></th>
      <th><?php link_to_unless($sort == 'genre_id', 'Genre', filter_url('sort', 'genre_id')) ?></th>
    </tr>
  </thead>
  <tbody>
    <?php foreach ($pager->getResults() as $book): ?>
      <tr>
        <td><?php echo $book->getId() ?></td>
        <td><?php echo $book->getTitle() ?></td>
        <td><?php echo $book->getAuthor()->getName() ?></td>
        <td><?php echo $book->getGenre()->getName() ?></td>
      </tr>
    <?php endforeach; ?>
  </tbody>
</table>
<?php echo pager_navigation($pager) ?>

NOTE: pager_navigation is covered at http://www.symfony-project.com/snippets/snippet/4, but needs to guess the uri, which is done at http://www.symfony-project.com/snippets/snippet/59.

NOTE: filter_navigation returns an unordered list, so you'll need css to display the <li> tags inline, and remove <ul> padding, margin, etc.

I'll leave it as an exercise to the reader to create the controller (action) code for this template, but it should be obvious.

The Code

<?php
 
/**
 * Generate a url using the current internal uri, but replaces a param with a new value.
 * If the param is not in the current uri's query string, it is added instead.
 *
 * This is useful for a page that uses several filters to record sets,
 * and needs all the filters to work together, instead of blasting each
 * other away when a new link is clicked.
 *
 * <strong>Examples:</strong>
 * <code>
 *  // with current uri => mymodule/myaction?author=10&genre=3
 *
 *  echo link_to('new author', filter_url('author', 5));
 *  // uri when clicked => mymodule/myaction?author=5&genre=3
 *
 *  echo link_to('new genre', filter_url('genre', 1));
 *  // uri when clicked => mymodule/myaction?author=10&genre=1
 *
 *  // with current uri => mymodule/myaction
 *
 *  echo link_to('an author', filter_url('author', 10));
 *  // uri when clicked => mymodule/myaction?author=10
 * </code>
 *
 * @param string the name of the parameter to replace
 * @param string the value to replace the current value with
 * @param boolean use route name
 * @return string the url with the parameter replaced
 * @see link_to
 */
function filter_url($param, $new_value, $with_route_name = false)
{
    // fetch params from query string
    $params = _get_params(_get_query_string());
 
    // replace param with new value
    $params[$param] = $new_value;
 
    return _get_uri($with_route_name) . '?' . _build_query_string($params);
}
 
/**
 * Removes a parameter from the current uri and returns the resulting url.
 *
 * @see filter_url
 */
function remove_filter_url($param, $with_route_name = false)
{
    // fetch params from query string
    $params = _get_params(_get_query_string());
 
    // remove param
    unset($params[$param]);
 
    return _get_uri($with_route_name) . '?' . _build_query_string($params);
}
 
/**
 * Generates an unordered list of links to filter the current record set by.
 * Multiple sets of filter_navigation links will work together, using the current uri.
 *
 * <strong>Examples:</strong>
 * <code>
 *  echo filter_navigation(array(10=>'Jones', 12=>'Smith, J.', 13=>'Darby'), 'author_id', 13);
 *  echo filter_navigation(objects_for_filter($authors), 'author_id', 13);
 * </code>
 *
 * @param array list of key=>value pairs of ids and strings
 * @param string the name of the parameter for this filter
 * @param string the selected id (or null, if none selected)
 * @param string the text to use for the "all" link
 * @see filter_url
 */
function filter_navigation($list, $param, $selected = null, $all_text = 'All')
{
    $html = '';
 
    $html .= content_tag('li', link_to_unless($selected === null, $all_text, remove_filter_url($param)));
    foreach ($list as $key => $value)
    {
        $html .= content_tag('li', link_to_unless($selected == $key, $value, filter_url($param, $key)));
    }
 
    return content_tag('ul', $html);
}
 
/**
 * Generates a simple list from a record set of propel objects.
 * Expects a getId function and a toString function.
 *
 * @param array objects to be converted to a list
 * @see filter_navigation
 */
function objects_for_filter($objects)
{
    $list = array();
 
    foreach ($objects as $object)
    {
        $list[$object->getId()] = $object->toString();
    }
 
    return $list;
}
 
function _get_uri($with_route_name = false)
{
    $internal_uri = sfRouting::getInstance()->getCurrentInternalUri($with_route_name);
    $ar = explode('?', $internal_uri);
 
    return ($with_route_name ? '@' : '') . $ar[0];
}
 
function _get_query_string()
{
    $internal_uri = sfRouting::getInstance()->getCurrentInternalUri();
    $ar = explode('?', $internal_uri);
 
    return isset($ar[1]) ? $ar[1] : '';
}
 
function _get_params($query_string)
{
    // parse query string into associative array
    $params = array();
    if ($query_string != '')
    {
        foreach (explode('&', $query_string) as $kvpair)
        {
            list($key, $value) = explode('=', $kvpair);
            $params[$key] = $value;
        }
    }
 
    return $params;
}
 
function _build_query_string($params)
{
    // build list of key=value strings
    $ar = array();
    foreach ($params as $key => $value)
    {
        $ar[] = $key . '=' . $value;
    }
 
    return implode('&', $ar);
}

NOTE: Place this in apps/myapp/lib/helper/FilterHelper.php.

Additional Usage Examples

<?php echo link_to('new author', filter_url('author_id', 10)) ?>
<?php echo url_for(filter_url('author_id', 10)) ?>
<?php echo link_to_if($condition, 'new author',  filter_url('author_id', 10)) ?>
<?php echo link_to_unless($condition, 'new author',  filter_url('author_id', 10)) ?>
<?php echo button_to('new author',  filter_url('author_id', 10)) ?>

Additional Helpers

<?php
 
/**
 * Shortcut combining link_to and filter_url into single function.
 *
 * @see link_to
 * @see filter_url
 */
function link_to_filter($name, $param, $new_value, $options = array())
{
    return link_to($name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining url_for and filter_url into single function.
 *
 * @see url_for
 * @see filter_url
 */
function filter_url_for($param, $new_value)
{
    return url_for(filter_url($param, $new_value));
}
 
/**
 * Shortcut combining link_to_if and filter_url into single function.
 *
 * @see link_to_if
 * @see filter_url
 */
function link_to_filter_if($condition, $name, $param, $new_value, $options = array())
{
    return link_to_if($condition, $name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining link_to_unless and filter_url into single function.
 *
 * @see link_to_unless
 * @see filter_url
 */
function link_to_filter_unless($condition, $name, $param, $new_value, $options = array())
{
    return link_to_unless($condition, $name, filter_url($param, $new_value), $options);
}
 
/**
 * Shortcut combining button_to and filter_url into single function.
 *
 * @see button_to
 * @see filter_url
 */
function button_to_filter($name, $param, $new_value, $options = array())
{
    return button_to($name, filter_url($param, $new_value), $options);
}
by Stephen Riesenberg on 2007-02-03, tagged css  filter  helper  pager  pagination  template  view 

Additional slot helpers

I recently had several template fragments which each had some conditional css that needed to be defined in the template itself. This is a perfect application for slots.

However, since there were several of these fragments/partials being executed, I would have to use several slots. That seemed unnecessary. Consider the following:

Example 1

/* _fragment.php */
<?php append_to_slot('style') ?>
<style>
.class {
  font-size: 12px;
}
</style>
<? end_slot() ?>

Example 2

/* _anotherfragment.php */
<?php append_to_slot('style') ?>
<style>
  .anotherclass {
    <?php if ($bool): ?>
      font-size: 24px;
    <?php else: ?>
      font-size: 26px;
    <?php endif; ?>
  }
</style>
<?php end_slot() ?>

Those were just an example, so don't laugh. They do illustrate, however, the use of a helper which continually appends your content to the existing slot, which may or may not already contain content; in this case the "style" slot.

Then of course, you can use the slot in your layout.php (in the head tag):

...
<head>
...
<?php if (has_slot('style')) echo get_slot('style') ?>
</head>

Note: while the append_to_slot function can be ended by either end_slot() or end_append_to_slot(), the prepend_to_slot function must be ended by end_prepend_to_slot()!

The code

<?php
 
/**
 * Begins the capturing of a slot,
 * but echoes the current value of a slot with the same name,
 * if one exists, before capturing the rest of the slot content.
 *
 * <strong>Example</strong>
 * <code>
 *  // first myslot
 *  slot('myslot');
 *  echo 'something to put in "myslot"';
 *  end_slot();
 *  // second myslot
 *  append_to_slot('myslot');
 *  echo 'some stuff to append to existing "myslot" slot';
 *  end_slot();
 * </code>
 *
 * @param   string slot name
 * @return  void
 * @see     end_append_to_slot, slot, end_slot
 */
function append_to_slot($name)
{
    // check for the existence of a slot with the same name
    $content = '';
    if (has_slot($name)) $content = get_slot($name);
 
    // regardless, begin capturing the slot
    slot($name);
 
    // echo either a blank string, or the previous value of the slot with the same name
    echo $content;
}
 
/**
 * Stops the content capture and save the content in the slot.
 * This function is interchangeable with end_slot, and is not necessary.
 * It is useful only for consistency's sake.
 *
 * @return  void
 * @see     append_to_slot, slot, end_slot
 */
function end_append_to_slot()
{
    // simply end the current slot
    end_slot();
}
 
/**
 * Begins the capturing of a slot,
 * but saves the current value of a slot with the same name,
 * if one exists, and then proceeds to capture the slot content.
 *
 * @param   string slot name
 * @return  void
 * @see     end_prepend_to_slot, slot, end_slot
 */
function prepend_to_slot($name)
{
    $context = sfContext::getInstance();
    $response = $context->getResponse();
 
    // check for the existence of a slot with the same name
    $content = '';
    if (has_slot($name)) $content = get_slot($name);
 
    // save the current slot value in a temporary location
    $response->setParameter('old_slot', $content, 'symfony/view/sfView/slot');
 
    // begin capturing the slot
    slot($name);
}
 
/**
 * Retrieves the old value of a slot which was backed up in prepend_to_slot,
 * if one exists, and echoes this value,
 * and then proceeds to stop the content capture and save the content in the slot.
 *
 * @return  void
 * @see     prepend_to_slot, slot, end_slot
 */
function end_prepend_to_slot()
{
    $context = sfContext::getInstance();
    $response = $context->getResponse();
 
    // retrieve the old value for this slot
    $content = $response->getParameter('old_slot', '', 'symfony/view/sfView/slot');
    // remove the old value
    $response->getParameterHolder()->remove('old_slot', 'symfony/view/sfView/slot');
 
    // echo the old value of the slot, or a blank string if no slot existed
    echo $content;
 
    // end the current slot
    end_slot();
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 

Template fragments, cont.

I'm not sure how useful these will be, but I came up with the idea, so I figured I'd post them as well.

Example

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include_fragment('label', array('id' => $key, 'object' => $value))) ?></th>
      <td><?php include_fragment('input', array('id' => $key, 'object' => $value))) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

These helpers use the fragment idea from http://www.symfony-project.com/snippets/snippet/124 and take it a step further. There's a difference here, however, that's important to note:

Note: Unlike partials, these fragments will have absolutely no variables available, unless explicitly passed in. Templating variables provided by symfony will not be available, due to php's scope handling when inside of functions. That's why my previous snippet used include(fragment_name('myfragment')) instead, which gave the fragment access to all currently in-scope variables.

The code

<?php
 
/**
 * Includes a template fragment.
 * This function is similar in nature to include_partial,
 * but does not use sfView, and therefore has virtually no overhead.
 * 
 * <strong>Example</strong>
 * <code>
 *  include_fragment('mypartial');
 * </code>
 * 
 * @param   string fragment name
 * @return  void
 * @see     include_partial, get_fragment, fragment_path, fragment_name
 */
function include_fragment($templateName, $vars = array())
{
    echo get_fragment($templateName, $vars);
}
 
/**
 * Evaluates and returns a template fragment.
 * The syntax is similar to that of include_fragment.
 * 
 * <strong>Example</strong>
 * <code>
 *  echo get_fragment('mypartial');
 * </code>
 * 
 * @param   string fragment name
 * @return  string result of a fragment include
 * @see     include_partial, include_fragment, fragment_path, fragment_name
 */
function get_fragment($templateName, $vars = array())
{
    // start the output buffer
    ob_start();
    ob_implicit_flush(0);
    // extract the variables in the scope of this function
    extract($vars);
    // include the file
    include(fragment_path($templateName));
    // clear the output buffer, and return the content
    return ob_get_clean();
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 

Template fragments - when even partials are too much

For the record, partials are wonderful. Self-sustained pieces of gui code, with access to all the symfony templating tools you need.

Sometimes however, even partials tend to be overkill. And sometimes, too, you have an annoying little piece of gui code that ought to go into your main template (indexSuccess.php or what have you), but you can't bear to put it there, because it would disturb the flow of an otherwise beautifully crafted template.

The truth is, every reuseable piece of gui code that isn't a full blown template really fits the partial paradigm, but sometimes it's just too much overhead, too much trouble, or just not necessary. Consider the following:

Example 1

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include(fragment_name('label')) ?></th>
      <td><?php include(fragment_name('input')) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

Example 2

<table>
  <?php foreach ($vars as $key => $value): ?>
    <tr>
      <th><?php include(fragment_path('somemodule/label')) ?></th>
      <td><?php include(fragment_path('anothermodule/input')) ?></td>
    </tr>
  <?php endforeach; ?>
</table>

The _label.php file could contain a few if statements... enough to generate the label for our input. It would look quite a bit uglier to place the code right in the th tag, but that's what we're really going for. _label.php would immediately have access to $key and $value, and any other variables already in scope, including typical templating variables provided by symfony. Additionally, there's no overhead to deal with, which helps if that foreach loop will execute 200 times (well, maybe that's a stretch). Finally, it just looks symfony-esque (at least to me)!

The code

<?php
 
/**
 * Get the path to a fragment.
 * The syntax is similar to that of include_partial.
 * To be used in conjunction with php's include function.
 * Variables in scope in the caller will be available in the execution of the fragment.
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_path('mypartial'))
 * </code>
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_path('mymodule/mypartial'))
 * </code>
 * 
 * @param   string fragment name
 * @return  string path to the fragment
 * @see     get_fragment, include_fragment, include_partial
 */
function fragment_path($templateName)
{
    // partial is in another module?
    if (false !== ($sep = strpos($templateName, '/')))
    {
        $moduleName   = substr($templateName, 0, $sep);
        $templateName = substr($templateName, $sep + 1);
    }
    else
    {
        $moduleName = sfContext::getInstance()->getActionStack()->getLastEntry()->getModuleName();
    }
    $fragmentName = fragment_name($templateName);
    // test for global fragment
    if ($moduleName === 'global')
    {
        $path = sfConfig::get('sf_app_template_dir');
    }
    // if not global, or the global fragment doesn't exist
    if ($moduleName !== 'global' || !is_readable($path.'/'.$fragmentName))
    {
        // get the full path to the file
        $path = sfConfig::get('sf_app_module_dir').'/'.$moduleName.'/'.sfConfig::get('sf_app_module_template_dir_name');
    }
    // return the full path to the partial
    return $path.'/'.$fragmentName;
}
 
/**
 * Translates a nice fragment name into the filename adhering to the naming convention for partials.
 * 
 * <strong>Example</strong>
 * <code>
 *  include(fragment_name('mypartial'))
 * </code>
 * 
 * @param   string fragment name
 * @return  string fragment filename
 * @see     fragment_path
 */
function fragment_name($templateName)
{
    return '_'.$templateName.'.php';
}

Remember: you can place this in myproject/apps/myapp/lib/helper/PartialPlusHelper.php

Then you can use it with <?php use_helper('PartialPlus') ?>

by Stephen Riesenberg on 2006-12-16, tagged helper  template  view 
(1 comment)