Code snippets for symfony 1.x

Navigation

Popular Tags

action admin ajax batch cache cli criteria css culture custom database date debug doctrine email filter form forms generator helper i18n javascript model navigation object pager pagination pake propel query routing session sql symfony template test user validation validator view widget
This is a public source code repository where you can find, rate and comment code snippets published by others, or store you owns and categorize them with tags for easy retrieval.

Popular snippets

Helper for Javascript Tabbed Panes

I figured I'd post this little helper for using the tabbed pane JS from http://webfx.eae.net/dhtml/tabpane/tabpane.html in your templates.

Simply download the JS package from the site above and place it in your web/js folder. I chose to move the css files from the JS folder to my CSS folder, but you can do whatever you like.

tabsHelper.php

<?php
/**
 * Helper for Javascript Tabbed Panes
 * 
 * Example Usage
 * <code>
 *  <?php use_helper('tabs') ?>
 *  <?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?>
 *            This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 *      <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?>
 *            This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 *  <?php tabInit();?>
 * </code>
 * 
 * @package    Helpers
 * @author     Jason Ibele
 * @version    SVN: $Id: tabsHelper.php 4 2006-07-19 14:00:47Z jason.ibele $
 */
 
$response = sfContext::getInstance()->getResponse();
$response->addJavascript('tab/tabpane.js');
$response->addStylesheet('tab/tab.webfx.css');
 
/**
 * Opens a new TabPane object and creates first tab
 *
 * @param string $mid         JavaScript variable name to use for webFXTabPane object
 * @param string $id          Main container div ID
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $main_class  Optional class name for main Div (note this must match original class definitions)
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabMainJS($mid, $id, $page_id, $H2_title, $main_class='tab-pane', $page_class='tab-page')
{
  echo '<div class="'.$main_class.'" id="'.$id.'">'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo $mid.' = new WebFXTabPane( document.getElementById( "'.$id.'" ) );'."\n\t";
  echo '</script>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Closes and existing pane div and opens a new one with required JS
 *
 * @param string $mid         JavaScript variable to use for webFXTabPane object
 * @param string $page_id     Name for div ID, each needs to be unique
 * @param string $H2_title    The title for the tab
 * @param string $page_class  Optional class name for page Div (note this must match original class definitions)
 */
function tabPageOpenClose($mid, $page_id, $H2_title, $page_class='tab-page')
{
  echo '</div>'."\n\t";
  echo '<div class="'.$page_class.'" id="'.$page_id.'">'."\n\t";
  echo '<h2 class="tab">'.$H2_title.'</h2>'."\n\t";
  echo '<script type="text/javascript">'.$mid.'.addTabPage( document.getElementById( "'.$page_id.'" ) );</script>'."\n";
}
 
/**
 * Initiates the javascript for tabs and closes the remaining divs
 *
 * @param string $mid    JavaScript variable to use for webFXTabPane object
 * @param string $n      selected index of tab you want to force as set
 */
function tabInit($mid='', $n='')
{
  echo "\t".'</div>'."\n\t";
  echo '<script type="text/javascript">'."\n\t";
  echo 'setupAllTabs();'."\n\t";
 
  if($n){ // n = selected index of tab you want to force as set
    echo $mid.'.setSelectedIndex('.$n.');';
  }
 
  echo '</script>'."\n";
  echo '</div>'."\n";
}
 
?>

template

<?php use_helper('tabs') ?>
 
 
 
 
 
<!-- open the first tab -->
<?php tabMainJS("tp1","tabPane1", "tabPage1", 'General');?><!-- General is the name of the tab -->
 
          This is text of tab 1. This is text of tab 1. This is text of tab 1. 
 
 
<!-- second tab -->     
    <?php tabPageOpenClose("tp1", "tabPage2", 'Security');?> <!-- Security is the name of the tab -->
 
          This is text of tab 2. This is text of tab 2. This is text of tab 2. 
 
 
<!-- third tab -->      
    <?php tabPageOpenClose("tp1", "tabPage3", 'Example');?> <!-- Example is the name of the tab -->
 
          This is text of tab 3. This is text of tab 3. This is text of tab 3.
 
 
<!-- close tabs and initiate the JS -->
<?php tabInit();?>
by Jason Ibele on 2006-07-31, tagged helper  javascript  tabs 
(1 comment)

update query using Propel

