Icon

Following the Footsteps of Heros, Never Lead to the Straight and Grey Roads. (Oh, Sleeper)

Frodosghost

Using MySQL Stored Procedures in Cakephp

I am new to this topic in many ways. For ages I have prefered to do the database work in Cakephp – or being honest, I have been afraid to touch MySQL, being that close to the data seemed like a stupid idea. Especially because I wasn’t bold enough to go playing with things.

That being said, I have been needing to make my data transfers faster for one project I am currently working on (under wraps at this time, more news soon I’m sure). Every second query was returning nothing. Not good. So the first thing was to speed up the queries. Read the rest of this entry »

Creating and Destroying Associations on the Fly

Strange things happen at strange times. I guess otherwise they wouldn’t be strange, but regular.

For the past few days I have been playing mental games with a particular find() in CakePHP. Basically I wanted to be able to get ALL of some data (Category), and the associated data (Credit) – if the Credits existed. My queries were returning ALL of the Categories in one query. But when I added in the Credit association, it only returned the Categories where the Credits existed.

This was proving to be difficult, so after a lot of stuffing about my @ghostpsalm came to the rescue. Well, he started me on the right path.

$cat = $this->Category->read(array('lft','rght'), $parent_id);

This found the lft and rght figures for a particular Category. This was important because it meant the following find() had no association layers – basically that the next find() would not be influenced by outside persuasions (Cake Model Setups).

$subCat = $this->Category->find('all', array(
 'conditions' => array(
 'Category.lft >' => $cat['Category']['lft'],
 'Category.rght <' => $cat['Category']['rght'],
 ),
 'contain' => array('Credit'),
));

This above find found everything we were looking for. All the Categories and All the Credits associated with them. So I could have used a php loop to find the One Credit we needed for the view – but that seems like a waste of retreived data to me. So I needed a way to be satisified, a way that the data being retreved would be the data that was used.

First step was to put in an extra condition. That the Credit.parent_id is that of the Users Site – with this join we would have All the Categories with ONLY the Credits that had been made. But, it wouldn’t work – that simple condition failed because the Category hasMany Credits, and the hasMany association does not LEFT JOIN in MySQL – therefore Credit.parent_id is an Unknown Column.

So what do you do when the associations fail? Break them of course. And how do you break Associations? By Creating and Destroying Associations on the Fly. It sounded like what I was looking for, and it also sounds like a cool name of a song. So I stuck with it, so thanks to ProLoser on the #cakephp irc channel.

So between the two queries above I added a simple bindModel:

$this->Category->bindModel(array(
 'hasMany' => array(
 'Credit' => array(
 'conditions' => array('Credit.parent_id' => $link['Link']['id'])
 )
 )
));

So what is really happening here is to ‘change the way an association is defined in order to sort or filter associated data’. I basically restate the hasMany association with a more exact conditions, and it applies it to the next find() – which my query with the lft and rght data in it.

It was the query I was looking for. This gives me every Category, with the related Credits by that user – weather the Credits exist or not, I still get the Categories.

So cake’s bindModel to the rescue. It was a function of cake I hadn’t used before, and quiet frankly one I never thought I’d have the use for. Truthfully, I was a little scared of it.

Finding data by the bindModel is not so hard, in fact it was rather easy. I was finding it a difficult problem to explain, and it was only after @ghostpsalm gave me a great start that I could see the forrest through the trees, as it were. It once again shows to me how cool CakePHP can be, once you get deep enough in that it needs to give you the only way out.

Cakephp : Form Options List Variable

So, it seems that cake is more awesome than me. And the Temporary hack I have below is over-ruled by cakephp. Thanks to Matt @ pseudocoder what I should have looked for is find $this->Model->(‘list’) and it would bypass the ‘foreach’ loop. Excellent.

The CakePHP Way

Many thanks to Matt in the comment below, he pointed me to the real Cake way to do this. So, ignore my tmporary hack. It’s easy.

function add(){
  $UserOutposts = $this->Arbiter->find('list', array(
    'conditions' => array('(Arbiter.status & ? = ?)' => array(Arbiter::USER, Arbiter::USER)),
    'contain' => false,
    'fields' => array('Arbiter.name')
  ));

  $this->set('outposts', $UserOutposts);
}

The Old Way (Temp Hack Over-ruled)

One of the reasons I love CakePHP is its ease of use. Once you speak its language everything flows together like a river.

Sometimes though I have fallen into hacking cake to make it do what I’d like. It used to be because I didn’t know it enough. Sometimes now it is because I need specifics in my views. And sometimes it was because I wanted a html options box, but cake didn’t have the data in the format I wanted it.

So this is my hack I did today.

It seems to be the one thing with the FormHelper that got me a little stuck. I was trying to set up a options box. The way we have been doing this is looping a ‘real box’ out in the view – using the cake returned arrays and then using ‘foreach’ to loop out the data we needed.

I wasn’t really a fan of that idea, so I had a go at hacking together a mid point. I’m sure someone will know a much better ‘cake’ way of doing this – and if you do, please leave a comment. But here is what I am calling a temporary hack (basically it make my cake mind feel like I haven’t wronged cakephp).

Temporary Hack : FormHelper Options Box in view

I am going to send the view a $var with a key-value paired array. So I find a list of all the Arbiters (a master list). That have a status of USER (see Mark Story’s Bitmasks). The ‘contain’ will return just the Arbiter data, nothing related. And return just the fields I need for the loop.

Then I just use a ‘foreach’ loop to push the found fields into the $var array I had setup. And using $this->set it goes to the view.

//Outposts Controller
function add(){
    $UserOutposts = $this->Arbiter->find('all', array(
      'conditions' => array('(Arbiter.status & ? = ?)' => array(Arbiter::USER, Arbiter::USER)),
      'contain' => false,
      'fields' => array('id','name')
    ));

    $opList = array();
    foreach($UserOutposts as $UO){
      $opList[$UO['Arbiter']['id']] = $UO['Arbiter']['name'];
    };

    $this->set('outposts', $things);
  }

That way in the view:

// views/outposts/add.ctp
echo $form->input('field', array('options' => $outposts, 'empty' => '(choose one)'));

Just put the sent var into the options and your good to go with an Options box, without having to build or loop it in the view.

As I said, I reckon this is a hack:

  • a) Because its in the controller and the rule is Fat Models -> Skinny Controllers
  • b) It still doesn’t seem as easy as cake should be. I have a rule with cakephp, if its taking too long it means I’m doing it wrong.

So I am waiting for someone to correct me. But until then I do have this temporary hack kicking out the jams.