CakePHP custom data object with grouping and nested arrays - arrays

I'm working on an app with extensive database relationships and I need to return a specific data object. I'm having trouble with it. Here's what I need it to look like:
AccommodationGroup =>
array(
['name']=>'Group1',
['AccommodationRoom']=> array(
[0]=> array(
['id']=>1,
['RoomIdentifier']=>'Cabin001',
),
[1]=> array(
['id']=>2,
['RoomIdentifier']=>'Cabin002'
)
)
)
AccommodationRoom is related to camp by camp_id. AccommodationGroup is related to AccommodationRoom by accommodation_group_id.
As you can see in the data object example, I need to retrieve all the groups for a particular camp with the rooms of each group as a nested array of the group, and all this restricted to a particular camp.
I've tried to get at it by selecting all the applicable groups using findAllByCampId() which, of course, doesn't work (knew it wouldn't but tried it anyway). Another table, AccommodationRoomsCamp is a transitional table between AccommodationRooms and Camps. One way to do it would be:
$this->AccommodationRoomsCamp->findAllByCampId($id, array('group'=>'AccommodationRoomsCamp.accommodation_group_id'))
but accommodation_group_id is not stored in AccommodationRoomsCamp because it is already being stored in the AccommodationRoom table. I think that I need to do more than one operation, but I'm baffled. Little bit of a newb. Ideas?

Have a little bit of a hard time following how your tables are related - but I think you can solve your problem using recursive finds. Something like this in your controller:
$this->AccomodationRoomsCamp->recursive = 2;
$this->AccomodationRoomsCamp->find('all, array('conditions' => array(bla bla bla bla
See http://book.cakephp.org/view/1063/recursive

Related

CakePHP - Passing an array of record IDs to paginate - is it possible?

Due to having to having to import data from an old non cake app and oddly built database table I need to pass paginate an array of records it is allowed to display - is this possible?
Normally I would reorganise the data into proper relationships etc but due to time scales etc this is not possible.
To give you more info - in my users table I have a field that contains a list of ID's that relate to documents they are allowed to view. The field will contain something like
123,23,45,56,765,122,11.9,71,25
Each ID refers to a document the documents model. I know that normally you would create proper ACOs and AROs and let the ACL/Auth componant handle which users can access what but this isnt an option this time around. So I thought if I could do it via paginate/find it might be an option?
Any help would be really appreciated.
Thanks in advance
You wouldn't pass it a list of IDs, you'd use the IDs in your paginate conditions - something like below.
(Code written off top of my head, so pardon any syntax errors...etc. It should give you the right idea/path at least):
//Controller
$this->loadModel('User');
$this->loadModel('Document');
$user = $this->User->findById($userId);
$documentIds = explode(',', $user['User']['doc_ids']);
$this->paginate = array(
'conditions' => array(
'id' => $documentIds
)
));
$documents = $this->paginate('Document');
When you pass an array as a condtion (eg. 'id'=>$arrayOfIds), it uses MySQLs "IN" - something like:
... WHERE id IN (45, 92, 173)

CakePHP 2.1 - How to properly use DISTINCT in find()

I have a question which is driving me crazy and I have to admit I am not that experienced in CakePHP. As mentioned in this question,
Using DISTINCT in a CakePHP find function, using DISTINCT this way:
$this->Model->find('all', array('fields'=>'DISTINCT field_name'));
does not return DISTINCT values, instead it returns all rows. In fact, the DISTINCT here is completely pointless because, for some reason , CakePHP adds TableName.idin the SQL query (why?? can I remove the id reference??), effectively returning every DISTINCT primary key (=all rows=unhelpful).
So, I still want to return the DISTINCT values of a particular field_name column. Can I not do it using just the find('all') or find('list') function? Is it really that the proper way of doing it using this Set::extract() function described in the link above? That appears to be a overly indirect solution by CakePHP, normally Cake make my life easier. :-) What is the proper way of using find and DISTINCT together? Maybe DISTINCT doesn't work for find()?
Looking at the CookBook, they say: "A quick example of doing a DISTINCT query. You can use other operators, such as MIN(), MAX(), etc., in a similar fashion:"
<?php
array(
'fields' => array('DISTINCT (User.name) AS my_column_name'),
'order' = >array('User.id DESC')
)
?>
Source: http://book.cakephp.org/2.0/en/models/retrieving-your-data.html
This indicates that DISTINCT should be possible to use, but what is what here? Does (User.name) correspond to the field_name I want DISTINCT for or is my_column_name my field_name?
Finally, has any of this changed when migrating from CakePHP 1.x to CakePHP 2.x? Ie are the answers for CakePHP 1.x seen on Stackoverflow still relevant?
Thanks in advance!
Yes, the second snippet is the correct way to do a SELECT DISTINCT in CakePHP 2.x. User.name corresponds to the field name, in this case to the field name in the users table. my_column_name is an (optional) alias for the field name in the result set, i.e. instead of name the field will be named my_column_name in the result set.
The correct way of using distinct in find with condition is:
$this->Model->find('all', array('fields' => array('DISTINCT Model.database_fieldname'),'conditions' => array('Model.database_fieldname' =>$val )));
where $val contains some value which you want to pass in the query.
Have a Nice day
For CakePHP 3.X
To select distinct fields, you can use the distinct() method:
// Results in SELECT DISTINCT country FROM a table...
$query = $articles->find();
$query->select(['country'])
->distinct(['country']);
CookBook Documentation