When you need to update several records in a row, you don't have to loop over the result of a doSelect() call and do a save() for each object (which would make way too many queries).

Instead, you can use the BasePeer method doUpdate() as follows:

BasePeer::doUpdate($select_criteria, $update_criteria, $connection);

For instance:

$con = Propel::getConnection();
 
// select from...
$c1 = new Criteria();
$c1->add(CommentPeer::POST_ID, $post_id);
 
// update set
$c2 = new Criteria();
$c2->add(CommentPeer::RATING, 5);
 
BasePeer::doUpdate($c1, $c2, $con);

If course, if you are in a Peer class, you will need to use the self object:

$con = Propel::getConnection();
 
// select from...
$c1 = new Criteria();
$c1->add(self::POST_ID, $post_id);
 
// update set
$c2 = new Criteria();
$c2->add(self::RATING, 5);
 
BasePeer::doUpdate($c1, $c2, $con);
by Francois Zaninotto on 2006-07-03, tagged criteria  propel 
(8 comments)

Filtering with rows from another table

By default, the admin generator allows to filter the data with the rows from the table currently listed.

Here's how to extend this to data from other linked tables.

We'll consider the following example : a table "command" linked to a table "user". This very simple schema.xml shows the relation between these two tables :

<table name="buyer" phpName="BtqBuyer" >
  <column name="buyer_id" type="BIGINT" required="true" primaryKey="true"/>
  <column name="buyer_name" type="VARCHAR" size="255" required="true"/>
</table>
 
<table name="command" phpName="BtqCommand" >
  <column name="com_id" type="BIGINT" required="true" primaryKey="true"/>
  <column name="com_ref" type="VARCHAR" size="6" required="true"/>
  <column name="com_buyer_id" type="BIGINT" required="true"/>
  <foreign-key foreignTable="buyer" onDelete="" onUpdate="">
    <reference local="com_buyer_id" foreign="buyer_id"/>
  </foreign-key>
</table>

In the file generator.yml, add a partial in the filters parameter to print our specific filter :

filters: [com_ref, _btq_buyer]

The source code for the partial _btq_buyer.php (located in the templates directory) is :

<?php echo input_tag('filters[buyer]', isset($filters['buyer']) ? $filters['buyer'] : '') ?>

Now we have to add our specific filter in the filter process. To do this, we extend the addFiltersCriteria from the admin generator. This is done in the file actions.class.php by adding :

protected function addFiltersCriteria (&$c)
{
  if (isset($this->filters['buyer']) && $this->filters['buyer'] != '')
  {
    $c->add(BtqBuyerPeer::BUYER_NAME, strtr($this->filters['buyer'], '*', '%'), Criteria::LIKE);
    $c->addJoin(BtqBuyerPeer::BUYER_ID, BtqCommandPeer::COM_BUYER_ID);
  }
}

As you can see, we've even allowed the use of wildcard in our filter. Nice ;)

by marc Hugon on 2006-06-21, tagged admin  filter  generator 
(5 comments)

Symfony BBCode Parser

BBCode Parser class written specifically for use in symfony, but designed with the possibility of extension in mind. This parser does not simply use regular expressions to replace the bbcodes--it parses the entire string using a recursive parse method, and ensures that all tags that require closing tags are closed in the proper order, thereby ensuring XHTML-compliant output.

Note: The current version is now contained in a plugin at http://trac.symfony-project.com/trac/wiki/sfBBCodeParserPlugin. Anyone who wishes to use this should download the version there. It is fixed and has an easier to use tag definition system.

sfBBCodeParser.class.php:

This is the main parser class. Place it in a lib directory appropriate to your use of the parser.

<?php
 
/**
 * BBCode Parser class written specifically for use in SymfonyBB, but designed
 * with the possibility of extension in mind.  This parser does not simply use
 * regular expressions to replace the bbcodes--it parses the entire string using
 * a recursive parse method, and ensures that all tags that require closing tags
 * are closed in the proper order, thereby ensuring XHTML-compliant output.  
 * 
 * @author Colin Williams <sentineldiety@gmail.com>
 * @package SymfonyBB/BBCode
 * @copyright Copyright © 2006 Colin Williams
 * 
 * 
 */
class SBBCodeParser
{
 
  /**
   * An array containing the loaded tags.
   * @access private
   */
  private $_tags;
 
