Tl;Dr: How to create one array; from two tables; with hasMany and belongsTo associations; to foreach through; with CakePHP; optimally.
EDIT: From(http://book.cakephp.org/view/872/Joining-tables) This is what I'm trying to learn how to do although it says "hasOne" and not hasMany....
"In CakePHP some associations (belongsTo and hasOne) performs automatic joins to retrieve data, so you can issue queries to retrieve models based on data in the related one."
I'm trying to foreach through an array that will ideally contain data from two tables that have a hasMany and belongsTo relationship. I know that CakePHP has functionality built in to make this very simple and would like to use it:
trips hasMany legs
legs belongsTo trips
So, I want to make an array containing data from both tables so I can easily display the data in the view. Notice there is nothing about joining in the array that contains find parameters. I think CakePHP joins tables when I create associations; I'm just not sure how to access those associations when creating an array.
(I've spent hours on this already and couldn't find an example of how to do it on the web or in books so thanks for your help). I could hack my way around this (by creating a separate array and having a much more convoluted view file) but I'm just starting to learn to program and would like to do it somewhat optimally and use CakePHP's functionality.
Thanks in advance for your help! Am I not right that with these associations (belongsTo and hasMany) I don't have to explicitly declare the joins in the controller/find()? I might, for now, try and manually make the joins to see what happens.
Existing code (this works fine):
trips_controllers:
function view() {
if(empty($this->data)){
$this->Session->setFlash('You forgot to put stuff in the form fields!');
$this->redirect(array('action'=>'index'));
}
$price=$this->data['Trip']['price'];
$origin=$this->data['Trip']['origin_airport'];
$this->set('trips', $this->Trip->find('all', array('conditions'=>array('Trip.price <='=>$price, 'Trip.origin_airport'=>$origin), 'limit'=>30, 'recursive=>2')));
view:
<th>Trip ID</th>
<th>Price</th>
<th>Origin</th
</tr>
<? foreach($trips as $trip):?>
<tr>
<td><?=$trip['Trip']['trip_id'];?></td>
<td><?=$html->link($trip['Trip']['url']); ?>
<td><?=$trip['Trip']['origin_airport'];?></td>
<td><?=$trip['Trip']['price'];?></td>
</tr>
<? endforeach;?>
Thanks again!
If Trip hasMany Legs, you should be able to loop through the data like so:
foreach ($trips as $trip) {
echo $trip['Trip']['id'];
…
foreach ($trip['Leg'] as $leg) {
echo $leg['id'];
…
}
}
debug($trips) is your friend. The default Cake data array layout is very sensible. Try to get used to it, there should be very little need to modify the structure in 99.99% of applications. On the contrary, there's a strong case to be made for keeping it consistent everywhere.
Related
Here is the issue I am facing all the time since I started to learn CakePHP 3
What is this concept of entity a real world example would help alot.
public function add()
{
// why do we have to create new entity / what is the role of entity here.
$comment = $this->Comments->newEntity();
if ($this->request->is('post','put')) {
// why do we have to use this line after posting / what is the role of this line.
$comment = $this->Comments->patchEntity($comment,$this->request->data);
if ($this->Comments->save($comment)) {
$this->Flash->success('comment submitted successfully.');
} else {
$this->Flash->error('Sorry, comment could not be updated.');
}
}
return $this->redirect($this->referer());
}
Let me open the book for you:
While Table Objects represent and provide access to a collection of
objects, entities represent individual rows or domain objects in your
application. Entities contain persistent properties and methods to
manipulate and access the data they contain.
-
why do we have to create new entity / what is the role of entity here.
Almost everything, if not all, in Cake3 works with entities, what an entity is is explained above. You need to create a new entity so that the FormHelper can work with it, AFAIR it can still work with an array if configured to do so as well but the entity should be used.
The reason entities exist is to abstract the data. Some people think entities are the representation of a DB row - that's wrong. As the book says, they can be a row but don't have to represent a row because the 3.0 ORM can work with other resources as well. In theory you can have a CSV data source that returns an entity per line.
I suggest you to read the entity code in the CakePHP core to get a deeper understanding of what else entities provide, just saying they're "just" a set of properties is to short thought.
why do we have to use this line after posting / what is the role of this line.
The post data is merged into the previously created entity, that's it. Use the API if you have basic questions like that. See the API entry for patchEntity().
In simple word, Entity is a set of one record of table and their relational table, on that you can perform operation without touch of database and encapsulate property of entity (fields of table) as you want.
Advantages of Entity.
Modifying result sets outside of the database (for formatting or otherwise)
Needing to represent both the table and row in the same class.
Data validation was a fucking nightmare.
Inconsistent API in terms of both how we handled things internally as well as what (and how) we returned stuff.
Other random stuff as you want.
You can do run-time modification of result sets. Just add a method to your entity to return results in the way you want. This also means you can use composition for managing entities (yaya traits)
Validation is beautiful. We can validate data before it gets into an object and then validate the object state in a separate step.
It is easier for developers to understand what they are dealing with. You either have an object or an array of objects. An object can be linked to data which can also include other objects, but you no longer have to guess at what the array key will be, nor whether its nested funkily.
We can iterate on the interface for tables and entities separately. We couldn't easily change internals for the old Model class because of the implications on both, whereas now we can (in theory) change one without mucking about in the other.
It looks prettier simple.
Try this:
if ($this->request->is('post','put')) {
$data = $this->request->getData();
$comment = $this->Comments->newEntity();
$comment = $this->Comments->patchEntity($comment, $data);
$status = $this->Comments->save($comment);
if ($status) {
$this->Flash->success('comment submitted successfully.');
} else {
$this->Flash->error('Sorry, comment could not be updated.');
}
}
return $this->redirect($this->referer());
}
My advice is never use Post and Put in the same function. Just for good pratice. Put works fine when you make a update using id like a parameter.
I continue building my CakePHP application. Now I've created all the database schema in MySQL, run some "cake bake all" and I have lots of models, views and controllers, and I'm gonna personalize them.
In many of my models I have this fields:
company_id
created_by
modified_by
As you can understand, the first field is the id of the owner of the "row", created_by is the id of who created the row and modified_by the latest person who updated it.
I know I can create a beforeSave filter in the model and update all the data (I suppose that I can know if I'm creating or updating a row, isn't it?), but just now I have 15 different models and I hope the app will grow more, so it's a lot of repetitive code to write. And, it breaks the DRY principle.
And my question is: Is there any "elegant" way to solve this problem? Maybe creating a class extending AppModel with a beforeSave filter for updating the fields, and that all my models inherit from the new Model class instead of AppModel?
Thanks for your help!
Make it a behaviour and load it for models that need that functionality.
See http://book.cakephp.org/2.0/en/models/behaviors.html#creating-behaviors
I think the most appropriate way is to create Behaviors.
You can set up the beforeSave callback in the behavior like what you have in your model.
Here is the link to the documentation http://book.cakephp.org/2.0/en/models/behaviors.html#behavior-callbacks
You can also check as example dereuromark's WhoDidItBehavior.
I know this question has been asked before, but I don't know if it's been asked specifically about CakePHP 2.0.x. I haven't been able to find any information about whether or not having users be a part of multiple groups is now possible with the ACL component. I never used the ACL component with CakePHP 1.3.x because it confused me. If it's better now, though, I'd like to use it so I don't re-invent the wheel by rolling my own. Any help would be appreciated.
I haven't tested it or used it like this before, but I can imagine one way:
Have a User model and a Group model, and User HATBM Group as Shaz Amjad notes.
At the point you're doing your access control, fetch a list of all Groups that User belongs to (probably using bindModel)..
Then, something like:
$permits = array();
foreach ($thisUsersGroups as $group) {
$permits[] = $this->Acl->check($group, 'myclass', 'update')
}
If $permits contains at least one true, they should be permitted.
There might well be a better or more automagic way of doing it, but I don't see what that shouldn't work in principle.
I want to store the form entries into a table that will store what my users searched for, then actually execute the code on the form results.
I tried doing:
$this->Search->create();
$this->Search->save($this->data);
But I don't think it liked me using a model that doesn't belong to that controller (my guess this doesn't follow convention and fully utilize CakePHP).
The other idea I had was to create a new searches_controller, then run $this->data through the above code and redirect back to the Trips controller but it seems like that's not the optimal way of doing it either.
Any thoughts on how best to do this?
In procedural PHP, I would just do an INSERT query before.
Thanks for the help!
I think that having Model which doesn't belong to the specific controller is not so bad idea.
Actually you can put this logic in the beforeFilter() of the Trips model like that:
class Trip extends AppModel {
//....
function beforeFind(){
if($this->data){
$search = ClassRegistry::init('Search'); //adding instance of Search model
$search->save($this->data); //save the data
//Do extra things if needed
}
}
//....
}
This way you don't have "alien" model in your controllers, and utilise the recommendation "Fat Models, Skinny controllers" :)
I have a form which looks like this:
Delete
[Publisher One ] []
[Publisher Two ] []
[Publisher Three] []
Add[ ]
So basically, every Publisher appears on the page in its own field. I can modify any of the Publisher names, delete any of the Publishers, or add a new publisher, all on one form, simply by saveAll-ing the form. I know that this will not hold up under 10,000 rows, but I am using CakePHP to remake an existing tool, and I am sure there will be a manageable number of rows.
The problem is that on first load, I have to pre-populate the fields. Now, from the CakePHP book, I am supposed to create the form with Model.n.field. However, the data I pull using a $this->Model->find('all') is in $data[n][Model][field] form. Am I going to have to mangle the data myself to get it in $data[Model][n][field] form, or is there an easy way to do that from within the find command, or perhaps a helper function to turn it from one into the other?
The Set class might be able to do the reprocessing for you - see CakeBook and an example. Also other Set class methods might be useful for you.
The difficulties in moving data in different forms around in Cake isn't the smoothest, I agree. But why don't you just do this:
pull your data in the controller. $data is an array with the nth record as the first index; then Modelname, then fieldname (sorry for the confusing wording -- this is just how you've done it above).
in your view:
foreach( $data as $publisher-id => $record )
{
echo '<tr><td>' . $record['Model']['publisher_name'] . '</td>';
echo '<td>' . $form->input( 'Model.publisher_id', array( 'value' => $model['Modelname']['publisher_id']) ) . '</td></tr>';
}
So I don't really think you need to mangle the data, or the arrays returned from the find operation. Try to just think about how you're going to output it, and you have to be a little clever about it.