Posts Tagged ‘symfony’

FOSS Fridays: MacGyvered Key/Value in Symfony

Posted on:Friday, August 7th, 2009 by Ashish Datta

On a project we’re currently working on, we arrived at a situation where our client had a loose and very fluid idea of the information he wanted to store about certain objects in his application. We didn’t specifically know the number of fields or the format of the data. Continually modifying the schema would of been painful so I wanted to try something different.

Since the data is more or less non-relational (it only relates to the object that owns it), what I really wanted was an ad-hoc key/value store. But I didn’t want to break Propel’s ORM abstractions. I still wanted to be able to do:

$company->getMission();

With the new system.

Turns out you basically can. Here’s how it works:

  1. Add a “dynamic_field” table to your schema. (definition is below)
  2. Override the __call(), hydrate(), and save() functions in Propel model file that you want to MacGyver.
  3. Pray.

Definition of the dynamic_field table:






  

So the idea is we want to basically build a Propel Behaviour to capture any undefined get/set calls and “get” the data out of the dynamic_field table or “set” the data by storing the value into the table. Since the table stores the model class and model id, the “keys” only have to be unique by model (just like Propel normally works).

Here is the code you need to add to the model file:

public function __call($method, $arguments){

  // snag the dynamic setters
  if(strpos($method, "set") !== false
      && $method[3] === strtoupper($method[3])){
	  $name = strtolower( substr($method, 3) );
	  $this->dynamicFields[ $name ] = array_pop( $arguments );
	  return true;
  }

  // snag the dynamic getters
  if(strpos($method, "get") !== false
      && $method[3] === strtoupper($method[3])){
        $name = strtolower( substr($method, 3) );

      if( array_key_exists($name, $this->hydratedFields) ){
        	return $this->hydratedFields[$name];
      }

       if( array_key_exists($name, $this->dynamicFields) ){
        	return $this->dynamicFields[ $name ];
        }

      	return null;
    }

    return parent::__call($method, $arguments);
}

public function hydrate($row, $startcol = 0, $rehydrate = false)
{
  parent::hydrate($row, $startcol, $rehydrate);
  // pull in our dynamic fields while we're at it
  $c = new Criteria();
  $c->add( DynamicFieldPeer::MODEL, get_class($this) );
  $c->add( DynamicFieldPeer::MODEL_ID, $this->getId() );
  $dynamic = DynamicFieldPeer::doSelect( $c );

  foreach($dynamic as $d){
     $this->hydratedFields[ $d->getFieldName() ] = unserialize( $d->getFieldValue() );
  }

  return true;
}

  public function save(PropelPDO $con = null){

	  // save the dyanmic ones
    if( count($this->dynamicFields) ){

    	// grab the old ones and update stuff
      $keys = array_keys($this->dynamicFields);
      $c = new Criteria();
      $c->add( DynamicFieldPeer::MODEL, get_class($this) );
      $c->add( DynamicFieldPeer::MODEL_ID, $this->getId() );
      $c->add( DynamicFieldPeer::FIELD_NAME, $keys, Criteria::IN );
      $savedFields = DynamicFieldPeer::doSelect( $c );

      foreach($savedFields as $sf){
      	$sf->setFieldValue( serialize( $this->dynamicFields[$sf->getFieldName()] ) );
      	$sf->save();
      	unset( $this->dynamicFields[$sf->getFieldName()] );
      }

		  foreach( $this->dynamicFields as $key => $val ){
			  $df = new DynamicField();
			  $df->setModel( get_class($this) );
			  $df->setModelId( $this->getId() );
			  $df->setFieldName( $key );
			  $df->setFieldValue( serialize( $val ) );
			  $df->save();
		  }

	  }

	  return parent::save($con);
  }

The code captures any undefined get/set calls and then deals with them appropriately. It won’t serialize the fields until the save() call (just like regular Propel objects). I also overloaded the hydrate() function so that the object will fetch all of its dynamic fields in one shot, as opposed one query per get.

Using the modified objects is exactly like regular Propel objects, the changes are entirely transparent except that you can get/set anything you want.

For example:

$company = CompanyPeer::retrieveByPK( 5 );
$company->setVision( "this is my vision" );
echo $company->getVision();

Will work even though there is no “vision” column on the company table. Magic.

There is one big problem with this trick though. Because of the Propel class hierarchy, there isn’t any way to introduce this code in one file and have other objects inherit the changes. You have to manually copy it to any model file that you want to enable it for.

FOSS Fridays: OpenSSL in PHP

Posted on:Friday, July 31st, 2009 by Ashish Datta

Well Twitter has “Follow Fridays” so I thought we should do FOSS Fridays. I don’t really have a plan for this and it might not last but let’s see where it goes.

In the last few days a couple of people have asked for tips on how to use OpenSSL from PHP. So here is a snippet on how to do it. This comes out of an application that provides a shared authentication system between our client’s LDAP system and their partner’s systems.

It works like so:

  1. Users login to the application using their LDAP credentials.
  2. When the users request to visit the partner site, our system packages up their login information, encrypts it, signs it, and shoots it along with the user to the partner site.
  3. Next, the partner checks if the user has an account and if they do it logs them in. Otherwise, it creates them a new account and logs them in.

All of this is done transparently so that the user doesn’t know they’ve actually left the original site.

Here is the code to do it. PS. it’s from a Symfony application.

$user = $this->getUser();

$profile = $user->getProfile();

if(is_null($profile)){ die("Could not get user profile?"); }

$email = $profile->getEmail();

$firstName = $profile->getFirstName();

$lastName = $profile->getLastName();

$password = $request->getParameter("password");

$keyText = file_get_contents(sfConfig::get("sf_root_dir") . "/" . sfConfig::get("app_their_public_key"));

$theirPublicKey = openssl_pkey_get_public($keyText);

$keyText = file_get_contents(sfConfig::get("sf_root_dir") . "/" . sfConfig::get("app_our_private_key"));

$outPrivateKey = openssl_pkey_get_private($keyText);

$arr = array();

$arr["U_EMAIL"] = $email;

$arr["U_PASSWORD"] = $password;

$arr["U_FIRST_NAME"] = $firstName;

$arr["U_LAST_NAME"] = $lastName;

$arr["RL_E"] = $this->generateUrl("ps_error", array(), true);

$arr["RL_S"] = "PRIVATE URL";

$arr["ETIME"] = time() + 60;

$queryString = http_build_query($arr);

$res = openssl_sign($queryString, $signature, $outPrivateKey);

if(!$res){ throw new sfException("Could not sign the payload!", 1); }

$t = openssl_pkey_get_details($theirPublicKey);

$t = (int) ($t['bits'] / 8 ) - 11;

$l=strlen($queryString);

$cryptPayload = '';

for ($i=0; $i<$l; $i+= $t) {

  $block = substr($queryString, $i, $t);

  if (!openssl_public_encrypt($block,$tS, $theirPublicKey)){
    throw new sfException('failed encrypt', 1);
   }

  $cryptPayload .= $tS;
}

$this->encodedSignature = base64_encode($signature);

$this->encodedData = base64_encode($cryptPayload);

The net result of all of this is an encrypted payload with the user’s credentials and a signature of the payload. The payload is encrypted with “their” public key and then signed with “our” private key. This ensures that only they can open the package and only we can generate valid signatures.

Happy Friday!

Iterating over Symfony Forms for Custom Output

Posted on:Friday, June 19th, 2009 by Matt Daum

Recently we were working on a project in which we needed to switch from forms auto-formatting themselves ( <?php echo $form;?>) to allow for much more customization in the output.   While there is a Symfony Forms for Designers chapter in the forms documentation, it doesn’t help much for iterating over a form object and customizing the output.  There is a simple foreach($form as $field) that will iterate over every field in the form.  The problem with this is when you have embedded forms and you want to do something different with the formatting on them.  This will iterate over all the fields and you will not know when the field is the embedded form or not.  So we came up with the following which works:

foreach($form as $field)
{
  // If this is true, then that means its an embedded form.
  if(get_class($field)=='sfFormFieldSchema')
  {
    foreach($field as $f)
    {
      // Don't want to see hidden field labels
      if(!$f->isHidden())
        echo $f->renderLabel()." ";
      echo $f->renderError();
      echo $f->render();
    }
  }
  else
  {
    if(!$field->isHidden())
        echo $field->renderLabel()." ";
    echo $field->renderError();
    echo $field->render();
  }
}

This will work fine for a form with as many as single embedded forms.  It allows you to easily use the same view for multiple forms and be able to customize their embedded forms easily.  If you have only one form that you will be doing this with, and are worried about performance we recommend then not using a foreach loop and doing it by hand, this will save you on performance as you will not have as many if statements in each iteration.

Loading Different Javascript/CSS Files in Different Environments

Posted on:Tuesday, June 9th, 2009 by Matt Daum

Today we were working on minifying our Javascript and CSS files for a site we are launching tomorrow.  We came across that we didn’t want to have to get the repository out of sync and update view.yml to only include our single CSS style sheet and one javascript file when working between developement environment and production.  What we wanted was the following in view.yml the javascripts: and stylesheets: parameters to change depending on what environment you are loading, however using prod: and dev: in this didn’t work like app.yml does.  An example of this would be:

all:
  title: My site
dev:
  stylesheets:[styleone.css,styletwo.css,stylethree.css]
  javascripts:[one.js,two.js,three.js]
prod:
  stylesheets:[style.min.css]
  javascripts:[main.min.js]

This way we could have them load in production much quicker and reduce load time, however still easily debug the files in development environments.  After looking around a bit we couldn’t find any standard current solution so we came up with the following.  In app.yml we defined two variables for the dev and production environments – javascript_files and css_files.  Here you put the list of the files you wanted to loaded in each environment.  Then in view.yml it now looks like:

all:
  title: My Title
  stylesheets: [<?php echo sfConfig::get('app_css_files');?>]
  javascripts: [<?php echo sfConfig::get('app_javascript_files');?>]

The configuration variables get their value depending on the environment you are running in so it loads the proper files.  With this configuration we can easily debug in development environment and still have minified versions of the CSS and Javascript in production environement.   Hopefully this will save some of you time and make your life easier.

Taking Your Application International

Posted on:Wednesday, May 27th, 2009 by Matt Daum

Many clients develop their applications with only one language in mind.  Recently one of our clients after we had developed two different applications for them decided that the applications needed to be translated into seven different languages.  At first the client said, “We’ll just make seven copies of it, and update each one separately.”  While this may at first seem the be a quick simple solution, think of the long term affects of this.  First, if the application is large, you are going to be wasting much space.  Second, and most important, using this approach you are going to be stuck trying to maintain seven different copies of the same application; every update for each each application will have to be made seven times.  Not only is this error prone, but it is inefficient.

Our solution?  Use a common practice called internationalization (i18n) and localization (l10n).  i18n and l10n is text translation (from page content to form labels to error message) and localizing of content ( displaying dates, currency, numbers, etc. in a specific format).  For many applications this is not an easy process, and often could require one to go back and rewrite much of the code.  However, we use Symfony which makes the task much easier.  Symfony allows you to use dictionary files and the database to handle this.  Symfony can scan your entire project looking for specific markup(__(“Text here”)) and pull the strings out into a simple XML file which you can enter the translations for.  The file looks like the following:

<?xml version=”1.0″ encoding=”UTF-8″?>
<!DOCTYPE xliff PUBLIC “-//XLIFF//DTD XLIFF//EN” “http://www.oasis-open.org/committees/xliff/documents/xliff.dtd”>
<xliff version=”1.0″>
<file source-language=”en_US” target-language=”en_US” datatype=”plaintext” original=”messages” date=”2009-05-28T17:27:02Z” product-name=”messages”>
<header/>

<body>

<trans-unit id=”1″>
<source>My String To Be Converted</source>
<target/>
</trans-unit>

</trans-unit>
</body>
</file>
</xliff>

With file all you need to do is to modify the <target /> to <target>My Translated Text</target>  When the string “My String To Be Converted”  is output in the application it will be converted to “My Translated Text”.  You’d save this file for example as messages.es.xml if it was the Spanish translation file.