  /**
   * An array containing the options.
   * @access private
   */
  private $_options;
 
  /**
   * Holds the static instance of this class.
   * @access private
   */
  private static $instance = null;
 
  /**
   * Constructor.
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   */
    public function __construct()
  {
    $this->_loadOptions();
    //echo '<pre>'; var_dump($this->_options); echo '</pre>';
    $this->_loadCodes();
    //echo '<pre>'; var_dump($this->_tags); echo '</pre>';
  }
 
  /**
   * Parses the provided text.
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return string The parsed text.
   */
  public function parse($str)
  {
    $firstopen = $this->_getFirstOpenTagLoc($str);
    if($firstopen === false)
      return $str;
    $tag = $this->_getTagAt($str, $firstopen);
    if($tag === false)
    {
      return $str;
    }
    //Now we actually parse the tag
    $pattern = $this->_tags[$tag['tag']]['pattern'];
    $parsed = str_replace('${attr}', $tag['attr'], $pattern);
    if(!$this->_tags[$tag['tag']]['selfclose'])
      $parsed = str_replace('${content}', $this->parse($tag['content']), $parsed);
    $parsed .= $this->parse($tag['textafter']);
    return $parsed;
  }
 
  /**
   * Loads the defined codes into the private _tags class var.
   * By default, this uses a database to store the tags.  However,
   * this can be easily changed by simply modifying/overloading this
   * method.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * 
   */
  private function _loadCodes()
  {
    $tags = BBCodePeer::doSelect(new Criteria());
    foreach($tags as $tag)
    {
      $this->_tags[$tag->getTag()] = array(
        'name'      => $tag->getName(),
        'pattern'   => $tag->getPattern(),
        'help'      => $tag->getHelp(),
        'selfclose' => $tag->getSelfclose()
      );
    }
  }
 
  /**
   * Loads options into the private _options class var.  By default, this uses
   * the symfony app config file to store the options, but this can be easily
   * changed by modifying/overloading this method.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   */
  private function _loadOptions()
  {
    $this->_options['openchar'] = sbbTools::addDefault(sfConfig::get('app_bbcode_openchar'), '[');
    $this->_options['closechar'] = sbbTools::addDefault(sfConfig::get('app_bbcode_closechar'), ']');
  }
 
  /**
   * Returns the location of the first opening bbcode tag in the string.  If
   * there is no valid tag, returns false.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return int|false
   */
  private function _getFirstOpenTagLoc($str)
  {
    $openchar = $this->_options['openchar'];
    $closechar = $this->_options['closechar'];
    $startpos = 0;
    while(true) {
      $startpos = strpos($str, $openchar, $startpos);
      if($startpos === false) return false;
      $endpos = strpos($str, $closechar, $startpos);
      $tag = substr($str, $startpos + 1, ($endpos - $startpos - 1));
      // If it's a closing tag, ignore it'
      if(substr($str, $startpos + 1, 1) == '/') 
      {
        $startpos += 1;
        continue;
      }
      // Strip out the attribute, if applicable
      if(!(strpos($tag, '=') === false)) 
      {
        $arr = explode('=', $tag);
        $tag = $arr[0];
      }
      // Find out if the possible tag is a valid tag
      if(in_array($tag, array_keys($this->_tags)))
      {
        return $startpos;
      }
      else
        $startpos += 1;
    }
  }
 
  /**
   * Returns an array containing the tag information for the tag which starts at
   * the position given.
   * 
   * @access private
   * @author Colin Williams <sentineldiety@gmail.com>
   * @returns array
   */
  private function _getTagAt($str, $startpos)
  {
    // Very simple check to make sure that it's a somewhat valid tag
    if(substr($str, $startpos, 1) != $this->_options['openchar'])
      return false;
    $tag = array();
    $endpos = strpos($str, $this->_options['closechar'], $startpos);
    $tag['raw'] = substr($str, $startpos + 1, ($endpos - $startpos - 1));
    //Check for an attribute, and add to tag array if present
    if(strpos($tag['raw'], '=') === false)
    {
      $tag['tag'] = substr($str, $startpos + 1, ($endpos - $startpos - 1));
      $tag['attr'] = '';
    }
    else
    {
        $arr = explode('=', $tag['raw']);
      $tag['tag'] = $arr[0];
      $tag['attr'] = $arr[1];
    }
    //Ensure that the tag is a valid tag
    if(!in_array($tag['tag'], array_keys($this->_tags)))
      return false;
    // Tag has no content
    if($this->_tags[$tag['tag']]['selfclose'])
    {
        $tag['selfclose'] = true;
      $tag['textafter'] = substr($str, $endpos +1);
      return $tag;
    }
    $closepos = strripos($str, $this->_options['openchar'].'/'.$tag['tag'].$this->_options['closechar']);
    if($closepos === false)
    {
      $str .= $this->_options['openchar'].$tag['tag'].$this->_options['closechar'];
    }
    $tag['content'] = substr($str, $endpos + 1, $closepos - $endpos - 1);
    $tag['textafter'] = substr($str, $closepos + strlen($this->_options['openchar'].$tag['tag'].$this->_options['closechar']) + 1);
    return $tag;
  }
 
