Code snippets for symfony 1.x

Navigation

Refine Tags

Snippets tagged "manytomany"

Many-to-many and one-to-many autocompletition

In here I will be using sfWidgetFormJQueryAutocompleterMany ( http://forum.symfony-project.org/index.php/m/77584/#msg_77462 ) to ease the relating of an object to other objects (hence keywords/tags/recepients... management becomes heaven based on non religious views).

The ORM used is Doctrine.

For this to make any sense I will tie the snippet to a quick tutorial like explanation. In the blog tutorials spirit I will now implement the widget into blog post tags management.

Getting the widget

Currently you can download it from the forum thread linked above. If let it will become part of sfFormExtraPlugin ( http://www.symfony-project.org/plugins/sfFormExtraPlugin ). If you find any annoyances / bugs just let me know.

Scheme

tableName: blog_post
 
  actAs:
    Timestampable:
    Sluggable: { fields: [title]}
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    Sluggable: { fields: [name]}
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

For the applications depending on i18n.

tableName: blog_post
 
  actAs:
    Timestampable:
    I18n:
      fields: [title, content, excerpt]
      actAs:
        Sluggable: { fields: [title], uniqueBy: [lang, title] }
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
 
    title:
      type: string(255)
      notnull: true
 
    content:
      type: clob
      notnull: true
 
    excerpt:
      type: clob
      notnull: true
 
  relations:
    Tags:
      class: Tag
      local: blog_post_id
      foreign: tag_id
      type: many
      foreignType: many
      foreignAlias: BlogPosts
      refClass: BlogPostTag
 
#------------------- Connection for publication -> tags  --------------------
BlogPostTag:
 
  tableName: blog_post_tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
  columns:
    blog_post_id:
      type: integer
      primary: true
    tag_id:
      type: integer
      primary: true
 
#------------------- Tag --------------------
Tag:
 
  tableName: tag
 
  options:
    collate: utf8_unicode_ci
    charset: utf8
 
  actAs:
    I18n:
      fields: [name]
      actAs:
        Sluggable: { fields: [name], uniqueBy: [lang, name] }
 
  columns:
    id:
      type: integer
      primary: true
      autoincrement: true
    name:
      type:  string(255)
 

Adding the widget to a form

Similar, if not identical, use as sfWidgetFormJQueryAutocompleter, applying a renderer class by doing so: /lib/form/doctrine/BlogPostForm.class.php

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
}
 

If you support more than one languages / i18n use this set up instead:

class BlogPostForm extends BaseBlogPostForm
{
    public function configure()
    {
        $autocompleteWidget = new sfWidgetFormChoice(array(
          'multiple'         => true,
          'choices'          => $this->getObject()->getTags(),
          'renderer_class'   => 'sfWidgetFormJQueryAutocompleterMany',
          'renderer_options' => array(
            'config' => '{
                json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'",
                json_cache: true,
                filter_hide: true,
                culture: "'.$this->getCulture().'",
                filter_selected: true,
                maxshownitems: 8        
              }')
        ));
        $this->widgetSchema['tags_list'] = $autocompleteWidget;
    }
    protected function getCulture() {
        return isset($this->options['culture']) ? $this->options['culture'] : sfContext::getInstance()->getUser()->getCulture();
    }
}
 

While browsing a website you could have opened several windows/tabs and change the language in one of them. Then the session for the culture is changed. If the page with the autocompleter is not the one with the changed language the new suggestions will be made for the new language which is wrong. To maintain persistent culture before form submission use the method provided plus the configuration option for the javascript of the widget "culture".

Wiring to an action

Our module is Tag, our action: autocomplet stating this: /modules/tag/actions/actions.class.php

public function executeAutocomplete(sfWebRequest $request)
    {
      $this->getResponse()->setHttpHeader('Content-Type','application/json; charset=utf-8');
 
      $tags = Tag::retrieveSuggestions($request->getParameter('q'), $request->getParameter('l'),$request->getParameter('c'));
 
      return $this->renderText(json_encode($tags));
    }
 

Retrieve results

Our model method used here is retrieveSuggestions : /lib/model/doctrine/Tag.class.php

static public function retrieveSuggestions($q, $l,$c)
  {
  $tags = Doctrine_Query::create()
    ->select('t.*,LOCATE(:token_raw,t.name) AS index')
    ->from('Tag t')
    ->where('t.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

And again for the i18ned applications:

static public function retrieveSuggestions($q, $l, $c)
  {
  $culture = ($c!=null) ? $c : sfContext::getInstance()->getUser()->getCulture();
  $tags = Doctrine_Query::create()
    ->select('t.id,tr.name,LOCATE(:token_raw,tr.name) AS index')
    ->from('Tag t')
    ->leftJoin('t.Translation tr')
    ->where('tr.lang = :culture AND tr.name LIKE :token')
    ->orderBy('index')
    ->limit($l)
    ->execute(array('culture' => $culture,'token_raw' => $q , 'token' => '%'.$q.'%'));
 
    $jsonTags = array();
    foreach ($tags as $tag)
    {
      $jsonTags[] =array('caption' => (string) $tag->Translation[$culture]->name,'value'=> $tag->getPrimaryKey()) ;
    }
    return $jsonTags;
  }
 

The important part here is that you return array with pair of keys:

If you omit key names or use numerical keys it won't work.

The beloved end.

by Anton Stoychev on 2009-04-30, tagged autocomplete  manytomany  onetomany  widget 

doctrine_admin_double_list in Symfony 1.2

When working with the admin generator in Symfony 1.2, I found I could no longer use the doctrine_admin_double_list field type. After hours of searching, I finally figured out how to get this functionality back. It can no longer be done from the generator.yml file, instead you need to modify your table's form class, e.g. lib/form/doctrine/XXXForm.class.php:

class XXXForm extends BaseXXXForm
{
  public function configure()
  {
    $this->widgetSchema['field_name'] = new sfWidgetFormDoctrineChoice(array(
      'model'           => 'ModelName',
      'add_empty'       =>  false,
      'renderer_class'  => 'sfWidgetFormSelectDoubleList',
    ));
  }
}
 

This requires the sfFormExtraPlugin plugin to be installed.

I assume this will work equally well with Propel. See symfony Forms in Action for more info.

by Ryan Hayle on 2009-04-02, tagged admindoublelist  doctrine  doctrineadmindoublelist  manytomany 

Using SQL aggregate functions

I had some trouble finding information on how to use SQL aggregate functions like GROUP BY, COUNT and HAVING with Propel, so here is some info about that.

Suppose you have a system with a many-to-many relation between articles and authors, this example shows how to find out how many articles each author has worked on.

$c = new Criteria();
 
// optionally look only for certain authors whose IDs are in $results
$c->add(AuthorPeer::ID, $results, Criteria::IN);
// JOIN them with the article IDs
$c->addJoin(ArticleAuthorPeer::AUTHOR_ID, AuthorPeer::ID);
// list each author only once and count the number of articles they have worked on
$c->addGroupByColumn(AuthorPeer::ID);
$c->addAsColumn('numArticles', 'COUNT('.AuthorPeer::ID.')');
 
// optionally retrieve only those authors that have a certain number of articles (like 'numArticles=2' or 'numArticles>2')
// the first argument does not really matter since this is a custom criteria
// according to the SQL standard this cannot be done with a WHERE clause
$c->addHaving($c->getNewCriterion(AuthorPeer::ID, 'numArticles=2', Criteria::CUSTOM));
 
// order by the number of articles
$c->addDescendingOrderByColumn('numArticles');
 
// get a ResultSet and iterate over it
$rs = AuthorPeer::doSelectRS($c);
$counts = array();
$results = array();
while ($rs->next())
{
  $author = new Author();
  // hydrate the object and store how many columns it has
  $lastColumn = $author->hydrate($rs);
  $results[] = $author;
  // then retrieve the COUNT from the first column not belonging to the object
  $counts[] = $rs->getInt($lastColumn);
}
$this->results = $results;
$this->counts = $counts;
 
by Georg Sorst on 2007-12-18, tagged aggregate  count  criteria  manytomany  propel  sql 
(2 comments)