debuggable

 
Contact Us
 

How to put Combined Fields Into CakePHP's Model->generateList()

Posted on 9/8/07 by Tim Koschützki

Deprecated post

The authors of this post have marked it as deprecated. This means the information displayed is most likely outdated, inaccurate, boring or a combination of all three.

Policy: We never delete deprecated posts, but they are not listed in our categories or show up in the search anymore.

Comments: You can continue to leave comments on this post, but please consult Google or our search first if you want to get an answer ; ).

Hi folks. Two days ago, someone on #cakephp had the problem of getting combined values out of the Model->generateList() function in CakePHP. I advised him on doing some Set::extract() things, which at the end of the day got rather complicated. As a result, I was conscience-stricken. : ] Here is a much more elegant solution I could come up with.

What is Model->generateList() good for in the first place?

Suppose you want to build a selectTag in cake 1.2 using data from your users table. The options of the select tag shall contain the user IDs, while the captions shall be the first names of the users. It's very easy to do.

In your controller:

$this->set('users', $this->User->generateList(null, null, null,'{n}.User.id', '{n}.User.first_name'));

In your view:

<p><?php echo $form->input('SomeModel.user_id', array('class'=>'txt', 'type' => 'select', 'empty' => '---')); ?></p>

Cake will automagically populate the select field as necessary.

The Problem

Now your project lead comes in and tells you there are 20 users named "Jeff" and 15 named "Peter" in the application, so your select field is not worthy at all. You need to supply both the user's first_name and last_name combined. You got owned! :/ Because generateList does not do that for you.

$this->set('users', $this->User->generateList(null, null, null,'{n}.User.id', '{n}.User.first_name {n}.User.last_name''));

..will not work. Althrough it would be cool. ; )

The Solution

Tinkered a bit around and here is where I got to. Place the following code in your app_model.php file:

function myGenerateList($conditions = null, $order = null, $limit = null, $keyPath = null, $valuePath = null, $separator = '') {
    if ($keyPath == null && $valuePath == null && $this->hasField($this->displayField)) {
      $fields = array($this->primaryKey, $this->displayField);
    } else {
      $fields = null;
    }
    $recursive = $this->recursive;
    $result = $this->findAll($conditions, $fields, $order, $limit);
    $this->recursive = $recursive;

    if(!$result) {
      return false;
    }

    if ($keyPath == null) {
      $keyPath = '{n}.' . $this->name . '.' . $this->primaryKey;
    }

    if ($valuePath == null) {
      $valuePath = '{n}.' . $this->name . '.' . $this->displayField;
    }

    // extract the keys as normal
    $keys = Set::extract($result, $keyPath);
   
   
    $finalVals = array();
   
    // explode the value path by our delimiter
    $tmpVals = explode('#', $valuePath);
   
    $i = 0;
    foreach($tmpVals as $tmpVal) {
      // extract the data for each path sibling
      $vals = Set::extract($result, $tmpVal);
     
      // and insert it at the appropriate position in our final value array
      // use the separator when we need to append the values
      foreach($vals as $key => $value) {
        if(!array_key_exists($key, $finalVals)) {
          $finalVals[$key] = $value;
        } else {
          $finalVals[$key] .= $separator.$value;
        }
      }
      $i++;
    }
       
   
    if (!empty($keys) && !empty($finalVals)) {
      $out = array();
      $return = array_combine($keys, $finalVals);
      return $return;
    }
    return null;
  }

Now, in your controller put in this:

$this->set('users', $users = $this->User->myGenerateList(null, 'User.id ASC', null,'{n}.User.id', '{n}.User.first_name#{n}.User.last_name', ' '));

The Difference To The Normal generateList()

There are five differences:

  • Path siblings are separated by a '#'. Examples: {n}.User.first_name#{n}.User.last_name, {n}.Model.field1#{n}.Model.field2
  • You can use as many path siblings as you like: {n}.User.first_name#{n}.User.last_name#{n}.User.about_me#{n}.User.cool_field
  • The last parameter is the separator between the values outputted. You can use any form of string.
  • You cannot supply 'id ASC' as the ordering, because id might be ambigious. Use Model.id for your ordering needs.
  • You cannot use $groupPath with this. Might implement this later.

Have fun with this and please let me know about issues. : ]

 
&nsbp;

You can skip to the end and add a comment.

[...] Koschuetzki has posted this tutorial today about how to create form fields with combined information from more than one database column. [...]

Beertigger  said on Aug 13, 2007:

Is there any way to pull in a third model's field with this? Something along the lines of: $this->set('foofoo', $foofoo = $this->Model->Model2->Model3->myGenerateList(null, 'Model2.id ASC', null,'{n}.Model2.id', '{n}.Model2.field1#{n}.Model3.field2', ' '));

I've been hunting high and low for a way to get this done. Thanks!

Beertigger

Chris  said on Aug 14, 2007:

This is an immensely handy plugin - thanks Tim!

Tim Koschuetzki said on Aug 14, 2007:

You are most welcome. :]

The good thing is that you can really mix up models as well.

[...] How to put Combined Fields Into CakePHP’s Model->generateList() | PHP Coding Practices - Become... (tags: cakephp generatelist forms selects) [...]

procedury said on Aug 22, 2007:

You are the best! Thanks!

Tim Koschuetzki said on Aug 22, 2007:

You are welcome!

crazyearl  said on Aug 23, 2007:

I've had the same problem and its been driving me crazy! I'm using cake 1.1 however, and received

Fatal error: Class 'Set' not found

When I attempted to implement your code, how could I get this to work with cake 1.1 ?

Beertigger  said on Aug 23, 2007:

I found it worked fine right out of the box in 1.1

Pure genius!

Tim Koschuetzki said on Aug 23, 2007:

Crazyyear: You need the set class from cake version 1.2

I have uploaded it here:

http://php-coding-practices.com/files/set.rar

Put it in to app model and see how far you can get. It may depend on other classes as well. :/

crazyearl  said on Aug 24, 2007:

Excellent! Works beautifully!!

Once you've hit the problem, it seems so obvious that you would need two or more fields in generateList(). I had the exact same problem, where I needed name and surname...

Awesome stuff!!

Tim Koschuetzki said on Aug 24, 2007:

Glad I could help you. :]

nate said on Aug 24, 2007:

See here:
https://trac.cakephp.org/changeset/5512

And here:
https://trac.cakephp.org/changeset/5518

Will you people please quit with your ugly hacks now?

Tim Koschuetzki said on Aug 25, 2007:

Hey Nate, yeah Felix told me about this and I will write about it later.

My approach is just a method for those who do not check out the latest revision (for whatever reason) and thus are bound to a specific version of cake, that does not support it (like me).

My agency for example does that and therefore I had to find a hack to accomplish this. But then again it's not so ugly, because it makes use of class inheritance, so..

crazyearl  said on Aug 25, 2007:

I thought it was a rather nice bit of code myself...

My problem was that my app is on cakephp 1.1 and converting the whole app to 1.2 wasn't feasible.

This solution is rather elegant imho...

nate said on Aug 25, 2007:

I'll give you a hint as to why you're wrong. Part of the inelegance is in the inflexibility of the formatting. It's not a good general solution.

Chris Domigan  said on Aug 26, 2007:

"Will you people please quit with your ugly hacks now?"
Not sure if you meant that to sound hostile, Nate. "You people" is the Cake community and I for one am thankful that Tim has written this. For those on earlier revisions this is extremely helpful.

nate said on Aug 26, 2007:

@Chris: First of all, when you quote someone, you need to understand that they decide on the meaning of what it is they said, not you.

Secondly, what I meant in this context by "you people" were the certain elements within the CakePHP community who think they know what they're talking about, but don't.

My biggest problem over the past few months has been these people, who often take poorly-executed, mis-informed, or just plain wrong ideas and pass them off as fact or "best practice". This is downright harmful to the community, and given that myself and the other core developers / contributors make ourselves available to answer questions through a variety of media (email, IRC, mailing list, etc.) there is absolutely no excuse for it.

When committing code to the CakePHP core, we always do our best to make it as instructive as possible. It's not always perfect on the first pass (we do PoC's too), but generally speaking it represents the high coding standards to which we as a development team adhere. We don't write limited-use solutions, we don't bloat method APIs, we don't try to cram two (or three) functions into one.

Personally, I've been quite disappointed by what I've seen here, especially for a site that claims to promote good coding practices. Read, educate yourself, then act like you know what you're talking about.

http://wiki.java.net/bin/view/People/SmellsToRefactorings

Tim Koschuetzki said on Aug 26, 2007:

Nate,

Thanks for you comments. I don't mind criticism, but I would appriciate it if you could show a better solution to this particular problem for people who are stuck with an old cake version.

Otherwise your comments are neither helpful to me who is trying to improve, nor other readers who try to evaluate the solution presented here.

Waffeljaw  said on Aug 27, 2007:

I agree with Tim, plus it makes you look like you love smoking man-pole.

Not everyone is as experienced as you claim to be, so if you feel you know better why not help us out instead of criticising everybody...