  /**
   * This method, along with the private class variable $instance, allows the
   * parser to be called without having to reload the codes each time (and thus
   * causing numerous unnecessary DB queries).
   * 
   * @access public
   * @author Colin Williams <sentineldiety@gmail.com>
   * @return SBBCodeParser An instance of this class
   */
  public static function getInstance()
  {
    if (!isset(self::$instance))
    {
      $class = __CLASS__;
      self::$instance = new $class();
    }
 
    return self::$instance;
  }
}
 
?>

BBCodeHelper.php:

This is the helper function that allows you to parse a string for BBCode. Place it in the appropriate helper dir (it should be in the same lib dir as the main parser file).

<?php
 
function parse_bbcode($unformatted) {
    $formatted = SBBCodeParser::getInstance()->parse($unformatted);
 
    return $formatted;
}eturn $return;
}
?>

Schema:

The following is the XML schema for the BBCode table. Add it to your schema. Sorry, I don't have a YAML version.

<table name="bbcodes" phpName="BBCode">
    <column name="id" type="integer" required="true" autoIncrement="true" primaryKey="true" />
    <column name="tag" type="varchar" size="64" />
    <column name="pattern" type="longvarchar" />
    <column name="name" type="varchar" size="255" />
    <column name="help" type="varchar" size="255" />
    <column name="selfclose" type="boolean" default="false" />
  </table>

Of course, you then need to rebuild your model.

Sample BBCodes:

This is the YAML format for the BBCodes I have implemented so far. Feel free to add to them. The syntax should be fairly self-explanatory. These are only basic formatting.

BBCode:
  b1:
    name:     Bold
    help:     Make the selected text bold
    tag:      b
    pattern:  <strong>${content}</strong>
  b2:
    name:     Italic
    help:     Make the selected text italic
    tag:      i
    pattern:  <em>${content}</em>
  b3:
    name:     Underline
    help:     Underline the selected text
    tag:      u
    pattern:  <ins>${content}</ins>
  b4:
    name:     Strikethrough
    help:     Strikethrough the selected text
    tag:      s
    pattern:  <del>${content}</del>
  b5:
    name:     Color
    help:     Change the color of the selected text
    tag:      color
    pattern:  <span style="color:${attr};">${content}</span>
  b6:
    name:     Quote
    help:     Mark the selected text as a quote
    tag:      quote
    pattern:  <strong>Quote:</strong><br /><q from="${attr}">${content}</q>
  b7:
    name:     Code
    help:     Mark the selected text as code
    tag:      code
    pattern:  <strong>Code:</strong><br /><pre class="code">${content}</pre>
  b8:
    name:     Subscript
    help:     Subscript the selected text
    tag:      sub
    pattern:  <sub>${content}</sub>
  b9:
    name:     Superscript
    help:     Superscript the selected text
    tag:      sup
    pattern:  <sup>${content}</sup>

Config:

Finally, you must add the following config information to your apps/APP/config/app.yml file:

bbcode:
    openchar:   '['
    closechar:  ']'
by Colin Williams on 2006-07-18, tagged bbcode  parser 
(6 comments)

Form validation in AJAX

The example is for a blog. The page that displays a post also proposes an AJAX form to add a comment. We want that when the validation of this form fails, it displays again in the page with an error message, and when the validation succeeds, the form is replaced byu the comment just posted.

The idea is to take advantage of the way the update option of the form_remote_tag() helper works. It accepts an associative array, where you can specify different zones to update in case of success and failure. The only problem is that for Prototype, a failure is a return code other than 2XX. So when we return the form showing the error message again, we need to set the status code to 404, for instance, for Prototype to choose to update the correct zone.