To read more on internationalization with Symfony visit http://www.symfony-project.org/book/1_2/13-I18n-and-L10n.  We use Symfony because of situations like this it allows us to quickly adapt our products to our customer needs.

Always think ahead when developing your applications.  Planning for the future work on an application can save you hours of rewriting.

sfWidgetjQueryTimepickr – Symfony timepickr widget

Posted on:Monday, April 6th, 2009 by Ashish Datta

A couple of months ago John Resig posted on his blog about a “new” way for users to pick time.

The component is a jQuery plugin called timepickr and I thought it was particularly neat. Anyway, I finally needed to write a form which had a time component so I figured I’d drop in the timepickr jQuery plugin.

It didn’t look like there was a Symfony Forms widget for it so I whipped one up. You can grab it here.

The only issue with timepickr is that it introduces a ton of dependencies. It requires jQuery, jQuery-ui, jQuery.utils, jQuery.string, and ui.dropslide. Additionally, it needs the dropslide css as well as the timepickr css.

The form widget assumes that you will include jquery-ui by yourself since everyone usually has different naming conventions. It will include the other JS files and CSS files for you.

You can download a package with all of the JS and CSS you need here. Again this DOES NOT include the jQuery-ui stuff. YOU have to include that yourself in your Symfony project as well as on the page that you deploy this widget.

To use it, just drop it in your Symfony project somewhere where classes get autoloaded (projectdir/lib works) and then instantiate a widget with new sfWidgetjQueryTimepickr() . It currently will support all of the timepickr options passed in as widget options (on the constructor). Full documentation for timepickr is here.

Have fun picking time.

GIT Ignores and Symfony

Posted on:Sunday, March 22nd, 2009 by Matt Daum

We use GIT for version control at Setfive.  However often with our Symfony projects creating all the ignore files in all the plugins model/base’s and other locations throughout the project get very tiresome.  Since on some larger projects you can end up with 10-20 plugins, having to create an ignore for the autogenerated model, forms, and filters takes a long time.  Today we really quickly just wrote a plugin that allows you to quickly just run symfony util:generate-ignores git --add-ignores and it will automatically place the .gitignore file throughout your project in the correct locations and add them into your next commit.  You can also just have it place them throughout the project, but not add them to the next commit if you drop the --add-ignores option.  The plugin also accepts “cvs” instead of git for cvs based projects.

The reason you do not want the base, om, etc. directories in your repository is because every time a person rebuilds the model they will be updating those files(many times just changing the timestamp at the top of the autogenerated files), which causes uncessarily large commits.

You can get the plugin via http://www.symfony-project.org/plugins/sfSCMIgnoresTaskPlugin or install it via symfony plugin:install sfSCMIgnoresTaskPlugin.

If you have any questions/requests shoot us an email.

sfPropelPager and GROUP BY criteria

Posted on:Tuesday, February 10th, 2009 by Ashish Datta

So for one reason or another (actually a few bad ones) I ended up having to use a Criteria object looking like this:

$c = new Criteria ( );
$having = $c->getNewCriterion('the_count', count($tags), Criteria::GREATER_EQUAL);
$c->add(TaggingPeer::TAG_ID, $tags, Criteria::IN);
$c->addSelectColumn("COUNT(*) AS the_count");
$c->addSelectColumn(TaggingPeer::ID);
$c->addSelectColumn(TaggingPeer::TAGGABLE_ID);
$c->addSelectColumn(TaggingPeer::TAGGABLE_MODEL);
$c->addGroupByColumn(TaggingPeer::TAGGABLE_MODEL);
$c->addGroupByColumn(TaggingPeer::TAGGABLE_ID);
$c->addHaving($having);

It makes SQL that looks something like this:

“SELECT tag_id, tag_model, id, COUNT(*) AS the_count FROM sf_tagging WHERE tag_id IN (1,2,3) GROUP BY taggable_model, taggable_id HAVING the_count > int”

The query sucks but whatever it works.

