Posts Tagged ‘doctrine’

Getting an extra ‘Invalid’ or other error on your symfony form?

Posted on:Thursday, January 27th, 2011 by Matt Daum

On a project I’m working on I came across the following problem: we had a email field that we needed to be unique in our system, but we also made sure that it matched a confirm email field. A snippet of our form looks like this:

  public function configure()
  {
     $this->setWidgets( array(
        'email'         => new sfWidgetFormInputText()
        'confirm_email' => new sfWidgetFormInputText()
      ));

      $this->setValidators(array(

      'email'=>         new sfValidatorAnd(
                          array(
                              new sfValidatorEmail( array('required' => true) ),
                              new sfValidatorDoctrineUnique(
                                  array('throw_global_error' => true, 'model' => 'sfGuardUser', 'column' => 'username'),
                                  array('invalid' => 'Sorry! A user with that email address already exists.')
                              )
                          )),
        'confirm_email' => new sfValidatorEmail( array('required' => true) )

      ));

      $this->validatorSchema->setPostValidator(
        new sfValidatorSchemaCompare('password', '==', 'confirm_password')
      );
  }

When we submitted an email that was already in the system we got back two errors:

  • Sorry! A user with that email address already exists.
  • Invalid.

For a while I thought is there some extra validator somewhere that I left on? Where is this invalid coming from? It ended up being due to the way the validators work. If a validator throws an error it doesn’t return that validator’s value. So by the time it gets to the sfValidatorSchemaCompare post validator the value of `email` is null and `confirm_email` has the value you input, thus the seemingly extra ‘Invalid’ message.

This can be fixed easily with a sfValidatorCallback instead of the sfValidatorSchemaCompare. Here is the fix:

  public function validateConfirmEmail( $validator, $values ){

    if($values['email']&&$values['email']!=$values['confirm_email'])
    {
      throw new sfValidatorError($validator, 'Please confirm your email, currently they do not match!.');
    }

    return $values;
  }

This way if the email is blank it doesn’t both making sure that the `email` matches the `confirm_email`. You don’t need to worry about a person just passing two blank emails as the earlier validator(the sfValidatorEmail requires it to be there and valid).

If you are getting an extra validation error, check your postValidators and how the values get to them.

Changing a Doctrine connection with the same name in single instance

Posted on:Monday, December 6th, 2010 by Matt Daum

On one of our projects that we use multiple connections that are defined at run time we recently were generating reports that required us to change a specific connection multiple times in a single run.    We noticed that even though we would define a new connection, it would not throw any errors but just continue to use the originally defined connection.  Here is how we were doing the connections:

$databaseManager = sfContext::getInstance()->getDatabaseManager();
$manager=Doctrine_Manager::getInstance();
$newConn = new sfDoctrineDatabase(array('dsn'=>'XXX','name'=> 'ExampleName'));
$newConn->connect();
$databaseManager->setDatabase('ExampleName',$newConn);

If you called the code above once, it would connect properly to the given DSN. However if you then called it a second time with a new DSN, it would not error and would simply just remain connected to the first DSN. After hunting around a bit it was the problem that Doctrine wasn’t assigning the new connection as the old connection was still open. To get around this we updated the code to the following:

$databaseManager = sfContext::getInstance()->getDatabaseManager();
$manager=Doctrine_Manager::getInstance();

// If the Doctrine Manager has the connection already close it so the new connection can be established
if($manager->contains('ExampleName'))
        $manager->closeConnection($manager->getConnection('ExampleName'));
$newConn = new sfDoctrineDatabase(array('dsn'=>'XXX','name'=> 'ExampleName'));
$newConn->connect();
$databaseManager->setDatabase('ExampleName',$newConn);

You need to first check to see if the Doctrine Manager has the connection, as if you try to get a connection that doesn’t exist, it will throw an exception.

Hope this saves you some time!