That, plus the usual use of partials here and there, and you have a working solution:

in modules/post/actions/action.class.php

// Display the form
public function executeShow()
{
  $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
}

in modules/post/templates/showSuccess.php

// Display question detail here
...
// Beginning of Comment zone
<div id="added_comment" style="display_none"> </div>
<div id="add_comment">
  <?php include_partial('comment/add_comment', array('post' => $post)) ?>
 </div>

in modules/comment/templates/_add_comment.php

<?php use_helper('Javascript', 'Validation') ?>
<?php echo form_remote_tag(array(
  'url'     => 'comment/add',
  'update'  => array('success' => 'added_comment', 'failure' => 'add_comment'),
  'script'  => true,
  'loading' => "Element.show('indicator')",
  'success' => "Element.hide('indicator');Element.show('added_comment');Element.hide('add_comment');",
  'failure' => "Element.hide('indicator');",
)) ?>
  <?php echo input_hidden_tag('post_id', $post->getId()) ?>
  <?php echo form_error('body') ?>
  <label for="body">Your comment</label>
  <?php echo textarea_tag('body') ?>
  <?php echo submit_tag('Send') ?>
</form>

in modules/comment/validate/add.yml

methods:
  post: [body]
 
fillin:
  activate:        Yes
 
names:
  body:
    required:      Yes
    required_msg:  You must provide a comment
    validators:    spamValidator
 
spamValidator:
   class:          sfRegexValidator
   param:
     match:        No     
     pattern:      /http.*http/
     match_error:  Do not provide more than one URL - It is considered Spam

in modules/comment/actions/action.class.php

public function handleErrorAdd()
{
  $this->post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
  $this->getResponse()->setStatusCode(404);
  return sfView::ERROR;
}
 
public function executeAdd()
{
  $post = PostPeer::retrieveByPk($this->getRequestParameter('post_id'));
  $this->forward404Unless($post);
  $comment = new Comment();
  $comment->setPost($post);
  $comment->setAuthor($this->getUser()->getAuthor());
  $comment->setBody($this->getRequestParameter('body'));
  $comment->save();
  $this->comment = $comment;
}

in modules/comment/templates/addError.php

<?php include_partial('comment/add_comment', array('post' => $post)) ?>

in modules/comment/templates/addSuccess.php

Your comment has been added:
<div class="comment">
  <?php echo $comment->getBody() ?>
</div>

As a bonus, the form is still there after a successful submission (but hidden), so with a few more lines of code, you can still provide a Digg-like "edit comment for the next 60 seconds" feature.

by Francois Zaninotto on 2006-10-16, tagged ajax  forms  validation 
(10 comments)

sfCustomUniqueValidator:

This snippet allow to check if an entry allready exists in a database but, at the difference of the sfUniqueValidator, you can provide as many fields as desired to perform the verification.

Installation:

The first thing you need to do is to create the file sfCustomUniqueValidator.php in your project lib directory:

<?php
  /**
 * sfCustomUniqueValidator checks if a record exist in the database with all the mentionned fields.
 *
 * ex: Check if a companie with company_name exist in country_id 
 *   class:            sfCustomUniqueValidator
 *   param:
 *     class:          Companies    //the class on which the search is performed
 *     nb_fields:      2            //the number of fields on which the comparison is done
 *     field_1:        company_name //First field of the comparison
 *     field_2:        country_id   //Other country for the comparison
 *
 * @package    lib
 * @author     Joachim Martin
 * @date       15/06/2007
 */
 
class sfCustomUniqueValidator extends sfValidator {
 
   /**
   * Executes this validator.
   *
   * @param mixed A file or parameter value/array
   * @param error An error message reference
   *
   * @return bool true, if this validator executes successfully, otherwise false
   */
 