My issue came when I tried to use it with a sfPropelPager. I set up the pager per usual but for some reason the results that were coming back weren’t correct. For some reason, the COUNT being returned by the sfPropelPager was completely wrong. It turns out the offending lines are here in sfPropelPager.class.php :

public function init()
{
  $hasMaxRecordLimit = ($this->getMaxRecordLimit() !== false);
  $maxRecordLimit = $this->getMaxRecordLimit();

  $cForCount = clone $this->getCriteria();
  $cForCount->setOffset(0);
  $cForCount->setLimit(0);
  $cForCount->clearGroupByColumns();

}

For whatever reason, sfPropelPager clears the GROUP BY clauses before it calculates the COUNT for a criteria object. I’m not sure why it does this – but it certainly is unexpected and breaks my query in particular.

There are a handful of posts about this on the Symfony forums and it looks like the Propel people know about the issue to.

The solution to this is to use the setPeerCountMethod() from sfPropelPager. The setPeerCountMethod() function allows you to specify a custom COUNT() method inside the peer for your Criteria. I went ahead and added a new function to put the GROUP BY columns back in:

public static function advancedSearchCount($c){
$c->addGroupByColumn(TaggingPeer::TAGGABLE_MODEL);
$c->addGroupByColumn(TaggingPeer::TAGGABLE_ID);
return self::doCount($c);
}

This solution works but it is extremely rigid. Since the custom count function has to be static you’d really be out of luck if you had variable columns or other dynamic requirements.

I’d love to know if someone has a cleaner/better/more elegant solution for this.

Client-side validation for the new Symfony forms with jQuery

Posted on:Friday, January 23rd, 2009 by Hamid Palo
The new Symfony forms are great improvement over the old forms and once you get over the learning curve you can’t imagine life without them. The only problem with them is that they don’t really offer client-side validation (yes, being agnostic when it comes to Javascript libraries is maybe good but come on). There is fortunately a plugin that does client-side validation but it is Prototype-based, and looks a bit clunky.
For those of us using jQuery there was nothing, so we decided to write our own small helper that integrates this jQuery validator with the new forms. It’s nothing fancy, but it does the trick for now, and we may expand it into a full-fledged plugin that does all sorts of crazy things.
Using the helper:
  1. Download and install the jQuery validation plugin.
  2. Download the helper and put it in lib/helper/jQueryValHelper.php
  3. Include the jQueryVal helper.
  4. To enable the helper for a specific form:
    <?php echo jquery_val_form_tag($form, array(‘action’ => url_for(“@register) ))?>

That’s it.

The validator currently supports email, min and max length validation. The only required option is action, the other options you can pass it are:

  1. error_placement: Change where the errors are rendered, for example:
    'error_placement' => 'error.prependTo(element.parent().next())'
  2. error_element: Change the element used to render the errors, default is label.

All other options you pass are added to the form as attributes.

Rich Date Input Widget for Symfony 1.1 Forms

Posted on:Sunday, September 14th, 2008 by Matt Daum

I noticed that for some reason the 1.1 forms do not allow for the rich date input that many people like to use, so I just created a new widget that allows for rich date input. See below for the widget’s code. It uses the input_date_tag widget in the ‘Form’ helper.

/**
* myWidgetFormRichDate is a rich date widget for 1.1+ forms
*
* @author Matt Daum matt [at] setfive.com
*/
class myWidgetFormRichDate extends sfWidgetFormDate
{

  /**
  * @param array $options An array of options
  * @param array $attributes An array of default HTML attributes
  *
  * @see sfWidgetForm
  */
  protected function configure($options = array(), $attributes = array())
  {
    parent::configure($options, $attributes);
  }

  /**
  * @param string $name The element name
  * @param string $value The value displayed in this widget
  * @param array $attributes An array of HTML attributes to be merged with the default HTML attributes
  * @param array $errors An array of errors for the field
  *
  * @return string An HTML tag string
  *
  * @see sfWidgetForm
  */
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    //Get the date input function from Form helper
    use_helper('Form');
    //Make the widget rich
    $attributes['rich']=true;
    return input_date_tag($name,$value, $attributes);
  }
}