My model has many relationships to other tables/models, if i use contain parameter, for example to order by field in results of one model, all other tables in query results are missing, and must manually written in contain parameter, how can i get all related models data without manually writing it?
$data = $this->Cv->find('first',
array(
'contain' => array('Cv_experience' => array('order' => 'Cv_experience.start_year desc'))));
It sounds like you've been relying on the Model's recursive property. Once you use 'contain', those models that used to come in automatically no longer do, right?
The answer is, you should be using recursive OR contain, not both. If you're using contain, you should set recursive to OFF ($recursive = -1;).
As far as which to use, it's HIGHLY recommended to not use recursve at all. Set it to -1 in your AppModel, and use Containable Behavior any time you want additional data. Recursive is bad practice for anything but a simple blog app, and I believe is even being completely removed in Cake 3.
Note on ordering: You cannot order by a contained model. 'Contain' creates multiple queries, so - if you want to do what you're trying, you'll need to use JOINs.
Related
I am writing a datasource to access a external web service, this works fine as expected, but after the web service has run, I get the following error:
CakeException: Cannot use modParams with indexes that do not exist.
in /project/lib/Cake/Utility/ObjectCollection.php on line 128
After a little googling, I found this similar question:
What does this error actually mean?
Unfortunately, this didn't help me solve my answer.
I understand that the error is expecting me to remove (or add extra) params, but I don't understand where I am setting these params to edit them.
(answering my own question to help others with the same issue!)
The Issue
The issue I had that in my AppModel, I have set public $actsAs = array('Containable');, I do this so all my models attach the containable behaviour, (I then set recursive to -1 by default for all models, and specify the recursion as required (with the call).
So in this case, my datasource model (which I use to interact with the datasource), had the containable behaviour attached to it (and so CakePHP thought this was correct and processed it as per normal model (which is not the case as this has no relations or database table).
The solution
The solution was to simply add public $actsAs = false; in my datasource model (which removed the inherited containable behaviour).
;)
After a few times adding Containable Behavior to my various model classes, I have a good mind to simply chuck the line into AppModel instead and thus make every model Containable. Which then makes me wonder, is there any situation where it is not desirable or counterproductive for a particular model to have Containable Behavior?
I would say too few to be worried about. I put containable in App Model:
class AppModel extends Model {
public $recursive = -1;
public $actsAs = array('Containable');
}
Containable overrides recursive anyway, so you don't really need to set recursive to -1, but I do just for clarity. Always using containable forces you into the best practice of always using only what you want/need. For small apps, it's not the end of the world to just use recursive and ignore containable, but it's still not best practice.
So, I guess the only argument for using recursive instead of containable would be that in small apps, you save yourself a tiny amount development time, and it won't really noticeably affect performance. I'd definitely go with using containable by default, and removing it where you deem it overkill, rather than the other way around.
Containable can be dangerous b/c Cake acts in an extremely inefficient manner in order to get the nested results.
This site explains it well...
http://www.endyourif.com/cakephp-containable-statement-pitfalls/
Basically though the nice array you are getting back is the results of many different queries while your best performance may come from using a single query with joins.
The convenience of containable is undeniable though.
I have models with the the following relations, defining a situation where users can belong to many groups, and multiple groups can be granted access to a project.
User HABTM Group HABTM Project
I would like to set things up so that any find() done on the Project model will only return results to which the current user has access, based on her group membership.
My first thought is to use the beforeFind() callback to modify the query. However, the two-level association has me stumped. I solved a similar problem (see this question) by rebinding models. However, that was for a custom find method—I don't think that approach will work in a general situation like this where I need to modify arbitrary queries.
Using afterFind() to filter results isn't a good idea because it will confuse pagination (for example) when it doesn't return the right number of records.
Finally, I have a nagging suspicion that I'm trying to re-invent the wheel. The access control I've seen in CakePHP (e.g. Cake ACLs) has been at the controller/action level rather than at the model/record level, but I feel like this should be a solved problem.
Edit: I eventually decided that this was over-complicated and just added a getAccessibleByUser($id) method to my Project model. However, I'm still curious whether it's possible to globally add this kind of restriction to all find() operations. It seems like exactly the sort of thing you'd want to do in beforeFind(), and I suspect (as DavidYell suggests below) that the answer may lie with the Containable behavior.
You should look at the Containable behaviour. If you are using CakePHP 2.x then it comes in the box.
This behaviour allows you to manage the model relations and the data which is returned by them, along with allowing you to pass conditions, such as a group_id into your contain.
http://book.cakephp.org/2.0/en/core-libraries/behaviors/containable.html
right now I have controllers/actions that do standard retrieval of model/associated model data. My actions currently just pass the variables to the views to pick and choose which values to display to the user via HTML.
I want to extend and reuse these functions for the case where a mobile device is making a call to grab the JSON format version of the data. I am using Router:parseExtensions("json") and that all works fine.
My main question is how to handle data size. Right now even a User model has many, many associated models and recursive relationships. As of now I am not using contain to cut out the unnecessary data before I pass it to the view, b/c the view will take the elements it wants and it won't affect the HTML size.
But for my JSON views, I just format it and return the whole thing, which makes it extremely large. My current thought process is I just need to case it to use containable in the case of JSON, but I was hoping there was a more elegant solution? Or is this the cakey way to do it?
Thanks!
Actually, using containable and fine tuning your query is a very elegant solution. Even if your view does not use the actual data, you put unnecessary load on your database by adding data / joins you don't need.
Try and limit your query and relations by using both Containable and fine tuning the relationships in your models and paginator calls.
It is also recommended that you move most of your find calls to the model layer. They will be re-usable, easier to test and overall more Cake-ish.
Trips hasMany Legs
Airports has no associations
How can I find the cheapest trip for each destination airport using CakePHP?
Right now, the only thing I can think of to do is to foreach through an array of airports. This would require hundreds of queries to the database (which I think is not the fastest way of doing it).
function getCheapestTrip($origin){
$airports=$this->Airport->getAirports();
foreach($airports as $airport):
$cheapest_flights=$this->Trip->find('first',
array(
'conditions'=>array('Leg.origin'=>$origin, 'MIN(Trip.price) as price'),
'fields'=>array('Trip.origin','price','Leg.destination','Leg.depart','Leg.arrive'),
'recursive'=>2,
));
endforeach;
}
}
Also, I think that this data type stuff should be in the model per CakePHP conventions (Fat models, skinny controllers). I read that to call a different model's function such as getAirports I can use loadModel but I found that in CakePHP's controller method section. How should one get another model's data/model function into anothers?
Thanks!
The answer to your second question, "How to load a model within another model?" can be found here.
If you're looking for a better algorithm rigth now I do not have the solution.
Mine is a design solution: basically you should add a field to your destination airport which will be updated every time you add a new flight so you have your information directly in your destination record.
This stands if I have understood your problem. I'm not english so I'm not familiar with the semantic of "leg" associated to a trip (to me it's a body part)
The problem you're solving is the Traveling Salesman Problem: http://en.wikipedia.org/wiki/Travelling_salesman_problem
From what I've read on how google maps does it, you'll want to precompute your most common routes and connections. Keep that precomputed info in a cheap cache (memcache prolly). Basically, you won't be able to recalculate each time, so calc a few common ones and build a precomputed cache.
WRT the algorithm, some google searching will be your friend for tips and tricks. This problem has been solved many times (none are exactly computationally efficient, which is why you should precompute and cache).