ahDoctrineEasyEmbeddedRelationsPlugin and Composite Primary Keys

Posted on:Friday, September 3rd, 2010 by Matt Daum

We”ve been working on a project in which a bunch of the tables had composite primary keys.  Often we wanted to embed these tables in other forms.  ahDoctrineEasyEmbeddedRelationsPlugin is a great plugin for managing embedded forms with Doctrine and Symfony.  It lets you easily let users add multiple new relations and delete previous ones.   However, it didn’t really support composite keys.   We also wanted to be able to expose the primary keys rather than unsetting them from the form(so that the relation is automatically declared).  This was a problem as we needed to be able to select at least one of the primary keys.  Here is an example where we wanted to let the user pick one of the primary keys:

User:
  columns:
     first_name:
        type: string(16)
Product:
   columns:
      name:
         type: string(32)
UserProduct:
   columns:
      user_id:
         type: integer
      product_id:
         type: integer
        price:
          type: decimal
    relations:
       User:
          local: user_id
          foreign: id
       Product:
         local: product_id
         foreign: id

In this case there are Products and Users. A Users can have a products and specify how much each costs. When you are adding products to a user, you would want to be able to select which product you are adding in the embedded form. This is where our problem was, with the plugin as of version 1.4.4 you couldn’t tell it not to unset the primary keys when it embedded the form. We did a checkout of the plugin from SVN and modified it a bit. It now has and additional parameter: newFormUnsetPrimaryKeys . This will make it so the plugin will not unset the primary keys on a new form if you set it to true (it defaults to false).

We also found that the plugin had hard coded a couple of places findOneById which required the primary key to be called `id`. We’ve updated this to use the Doctrine method getIdentifierColumnNames() to get the primary keys.

We only applied to be developers on this script, so are currently waiting on it to be packaged and released, however if you want our updated just do a svn checkout of the plugin and you will be all set!

Let us know if you have any questions on it!

Doctrine Multiple Connections with Symfony Web Debug Toolbar

Posted on:Thursday, July 8th, 2010 by Matt Daum

In April I wrote about using Doctrine with multiple connections for specific table models.  This worked really well, except Symfony’s web debug toolbar would not show any SQL queries that were not defined in the databases.yml file.  This of course made it quite difficult to debug many queries, as the query logs show the queries before the parameters are inserted, for example:

SELECT a.id, a.title FROM posts a WHERE id = ?

The web debug toolbar however shows them with the parameters in place. This makes it a ton quicker to debug as you can see the parameters in place, as well as copy and paste the query straight into the SQL client to see the raw results. After a few months and a project becoming much more complex it was necessary to see the queries. I looked up how sfWebDebugPanelDoctrine gets the queries and found:

  protected function getDoctrineEvents()
  {
    $databaseManager = sfContext::getInstance()->getDatabaseManager();

    $events = array();
    if ($databaseManager)
    {
      foreach ($databaseManager->getNames() as $name)
      {
        $database = $databaseManager->getDatabase($name);
        if ($database instanceof sfDoctrineDatabase && $profiler = $database->getProfiler())
        {
          foreach ($profiler->getQueryExecutionEvents() as $event)
          {
            $events[$event->getSequence()] = $event;
          }
        }
      }
    }

    // sequence events
    ksort($events);

    return $events;
  }

So the sfDatabaseManager would manage all the connections and return which ones to pull queries off of. I looked at it a bit and saw that it has setDatabase which sets the databases it has registered. Since it requires you give it a sfDatabase as a parameter I had to update the way we connect the databases a bit so we could pass them to it. The new version is below:

          $databaseManager = sfContext::getInstance()->getDatabaseManager();
          $newConn=new sfDoctrineDatabase(array('name'=>'NewConnectionName','dsn'=>$databaseString));
          $newConn->connect();
          $databaseManager->setDatabase('NewConnectionName',$newConn);

Now you will now see the queries in the web debug toolbar.