    public function execute(&$value, &$error) {
 
        $className  = $this->getParameter('class').'Peer';
 
        //Get fields number
        $nb_fields = $this->getParameter('nb_fields');
 
        //Define new criteria       
        $c = new Criteria();
 
        //Loop on the fields
        for($i = 1; $i <= $nb_fields ; $i++) {
            //Retrieve field_$i 
            $check_param = $this->getParameterHolder()->get("field_$i");
            $check_value = $this->getContext()->getRequest()->getParameter($check_param);
 
            //If check value defined        
            if ($check_value != '') {   
                //Adding field to the criteria
                $columnName = call_user_func(array($className, 'translateFieldName'), $check_param, BasePeer::TYPE_FIELDNAME, BasePeer::TYPE_COLNAME);
                $c->add($columnName, $check_value);
            }
        }
 
        $object = call_user_func(array($className, 'doSelectOne'), $c);
 
        if ($object)
        {
          $tableMap = call_user_func(array($className, 'getTableMap'));
          foreach ($tableMap->getColumns() as $column)
          {
            if (!$column->isPrimaryKey())
            {
              continue;
            }
 
            $method = 'get'.$column->getPhpName();
            $primaryKey = call_user_func(array($className, 'translateFieldName'), $column->getPhpName(), BasePeer::TYPE_PHPNAME, BasePeer::TYPE_FIELDNAME);
            if ($object->$method() != $this->getContext()->getRequest()->getParameter($primaryKey))
            {
              $error = $this->getParameter('custom_unique_error');
 
              return false;
            }
          }
        }
 
        return true;
    } 
 
    public function initialize ($context, $parameters = null) {
        // initialize parent
        parent::initialize($context);
 
        //Set default parameters value
        $this->setParameter('custom_unique_error','The value is not unique');
 
        $this->getParameterHolder()->add($parameters);
 
        // check parameters
        if (!$this->getParameter('class'))
        {
          throw new sfValidatorException('The "class" parameter is mandatory for the sfCustomUniqueValidator validator.');
        }
 
        if (!$this->getParameter('nb_fields'))
        {
          throw new sfValidatorException('The "nb_fields" parameter is mandatory for the sfCustomUniqueValidator validator.');
        }
 
        return true;
    }
}

Usage:

The following code check if a companie with the same name and same activity exists in the same country

sfCustomUniqueValidator:
      class:                   Companies
      nb_fields:               3
      field_1:                 company_name
      field_2:                 activity_id
      field_3:                 country_id
      custom_unique_error:     This company already exist for this country

class: the model to test

nb_fields: how many fields will be checked

field_x: a field to test, obviously you need to have as many field_x as the nb_fields value

custom_unique_error: your error message

This is my very first contribution to symfony so feel free to comment/optimize.

by joachim martin on 2007-06-21, tagged unique  validation  validator 
(5 comments)

Minimal CSS pagination helper

This is helper is mostly inspired by Pagination navigation helper, but there are some differences:

PaginationHelper.php

function pagination($pager)
{
    $uri = sfRouting :: getInstance()->getCurrentInternalUri();
    $html = '';
 
    if ($pager->haveToPaginate())
    {
        $uri .= strstr($uri, '?') ? '&page=' : '?page=';
 
        if ($pager->getPage() != 1)
        {
            $html .= '<li>' . link_to('first', $uri . '1') . '</li>';
            $html .= '<li>' . link_to('previous', $uri . $pager->getPreviousPage()) . '</li>';
        }
 
        foreach ($pager->getLinks() as $page)
        {
            if ($page == $pager->getPage())
                $html .= '<li><strong>' . link_to($page, $uri . $page) . '</strong></li>';
            else
                $html .= '<li>' . link_to($page, $uri . $page) . '</li>';
        }
 
        if ($pager->getPage() != $pager->getLastPage())
        {
            $html .= '<li>' . link_to('next', $uri . $pager->getNextPage()) . '</li>';
            $html .= '<li>' . link_to('last', $uri . $pager->getLastPage()) . '</li>';
        }
 
        $html = '<ul class="pagination">' . $html . '</ul>';
    }
 
    return $html;
}

Minimal CSS

ul.pagination li {
    display: inline;
    list-style-type: none;
    padding-right: 1em;
}

In your template

<?php echo use_helper('Pagination') ?>
<?php echo pagination($pager) ?>
by brikou on 2006-07-19, tagged css  helper  pager  pagination  propel 
(6 comments)

Setting UTF-8 for Propel with MySQL tables

Since it can be annoying to have Propel ignore collation of tables in MySQL >= 4.1, you must force Propel to use UTF-8 collation by running the SET NAMES UTF8 SQL query.

In order to do that, you specify a filter that executes at every request. Specify the following code in filters.yml:

myUtf8ConnectionFilter:
  class: myUtf8ConnectionFilter
  activate: on