CakePHP Relationships over more then one Model/Table

I'm new to learning CakePHP. I did the Blog Tutorial and am now trying to add Categories for Posts. I created Category and SubCategory Models and MySQL DB Tables and I related the Models as follows:
Post -> "belongsTo" -> SubCategory -> "belongsTo" -> Category
Post -> Subcategory is working fine and I can resolve the SubCategory Name in the View via:
php echo $post['SubCategory']['name'];
Now: How do I go one step further in the relation and get the Category Name for a Post in the Post View (via the SubCategory)? The following obviously gives me the Category ID, but not it's name:
php echo $post['SubCategory']['category_id'];
Thanks a lot!
Check out the recursive parameter for a model and go for a step 2 (2 levels of depth). This would allow you to use the category if your definition is correct. Keep in mind that it would need to fetch a lot of data and this would affect the overall performance of the site.
You should look into ContainableBehavior, which will help you only grab the results you really need. The first thing I always suggest is changing $recursive = -1 and using Containable. This will also greatly improve the performance of your app because you will be performing less calls for data you don't actually use.
Using your example:
$results = $this->Post->find('all', array(
'contain' => array(
'SubCategory' => array(
'Category'
)
)
));
// in your view foreach loop
echo $post['SubCategory']['Category']['name'];

CakePHP update field value based on other value in same row?

I have been trying to figure out how to do this and it seems that its not something that many people are trying to do in cakephp or I am just completely misunderstanding the documentation.. I am using the following query below to get a field value so I get a value where the "findbycreated" is 0... this part works fine
$unregisteredemail = $this->Testmodel->findBycreated('0');
$emailaddress = $unregisteredemail['Testmodel']['emailaddress'] ;
$emailpassword = $unregisteredemail['Testmodel']['password'] ;
But now, after I do some things with this data that I retrieved, I want to mark a field, in the same row, in the same model / table as a value of '1' to indicate that an action has taken place (email address has been successfully created, for example)... I just can't figure out how to do this in cakephp despite my efforts of going through the documentation and searching, this should be rather simple, I am tempted, at this point, to just use a regular mysql query as its a simple query.. basically the query is (please excuse my syntax as I haven't used direct mysql queries in a while) "update (database / table) set 'created'='1' where 'emailaddress'=$emailaddress"
Or I could use the row ID, if needed, as cakephp seems to prefer this, but still can't get how to do this.. this is my attempt below that is not working:
// update database to show that email address has been created
$this->Testmodel->read('emailaddress', $this->Testmodel->findBycreated('0'))
$this->Testmodel->id = 1;
$this->Testmodel->set(array(
'created' => '1'
));
$this->Testmodel->save();
There are, as you can see from the previous answers, several ways to achieve the same end. I'd just like to explain a little about why your way didn't work.
In the model, CakePHP has abstracted the database row(s) into an array according its implementation of ORM . This provides us with a handy way of manipulating the data and chucking it around the MVC architecture.
When you say:
$this->Testmodel->set(array(
'created' => '1'
));
You are dealing directly with the model, but the data is actually stored, as an array, in a class variable called $data. To access and manipulate this data, you should instead say:
$this->data['Testmodel']['created'] => '1';
The reason for specifying the model name as the first index is that where associated tables have been retrieved, these can be accessed in the same way, so you might have , for instance:
Array([Testmodel] => Array ([id] => 1,
[created] => [1],
...
)
[Relatedmodel] => Array ([id] => 1,
[data] => asd,
...
)
)
...and so on. Very handy.
Now, when you use $this->MyModelName->save() with no parameters, it uses $this->data by default and uses the part of the array of data appropriate to the model you are calling the save method on. You can also pass an array of data, formatted in the same way if, for some reason, you don't (or can't) use $this->data.
Your use of the method read() is incorrect. The first parameter should be null, a string or an array of strings (representing fieldname(s)). The second parameter should be the id of the record you wish to read. Instead, for param 2, you are passing the result of a find, which will be an array. The result, which you are not capturing, will be empty.
I would write your code like:
$this->data = $this->Testmodel->read('emailaddress',1);
$this->data['Testmodel']['created'] = 1;
$this->Testmodel->save();
or more succinctly:
$this->Testmodel->id = 1;
$this->Testmodel->saveField('created', 1);
In this situation I would let Cake deal with the id's and just focus on changing the row data and resaving it to the database
$row = $this->Model->findBycreated(0);
$row['Model']['emailaddress'] = 1;
$this->Model->save($row);
This way, you don't have to worry about the id's, as the id will be in your dataset anyway, so just change what you want and then tell Cake to save it.
Ninja edit, Be sure that you are returning a full row with an id from your findBycreated() method.
There're many ways to do your work.I suggest you to read the cookbook about saving data in cakephp.And besides david's solution another simple way would be
$this->Testmodel->id = 1;
$this->Testmodel->saveField('created' =>'1');
Ok, I think I finally found the solution, I was able to get this to work:
$this->Test->updateAll(
array(
'Test.field' => 'Test.field+100'
),
array(
'Test.id' => 1
)
);
I think you have to use updateAll as anything else will just create a new row.. basically CakePHP, for whatever reason, neglected to include a function for updating just one field so you have to put it into an array with the updateAll to make it work...
the +100 is where the updated info goes, so in this case "100" would be what the field is updated to.
In cakephp 3.x the syntax seems to be different. This is what worked for me in 3.x:
$this->Tests->updateAll(
[
'Tests.field = Tests.field+100'
],
[
'Tests.id' => 1
]
];
The difference is that the entire expression needs to be in the value of the first array.