nate said on Aug 27, 2007:

Better solution: for older 1.1 versions, copy the 1.2 Set class to vendors, and override Model::generateList() with the code from 1.2. For newer 1.1 installs, rename the new Set class to Set2, and copy to vendors. Override Model::generateList() as above.

@Waffeljaw: I wrote code that addresses the problem (linked above), which you can read and learn from.

Tim Koschuetzki said on Aug 27, 2007:

Alright , thanks Nate. : ]

Yevgeny Tomenko (aka SkieDr) said on Sep 10, 2007:

I dislike your solution. You try to solve very partial task and make it not universal at all.
Try to use Autofield behavior.

In this case you need not to create new code for generateList. You just describe additional columns in your model and will use it.

rp  said on Sep 11, 2007:

great !! thanks a lot !

Tim Koschuetzki said on Sep 11, 2007:

@Yevgeny: By the time I wrote this article your AutofieldBehavior was not available (or I did not hear of it), so..

Nate already proposed a better solution. This will stay here for people who do not care much about behaviors or the best implementation, but need a quick fix because they are in a time crunch.

I already made another better solution myself, which I may write about soon.

Yevgeny Tomenko (aka SkieDr) said on Sep 11, 2007:

Did you check my solution that i post i my blog?
http://cakeexplorer.wordpress.com

Tim Koschuetzki said on Sep 13, 2007:

Yup, I did. That's what I was referring to in my comment above. : ]

roryy  said on Oct 04, 2007:

nate, when i rename set to set2 and use the generelist from 1.2 then I get the message that set cannot be found

John Pierce said on Oct 26, 2007:

For anyone who is confused about how to do this with generateList, the following example shows how to combine 3 fields. In this example the resulting data is formated "surname, firstname - company' - this is for version 1.2

$customers = $this->Customer->generateList(null,'Customer.surname',null,"{n}.Customer.id", array('{0}, {1} - {2}', '{n}.Customer.surname', '{n}.Customer.firstname', '{n}.Customer.company'));

flauebrt  said on Dec 18, 2007:

great job!!!

flauebrt  said on Dec 18, 2007:

great job!!

naid  said on Mar 07, 2008:

kudos to Tim Koschuetzki

i've been searching for this functionality and i'm new to cakePHP which makes it harder for me to accomplish everything i wanted to do in cake.

cfbjr said on Mar 09, 2008:

Thanks Tim,

Don't let these core guys get you down. Many of us out here need a quick hack here and there. We'll go back and refactor in due time. In the meantime the client is screaming for something that seems so simple to just get done.
When it not right there in the framework, enter the hack to the rescue. I appreciate guys like you and the help you provide to those of us "in the trenches" every day.

saying  said on Mar 22, 2008:

good lord. cakePHP is so great, it's too bad nate consistently responds to the community in a hostile, nasty manner.

Tim Koschuetzki said on Mar 24, 2008:

Oh he does?

Jim Keller said on May 20, 2008:

This post was partially responsible for my blog entry comparing CakePHP to FUSE, another MVC framework for PHP. I recently took over a Cake project, and while Cake is great and I'm glad the project was started in an MVC framework, there are a few things I think are easier in FUSE (like generating lists). Have a look:

http://jimkeller.blogspot.com/2008/05/cakephp-vs-fuse-mvc-framework-for-php.html

E. Wayne Madison  said on Jun 26, 2008:

In CakePHP 1.2.0.XXXX generateList is "Deprecated".

I use tables and the data that it contains to control data input.

For instance I have table which contain, (zone)state data, country data, etc., etc. etc.

I use the table data to drive select option lists.

In the form the User selects (Mr. Mrs. Ms.)
The User Table will contain (1,2, or 3) or 33 for Michigan or 223 for the United States.

I need to be able to produce a report which prints out not (1,2 OR 3) but
Mr. Mrs. or Ms.

Thanks for an elegant but deprecated solution.

We need a solid solution, a portable solution.

GenerateList is deprecated.

Wayne

Tim Koschützki said on Jun 27, 2008:

E. Wayne Madison: Hence it is marked as deprecated. Also look at the date when this post was published.

We will work on a new solution article.

Streamline  said on Jan 29, 2009:

Gr8 solution.. Helped me a lot

Ben  said on Feb 27, 2009:
Tim Koschützki said on Feb 27, 2009:

Thanks Ben

Streamline: Yeah check out the article Ben provided. This article here is far outdated.

This post is too old. We do not allow comments here anymore in order to fight spam. If you have real feedback or questions for the post, please contact us.