ForexTV.com Goes Live

Posted on:Thursday, May 27th, 2010 by Matt Daum

We’re proud to announce the relaunch of of a partners website: http://www.forextv.com.  The website delivers Forex news and video along with other Forex resources.  The website has been rebuilt on the Symfony framework.

In the coming weeks we will be rolling out many new features, including several social components.  Keep checking back and let us know how we are doing!

Using Doctrine Result Cache With Two Deep Relations

Posted on:Wednesday, April 28th, 2010 by Matt Daum

Recently we’ve been working on a new project that requires caching of both views and database queries. One of the problems I came across I wanted to Result Cache an query I was using for a pager. This caused a couple of problems, one being I needed to be able to clear the cache by its prefix so we would never have a stale cache. Doctrine has a built in deleteByPrefix call for this, however on a pager how do I get it so that it will use a result cache, but still use different indexes for different pages? The following code would not work:

$this->createQuery('sc')
           // A couple of complex joins here, etc
            ->where('sc.video_id = ?',$id)
            ->orderBy('sc.created_at DESC')
            ->useResultCache(true,sfConfig::get('app_comment_cache'),'comment_index');

Well here the problem is everything is being cached as as the ‘comment_index’ cache, so if you passed that query to a pager, and told it to be on the second page, it’d see the ‘comment_index’ cache exists, and use that. A simple way around this is:

  // Query build...
 ->useResultCache(true,sfConfig::get('app_comment_cache'),'comment_index_'.$page);

In this example page is the parameter you are passing the query and the Doctrine pager to tell it what page cache to look at.

Then a very weird problem was occurring, I was getting more queries if I USED the cache than if I didn’t. Very weird. It seemed that one of the joins object did not seem to be getting stored in the cache. The join looked something like this:

  $this->createQuery('sc')
         ->leftJoin('sc.User u')
         ->leftJoin('u.Profile p')
         //.... more joins etc

The problem was the profile object was not getting stored in the result cache and thus causing a query each time it was called from the user object. After much hunting around, a long time in #doctrine, and a few leads from a couple of people, it turns out, by default, Doctrine will only serialize the immediate relation to the main object (in this case ‘sc’). However, you can make it so that it will serialize objects further down the line by overriding the function serializeReferences to return true in the class you want to serialize references from. In my example this is the User class. Since our application will never only need the ‘User’ class to be serialized on a result cache I completely overrode the function and made it always return true

class User extends BaseUser{
  public function serializeReferences($bool=null)
  {
    return true;
  }
}

Of course you can set this on a per object instance via $user->serializeReferences(true). Overriding the method the way I did you need to be careful as you could potentially waste a ton of storage space in your result cache.

Hope this saves someone some head banging and confusion on how using a cache could actually cause more queries if not stored properly.

Doctrine Multiple Connections and Specific Tables

Posted on:Wednesday, April 7th, 2010 by Matt Daum

Recently for a project we had the following situation: Users have their own specific databases, however each database has the same schema. Since there are an unlimited number of databases this prevented us from using databases.yml to define which tables need to go to which databases. The user’s database connection would be defined at run time and we needed only certain tables to be bound to that connection. A bunch of googling turned not much up, mostly talking about defining it in the databases.yml file. After a little bit of searching through some of the Doctrine documentation I came across bindComponent. This allows you to tell a model to use a specific connection. For example:

$newConn = Doctrine_Manager::connection('mysql://myuser:mypassword@somehost/somedatabase','UsersConnection');
$manager=Doctrine_Manager::getInstance();
$manager->bindComponent('SomeModel','UsersConnection');

The above would bind the model ‘SomeModel’ to the connection ‘UsersConnection’. To make this so that it is automatically done each time it is executed a good place to add it is a custom filter.

Also if you plan on storing the user’s database passwords it is a good practice to encrypt them. Look into possibly using the class sfCrypt for this.