Create a new file called myUtf8ConnectionFilter.class.php in your application's lib folder and insert the following code:

<?php
class myUtf8ConnectionFilter extends sfFilter
{
  public function execute($filterChain)
  {
    $con = Propel::getConnection();
    if ($con){
       $con->executeQuery("set names utf8");
    } else {
      throw new Exception($e);
    }
    $filterChain->execute();
  }
}
?>

The code was copied from this website - I am not asserting any authorship.

by Klemen Slavič on 2006-06-19, tagged collation  propel  utf8 
(5 comments)

AJAX vote - 5 stars

In the template where the object that users can vote on (here, a snippet) is displayed, add:

  <?php echo use_helper('Javascript') ?>
  <?php $id = $snippet->getId() ?>
  <?php if($sf_user->canVoteFor($snippet)): ?>
    <span id="vote_for_<?php echo $id ?>" style="white-space:nowrap;">
      rate this snippet: <?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $snippet->getAverageVote()): ?>rated<?php endif; ?>" 
      id="vote_<?php echo $id ?>_<?php echo $i ?>"><?php echo link_to_remote(image_tag('spacer.gif', 'width=10 height=10'), array(
        'url'         => 'snippet/voteForSnippet?id='.$id.'&vote='.$i,
        'update'      => 'vote_for_'.$id,
        'loading'     => visual_effect('appear', 'indicator'),
        'complete'    => visual_effect('fade', 'indicator').visual_effect('highlight', 'vote_for_'.$id),
      ), array(
        'onMouseOver' => 'highlight_stars('.$id.', '.$i.', true);',
        'onMouseOut'  => 'highlight_stars('.$id.', '.$i.', false);',  
      )) ?></div><?php endfor; ?>
      <span id="indicator" style="display:none"> <?php echo image_tag('indicator.gif') ?></span>
    </span>
  <?php else: ?>
    <?php include_partial('voted', array('id' => $id, 'vote' => $snippet->getAverageVote())) ?>
  <?php endif; ?>

Of course, you have to define the rules about who can vote on what in the -&gt;canVoteFor() method of the User object (in apps/myapp/lib/myUser.php). The template uses a _voted.php partial:

<?php for($i = 1 ; $i <= 5 ; $i++): ?><div class="rate <?php if($i <= $vote): ?>rated<?php endif; ?>" id="vote_<?php echo $id ?>_<?php echo $i ?>"><img src="/images/spacer.gif" width="10" height="10" /></div><?php endfor; ?>

DO NOT add extra spaces to the long lines, or the stars get separated by blank spaces.

The mechanism that changes the aspect of stars relies on classes, so add the following styling to a CSS used in your template:

.rate {
display:inline;
width:10px;
height:10px;
margin:0;
padding:0;
background-image:url(/images/vote_star.gif);
background-position: left 10px;
}
.rated
{
background-position: left 0px;
}
.ratehover {
background-position: left 20px;
}

The vote_star.gif file is a 10x30px image containing three versions of the star: voted, hovered, not voted.

vote_star

The snippet/voteForSnippet action does something like:

  public function executeVoteForSnippet()
  {
    $this->id   = $this->getRequestParameter('id');
    $snippet = SnippetPeer::retrieveByPk($this->id);
    if(!$snippet)
    {
      return sfView::NONE;
    }
 
    $vote = new Vote();
    $vote->setUserId($this->getUser()->getUserId());
    $vote->setSnippetId($snippet->getId());
    $vote->setVote($this->getRequestParameter('vote'));
    $vote->save();
 
    $this->vote = $vote->getSnippet()->getAverageVote();
  }
by Francois Zaninotto on 2006-05-20, tagged ajax  vote 
(7 comments)

Soap server

This has been tested with the version ( 1.0.0beta4 ), I had a bit different structure for older version.

Ok ... here we go... how to create a soap server and take advatage of symfony's models and actions.

First we need the soap environment file, this will be the file that will initiate the soap API environment and the soap server:

name: /web/soapapi.php (soap server)

<?php
 
define('SF_ROOT_DIR',    realpath(dirname(__FILE__).'/..'));
define('SF_APP',         'fo');
define('SF_ENVIRONMENT', 'soap');
define('SF_DEBUG',       true);
 
