Code snippets for symfony 1.x

Navigation

Snippets by user Stephen Riesenberg

Augmenting Existing Helper Groups

In previous snippets, I gave examples of new helpers. Some of these helpers could fit into already existing helper groups in the symfony core. I then suggested adding a helper group, like "NumberPlus" to augment the "Number" helper group.

There is a nicer way to do this. To augment the Number helper group without needing to redefine existing functions, add a file in <proj>/apps/<app>/lib/helper/NumberHelper.php and place the following in it:

The Code

<?php
 
include_once('symfony/helper/NumberHelper.php');
 
// custom Number helpers go here
function my_number_helper()
{
  // ...
}
 
// ...

Since symfony's bootstrapping process adds the appropriate paths to the include path of php, this works nicely. Of course, this assumes that your installation of symfony isn't customized too much, so you may need to tweak the path a little bit.

Now, when you need access to predefined symfony Number helpers, or your custom helpers, just use:

Example

<!-- someTemplateSuccess.php -->
<?php use_helper('Number') ?>
 
<?php echo format_number(5) ?>
 
<?php echo my_number_helper() ?>

NOTE: I haven't experimented with this outside of my setup, but since my installation of symfony isn't too unusual, I'm assuming it works nicely. If it doesn't work for you, please post the details so I can modify this snippet if necessary.

by Stephen Riesenberg on 2007-04-18, tagged 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 

Populate Object Filter

If you're like me, you hate having to populate your objects (from the model) with parameters from the request. Wouldn't it be nice to specify a little nugget of configuration, and then never worry about it again? Consider the following, which is tedious and boring:

Old Example

class mymoduleActions extends sfActions
{
  ...
 
  public function executeUpdate()
  {
    $employee = new Employee();
    $employee->setFirstName($this->getRequestParameter('first_name'));
    $employee->setLastName($this->getRequestParameter('last_name'));
    $employee->setSsn($this->getRequestParameter('ssn'));
    $employee->setDob($this->getRequestParameter('dob'));
    ...
    $this->employee = $employee;
  }
}

And the list goes on. Imagine, however, that you have the following in your module filters.yml file:

myPopulateObjectFilter:
  class: myPopulateObjectFilter
  param:
    use_database:  on
    model_object:  employee
    model_class:   Employee
    exclude:       [ssn]
    defaults:
      first_name:  example default name

If you had a filter that automatically created an object, and based upon the previous yaml, populated that object with data from the request (after a user hit submit on a form containing this information), then subsequently set that object for retrieval as a request attribute, you'd be a pretty happy person right?

New Example

class mymoduleActions extends sfActions
{
  ...
 
  public function executeUpdate()
  {
    $this->employee = $this->getRequest()->getAttribute('employee');
    ...
  }
}

Of course, you'd be free to do whatever you want with the results of the prepopulated object.

A great application would be an alternative to the fillin form filter. If you have your template set up to populate inputs from an object from the model, this filter can populate that object for you, and you can simply pass it on to the template.

The Code

<?php
 
/**
 * myPopulateObjectFilter
 * Uses configuration in filters.yml to create an object,
 * populate it with data from the request,
 * and set a request attribute with the result.
 *
 * @author  Stephen Riesenberg
 */
class myPopulateObjectFilter extends sfFilter
{
    protected
        $defaults       = null,
        $controller     = null,
        $request        = null;
 
    public function initialize($context, $parameters = array())
    {
        parent::initialize($context, $parameters);
 
        // get defaults from filters.yml (if any) and create new parameter holder to store them
        $this->defaults = new sfParameterHolder();
        $this->defaults->add($this->getParameter('defaults', array()));
 
        // get controller and request
        $this->controller   = $this->getContext()->getController();
        $this->request      = $this->getContext()->getRequest();
    }
 