How do I join two tables in a third n..n (hasAndBelongsToMany) relationship in CakePHP?

I have a n...n structure for two tables, makes and models. So far no problem.
In a third table (products) like:
id
make_id
model_id
...
My problem is creating a view for products of one specifi make inside my ProductsController containing just that's make models:
I thought this could work:
var $uses = array('Make', 'Model');
$this->Make->id = 5; // My Make
$this->Make->find(); // Returns only the make I want with it's Models (HABTM)
$this->Model->find('list'); // Returns ALL models
$this->Make->Model->find('list'); // Returns ALL models
So, If I want to use the list to pass to my view to create radio buttons I will have to do a foreach() in my make array to find all models titles and create a new array and send to the view via $this->set().
$makeArray = $this->Make->find();
foreach ($makeArray['Model'] as $model) {
$modelList[] = $model['title'];
}
$this->set('models', $models)
Is there any easier way to get that list without stressing the make Array. It will be a commom task to develops such scenarios in my application(s).
Thanks in advance for any hint!
Here's my hint: Try getting your query written in regular SQL before trying to reconstruct using the Cake library. In essence you're doing a lot of extra work that the DB can do for you.
Your approach (just for show - not good SQL):
SELECT * FROM makes, models, products WHERE make_id = 5
You're not taking into consideration the relationships (unless Cake auto-magically understands the relationships of the tables)
You're probably looking for something that joins these things together:
SELECT models.title FROM models
INNER JOIN products
ON products.model_id = models.model_id
AND products.make_id = 5
Hopefully this is a nudge in the right direction?
Judging from your comment, what you're asking for is how to get results from a certain model, where the condition is in a HABTM related model. I.e. something you'd usually do with a JOIN statement in raw SQL.
Currently that's one of the few weak points of Cake. There are different strategies to deal with that.
Have the related model B return all ids of possible candidates for Model A, then do a second query on Model A. I.e.:
$this->ModelB->find('first', array('conditions' => array('field' => $condition)));
array(
['ModelB'] => array( ... ),
['ModelA'] => array(
[0] => array(
'id' => 1
)
)
Now you have an array of all ids of ModelA that belong to ModelB that matches your conditions, which you can easily extract using Set::extract(). Basically the equivalent of SELECT model_a.id FROM model_b JOIN model_a WHERE model_b.field = xxx. Next you look for ModelA:
$this->ModelA->find('all', array('conditions' => array('id' => $model_a_ids)));
That will produce SELECT model_a.* FROM model_a WHERE id IN (1, 2, 3), which is a roundabout way of doing the JOIN statement. If you need conditions on more than one related model, repeat until you have all the ids for ModelA, SQL will use the intersection of all ids (WHERE id IN (1, 2, 3) AND id IN (3, 4, 5)).
If you only need one condition on ModelB but want to retrieve ModelA, just search for ModelB. Cake will automatically retrieve related ModelAs for you (see above). You might need to Set::extract() them again, but that might already be sufficient.
You can use the above method and combine it with the Containable behaviour to get more control over the results.
If all else fails or the above methods simply produce too much overhead, you can still write your own raw SQL with $this->Model->query(). If you stick to the Cake SQL standards (naming tables correctly with FROM model_as AS ModelA) Cake will still post-process your results correctly.
Hope this sends you in the right direction.
All your different Make->find() and Model->find() calls are completely independent of each other. Even Make->Model->find() is the same as Model->find(), Cake does not in any way remember or take into account what you have already found in other models. What you're looking for is something like:
$this->Product->find('all', array('conditions' => array('make_id' => 5)));
Check out the Set::extract() method for getting a list of model titles from the results of $this->Make->find()
The solution can be achieved with the use of the with operation in habtm array on the model.
Using with you can define the "middle" table like:
$habtm = " ...
'with' => 'MakeModel',
... ";
And internally, in the Model or Controller, you can issue conditions to the find method.
See: http://www.cricava.com/blogs/index.php?blog=6&title=modelizing_habtm_join_tables_in_cakephp_&more=1&c=1&tb=1&pb=1

Resources