require_once(SF_ROOT_DIR.DIRECTORY_SEPARATOR.'apps'.DIRECTORY_SEPARATOR.SF_APP.DIRECTORY_SEPARATOR.'config'.DIRECTORY_SEPARATOR.'config.php');
 
ini_set("soap.wsdl_cache_enabled", "0");
$server = new SoapServer('http://www.example.com/soapapi.wsdl'); //this file is defined in the next step
$server->setClass("mySoapController"); // more to come on this one
$server->handle();
 
?>

Now we need a wsdl file that will take advantage of the environment above.

name: /web/soapapi.wsdl (soap server definition)

<?xml version='1.0' encoding='UTF-8'?>
<definitions name="soapapi" targetNamespace="urn:soapapi" xmlns:typens="urn:soapapi" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns="http://schemas.xmlsoap.org/wsdl/">
   <message name="getFactorial">
      <part name="user" type="xsd:string">user name</part>
      <part name="password" type="xsd:string">user password</part>
      <part name="number" type="xsd:float">user password</part>
   </message>
   <message name="getFactorialResponse">
      <part name="return" type="xsd:float"/>
   </message>   
   <portType name="soapapiPortType">
      <operation name="getFactorial">
         <documentation>
            user            required
            password            required
 
            Generates the factorial of a passed number.
         </documentation>
         <input message="typens:getFactorial"/>
         <output message="typens:getFactorialResponse"/>
      </operation>
   </portType>
   <binding name="soapapiBinding" type="typens:soapapiPortType">
      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
      <operation name="getFactorial">
         <soap:operation soapAction="urn:soapapiAction"/>
         <input>
            <soap:body namespace="urn:soapapi" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
         </input>
 
         <output>
            <soap:body namespace="urn:soapapi" use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
         </output>
      </operation>      
   </binding>
   <service name="soapapiService">
      <port name="soapapiPort" binding="typens:soapapiBinding">
         <soap:address location="http://www.example.com/soapapi.php"/>
      </port>
   </service>
</definitions>

Now we need to write our soap cotroller layer and that is the class that is asssigned by the soap server above.

name: /apps/fo/lib/mySoapController.class.php

<?php
 
class mySoapController extends sfController {
 
   public $request;
 
   public function __construct(){
      /**
       * Since we're bypassing Symfony's action dispatcher, we have to initialize manually.
       */
      $this->context = sfContext::getInstance();
      $this->request = $this->context->getRequest();
   }
 
   protected function soapAuth($domain,$password){
     try {
 
       $c = new Criteria();
       $c->add(UserPeer::USERNAME ,$domain);
       $c->add(UserPeer::PASSWORD ,$password);
 
       $check = UserPeer::doSelectOne($c);
 
       if($check){
         $user = $this->context->getUser();
         $user->addCredential($check->getCredential());
         $user->setAuthenticated(true);
       }else{
         throw new Exception('Soap Authentication failed');
       }
     }catch (Exception $e){       
       throw new SoapFault("1",$e->getMessage());
     }
   }
 
   function getFactorial($user, $password, $number){
 
     $this->soapAuth($user, $password); //I call this at the start of each function call in the soap controller (You can choose not to do it)
     $this->request->setParameter('number',$number);
 
     $action = $this->getAction('soapapi','getFactorial');
     $result = $action->executeGetFactorial();
 
     return $result;
   }
}
?>

We are almost there ... Next we need action and view

name: /apps/fo/modules/soapapi/actions.class.php

<?php
 
class soapapiActions extends sfActions
{
  public function __construct(){
    if(SF_ENVIRONMENT == 'soap') $this->initialize(sfContext::getInstance());
  }
 
  public function executeIndex()
  {
    $this->forward('default', 'module');
  }
 
  public function executeGetFactorial(){
 
    $num = $this->getRequestParameter('number');
 
    $result = 1;
    for($i=1;$i<$num;$i++) $result = $result + ($result * $i);
 
    if(SF_ENVIRONMENT == 'soap') return $result; //you can return the view or just a number... usually for soap you want to return simple stuff ... so you need to do the check
 
    $this->result = $result;
 
  }
}
 
?>

finally the view...

name: /apps/fo/modules/soapapi/getFactorialSuccess.php

<?php
 
echo "The factorial for ".$sf_params->get('number')." is {$result} ";
 
?>
by Fuad Arafa on 2007-01-18, tagged soap 
(15 comments)