    public function execute($filterChain)
    {
        // get request variable list
        $vars = $this->request->getParameterHolder()->getNames();
        $exclude = array_merge($this->getParameter('exclude', array()), array('module', 'action'));
        $vars = array_diff($vars, $exclude);
 
        $funcs = array();
        foreach ($vars as $var)
        {
            $funcs[$var] = 'set'.ucfirst(sfInflector::camelize($var));
        }
 
        // get model_object and model_class
        $object = $this->getParameter('model_object');
        $class = $this->getParameter('model_class');
 
        // fetch from the database or create the object to fill in
        if ($this->getParameter('use_database', false) && null !== ($id = $this->request->getParameter('id')))
        {
            $peer_class = $class . "Peer";
            $record = $peer_class::retrieveByPk($id);
        }
        else
        {
            $record = new $class();
        }
 
        // something like: array('id' => 'setId', 'my_field' => 'setMyField', ...)
        foreach ($funcs as $var => $func)
        {
            if (is_callable(array($record, $func)))
            {
                $record->$func($this->getValue($var));
            }
        }
 
        // set the updated record into a request attribute
        $this->request->setAttribute($object, $record);
 
        // execute next filter
        $filterChain->execute();
    }
 
    protected function getDefault($var)
    {
        return $this->defaults->get($var);
    }
 
    protected function getValue($var)
    {
        return $this->request->getParameter($var) != '' ? $this->request->getParameter($var) : $this->getDefault($var);
    }
}

NOTE: Place this in a file called myPopulateObjectFilter.class.php in the myproject/apps/myapp/lib/ directory.

by Stephen Riesenberg on 2007-01-06, tagged filter  model  object 
(2 comments)

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)

More on multiple ajax div updates

Quentin Garnier, in this snippet: http://www.symfony-project.com/snippets/snippet/57, suggested a way to have a link call multiple remote functions (with "in order" calls being enforced by a new 'wait' parameter). With a slightly modified version of his code, here are a few more functions to be added to you Javascript.php function in the symfony core:

function periodically_call_remotes($options = array())
{
  $frequency = isset($options[0]['frequency']) ? $options[0]['frequency'] : 10; // every ten seconds by default
  $code = 'new PeriodicalExecuter(function() {'.remote_functions($options).'}, '.$frequency.')';
 
  return javascript_tag($code);
}
 
function link_to_remotes($name, $options = array(), $html_options = array())
{
  return link_to_function($name, remote_functions($options), $html_options);
}
 
function remote_functions($options)
{
  // Multi ajax call
  $multi_function = "";
  // First pass (wait)
  for ($i = 0; isset($options[$i]); $i++) {
    if (isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']])) {
      if (isset($options[$options[$i]['wait']]['complete'])) {
          $options[$options[$i]['wait']]['complete'] .= '; ' . remote_function($options[$i]);
      } else {
          $options[$options[$i]['wait']]['complete'] = remote_function($options[$i]);
      }
    }
  }
 
  // Second pass
  for ($i = 0; isset($options[$i]); $i++) {
    if (!(isset($options[$i]['wait']) && $options[$i]['wait'] != $i && isset($options[$options[$i]['wait']]))) {
      $multi_function .=  remote_function($options[$i]) . ';';
    }
  }
  return $multi_function;
}

To use it, you could do something like this:

<?php echo link_to_remotes('multi ajax',
    array(
        array(
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

or this:

<?php echo periodically_call_remotes('multi ajax',
    array(
        array(
            'frequency' => 60 /* every 60 seconds, frequency only needs to be specified in first call, not subsequent calls */
            'update' => 'first_zone',
            'url' => '/module/action1',
            'loading' => "Element.show('indicator')"
        ),
        array(
            'update' => 'second_zone',
            'url' => '/module/action2',
            'wait' => 0 /* update in a second server call, after 'news_zone' returns */
        ),
        array(
            'update' => 'third_zone',
            'url' => '/module/action3',
            'wait' => 0
        ),
        array(
            'update' => 'fourth_zone',
            'url' => '/module/action4',
            'complete' => "Element.hide('indicator')",
            'wait' => 2 /* update in a third server call, after 'third_zone' returns */
        )
    )
) ?>

Notes

It should be noted that using the 'wait' parameter will cause all top level ajax remote function calls to be executed in one call to the server, and each second level to be executed in a second call, etc. Basically, multiple calls to the server are generated by the user action.

This will cause huge performance issues if you attempt to use these functions in a high-traffic scalable-sensitive system. It is mainly intended for a link which executes two or three functions at most. To do more, you should consider using another approach, like the JSON approach documented in the wiki.

by Stephen Riesenberg on 2006-11-20, tagged ajax  helper  multiple 
(2 comments)

Flash and Request parameters outside of an action

I don't know about you, but I often farm out action logic--like building a datastructure to pass to a template--to static functions in a class in my application or module lib/ directory.

However, these functions don't typically have access to the shortcut methods for setting/getting flash and request parameters (etc.). Like:

$foo = $this->getRequestParameter('foo');

Unless I've missed something, the code to do something similar outside of an action is just a little messy, and quite repetitive if used more than once. So I add a few things to my project to make the job simpler.

/**
 * This class adds a few more useful functions to the sfUser instance
 */
class myBasicUser extends sfBasicSecurityUser
{
    const FLASH_NAMESPACE = 'symfony/flash';
 
    /** User flash parameters (stored in the session until the next request) */
    public function setFlash($name, $value)
    {
        $this->setAttribute($name, $value, self::FLASH_NAMESPACE);
    }
 
    public function getFlash($name, $default = null)
    {
        return $this->getAttribute($name, $default, self::FLASH_NAMESPACE);
    }
 
    public function hasFlash($name)
    {
        return $this->hasAttribute($name, self::FLASH_NAMESPACE);
    }
 
    /** User attribute parameters (stored in the session until removed) */
    public function removeAttribute($name, $ns = null)
    {
        $this->getAttributeHolder()->remove($name, $ns);
    }
 
    public function getAttributes($ns = null)
    {
        return $this->getAttributeHolder()->getAll($ns);
    }
 
    public function removeAttributes($ns = null)
    {
        $this->getAttributeHolder()->removeNamespace($ns);
    }
 
    /** User parameter parameters (erased after every request) */
    public function removeParameter($name, $ns = null)
    {
        $this->getParameterHolder()->remove($name, $ns);
    }
 
    public function getParameters($ns = null)
    {
        return $this->getParameterHolder()->getAll($ns);
    }
 
    public function removeParameters($ns = null)
    {
        $this->getParameterHolder()->removeNamespace($ns);
    }
}
 
class myUser extends myBasicUser
{
//...
}

*Note: add those files to your lib/ director as myBasicUser.class.php and myUser.class.php respectively.

That gives me an easy way to do some things with the user object, but now I need an easy way to get the user outside of an action and an easier way to get/set request parameters/attributes, etc. I defined a class in my application lib/ directory like this:

/**
 * Shortcuts to all request and user attribute setting/getting/has functions
 * This class simply makes repetitive tasks a few characters (and in some cases, lines) shorter to accomplish
 */
class myTools
{
    /** Get context singleton */
    public static function getContext()
    {
        return sfContext::getInstance();
    }
 
    /** Get request singleton */
    public static function getRequest()
    {
        return sfContext::getInstance()->getRequest();
    }
 
    /** Request parameter holder */
    public static function getRequestParameterHolder()
    {
        return sfContext::getInstance()->getRequest()->getParameterHolder();
    }
 
    /** Request attribute holder */
    public static function getRequestAttributeHolder()
    {
        return sfContext::getInstance()->getRequest()->getAttributeHolder();
    }
 
    /** Request parameters (read-only) */
    public static function getRequestParameter($name, $default = null)
    {
        return sfContext::getInstance()->getRequest()->getParameter($name, $default);
    }
 
    public static function hasRequestParameter($name)
    {
        return sfContext::getInstance()->getRequest()->hasParameter($name);
    }
 
    /** Request attributes (read/write - erased after every request) */
    public static function getRequestAttribute($name, $default = null)
    {
        return sfContext::getInstance()->getRequest()->getAttribute($name, $default);
    }
 
    public static function setRequestAttribute($name, $value)
    {
        sfContext::getInstance()->getRequest()->setAttribute($name, $value);
    }
 
    public static function hasRequestAttribute($name)
    {
        return sfContext::getInstance()->getRequest()->hasAttribute($name);
    }
 
    /** Get user singleton */
    public static function getUser()
    {
        return sfContext::getInstance()->getUser();
    }
 
    /** User attribute holder */
    public static function getAttributeHolder()
    {
        return sfContext::getInstance()->getUser()->getAttributeHolder();
    }
 
    /** User parameter holder */
    public static function getParameterHolder()
    {
        return sfContext::getInstance()->getUser()->getParameterHolder();
    }
 
    /** User flash parameters (stored in the session until the next request) */
    public static function setFlash($name, $value)
    {
        sfContext::getInstance()->getUser()->setFlash($name, $value);
    }
 
    public static function getFlash($name)
    {
        return sfContext::getInstance()->getUser()->getFlash($name);
    }
 
    public static function hasFlash($name)
    {
        return sfContext::getInstance()->getUser()->hasFlash($name);
    }
 
    /** User attribute parameters (stored in the session until removed) */
    public static function getAttribute($name, $default = null, $ns = null)
    {
        return sfContext::getInstance()->getUser()->getAttribute($name, $default, $ns);
    }
 
    public static function setAttribute($name, $value, $ns = null)
    {
        sfContext::getInstance()->getUser()->setAttribute($name, $value, $ns);
    }
 
    public static function removeAttribute($name, $ns = null)
    {
        sfContext::getInstance()->getUser()->removeAttribute($name, $ns);
    }
 
    public static function getAttributes($ns = null)
    {
        return sfContext::getInstance()->getUser()->getAttributes($ns);
    }
 
    public static function removeAttributes($ns = null)
    {
        sfContext::getInstance()->getUser()->removeAttributes($ns);
    }
 
    /** User parameter parameters (erased after every request) */
    public static function getParameter($name, $default = null, $ns = null)
    {
        return sfContext::getInstance()->getUser()->getParameter($name, $default, $ns);
    }
 
    public static function setParameter($name, $value, $ns = null)
    {
        sfContext::getInstance()->getUser()->setParameter($name, $value, $ns);
    }
 
    public static function removeParameter($name, $ns = null)
    {
        sfContext::getInstance()->getUser()->removeParameter($name, $ns);
    }
 
    public static function getParameters($ns = null)
    {
        return sfContext::getInstance()->getUser()->getParameters($ns);
    }
 
    public static function removeParameters($ns = null)
    {
        sfContext::getInstance()->getUser()->removeParameters($ns);
    }
 
    /** User credentials */
    public static function clearCredentials()
    {
        sfContext::getInstance()->getUser()->clearCredentials();
    }
 
    public static function listCredentials()
    {
        return sfContext::getInstance()->getUser()->listCredentials();
    }
 
    public static function removeCredential($credential)
    {
        sfContext::getInstance()->getUser()->removeCredential($credential);
    }
 
    public static function addCredential($credential)
    {
        sfContext::getInstance()->getUser()->addCredential($credential);
    }
 
    public static function addCredentials()
    {
        sfContext::getInstance()->getUser()->addCredentials(func_get_args());
    }
 
    public static function hasCredential($credential)
    {
        return sfContext::getInstance()->getUser()->hasCredential($credential);
    }
 
    /** User authentication */
    public static function isAuthenticated()
    {
        return sfContext::getInstance()->getUser()->isAuthenticated();
    }
 
    public static function setAuthenticated($authenticated)
    {
        sfContext::getInstance()->getUser()->setAuthenticated($authenticated);
    }
 
    /** User culture */
    public static function setCulture($culture)
    {
        sfContext::getInstance()->getUser()->setCulture($culture);
    }
 
    public static function getCulture()
    {
        return sfContext::getInstance()->getUser()->getCulture();
    }
}

There you have it. Now it's as simple as...

class myModuleLib
{
  public static function myFunc()
  {
    ...
    $foo = myTools::getRequestParameter('foo', 'default_value');
    myTools::setFlash($foo);
    ...
  }
}

Overkill...? Maybe. You may use any of these functions that are useful, and simply omit those you think are unnecessary. The goal here is to standardize (even more) the methods used to access attributes of the request and user, etc. I'm guessing this will be beneficial for beginners (if nothing else, as an example), and also for people (like me) who like ridiculously consistent code.

Any comments, including comments on naming conventions of the functions are appreciated.

by Stephen Riesenberg on 2006-11-19, tagged action  attributes  module  parameters  request  session  user 
(3 comments)