I want to make a Library in cakePHP and I have some problems with the database relations.
Basically there is a Book object, a Publisher object, a Comment object and a User object.
Every Book can have a Publisher, and multiple Comments. A comment can belongsTo only one book and one user. Now the question is, How can i show the users also in the returned array
I want to show user info for every comment
Here is the code.
class Book extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment'
)
);
public $belongsTo = 'Publisher';
}
class Publisher extends AppModel{
public $hasMany = array(
'Book' =>array(
'className' => 'Book'
)
);
}
class Comment extends AppModel {
public $hasOne = array(
'User' => array(
'className' => 'User',
),
'Book' => array(
'className' => 'Book',
)
);
}
class User extends AppModel {
public $hasMany = array(
'Comment' => array(
'className' => 'Comment'
)
);
}
And here is the result:
[
{
"Book":{
"id":"1",
"title":"Egy magyar nabob",
"author_id":"1",
"publisher_id":"1",
"punblishing_date":"2014-10-08 00:00:00",
"creation_date":"2014-10-01 22:48:55"
},
"Publisher":{
"id":"1",
"name":"Koinonia Kiado",
"description":"Egy kolozsvari kiado",
"address":null
},
"Comment":[
{
"id":"3",
"comment":"Ez egy irto jo konyv :D",
"user_id":"1",
"book_id":"1",
"creation_date":"2014-10-01 23:08:21"
},
{
"id":"4",
"comment":"Ujabb COmment :D",
"user_id":"1",
"book_id":"1",
"creation_date":"2014-10-01 23:17:02"
}
]
},
{
"Book":{
"id":"2",
"title":"Ket magyar nabob",
"author_id":"1",
"publisher_id":"1",
"punblishing_date":null,
"creation_date":"2014-10-01 23:17:22"
},
"Publisher":{
"id":"1",
"name":"Koinonia Kiado",
"description":"Egy kolozsvari kiado",
"address":null
},
"Comment":[
{
"id":"5",
"comment":"Ez ismet egy fantasztikus konyv :D",
"user_id":"1",
"book_id":"2",
"creation_date":"2014-10-01 23:17:49"
}
]
}
]
Thanks!
A relatively simple way is to have a getUsers() action in your Users controller, that is similar to the view action, e.g. you can pass it a User ID, but instead of loading a view it will just return an array. Then each time you come across a comment in your Book controller/view you can use requestAction to call Users/getUsers(), passing it Comment.user_id, to return the related User array.
Related
Basically, there are 3 models:
Class Model1 {
Public $hasMany = array {
'Model2' => array(
'className' => 'Model2',
'foreignKey' => 'model1id'
)
};
}
Class Model2 {
Public $belongsTo = array {
'Model1' => array(
'className' => 'Model1',
'foreignKey' => 'model1id'
)
};
Public $hasMany = array {
'Model3' => array(
'className' => 'Model3',
'foreignKey' => 'model2id'
)
};
}
Class Model3 {
Public $belongsTo = array {
'Model2' => array(
'className' => 'Model2',
'foreignKey' => 'model2id'
)
};
}
In ctp file under Model1s view folder, I have code as below:
echo $this->Form->create('Model1');
echo $this->Form->input('[some_model1_field]');
echo $this->Form->input('Model2.0.[some_field]');
echo $this->Form->input('Model3.0.[some_field]');
echo $this->Form->end('Save');
In Controller, having a function like below:
$this->Model1->saveAssociated($this->request->data, array('deep' => true));
The result is the all fields are inserted into db, including the foreignkey model1id saved into model2 table automatically by framework. The only issue is that the model2id cannot saved into model3 table automatically by framework. How to deal with it?
Model1 is not associated with Model3, your input field names and the resulting request data however are representing such an association. Debug the request data and compare it to what the docs are showing
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-saveassociated-array-data-null-array-options-array
The problem with the structure should be obvious, it's going to be something like
[
// ...
'Model2' => [
[/* ... */]
]
'Model3' => [
[/* ... */]
]
]
If you'd wanted to save Model3s for Model2, then you'd have to properly nest the data like
echo $this->Form->input('Model2.0.Model3.0.[some_field]');
which should result in something like
[
// ...
'Model2' => [
// ...
'Model3' => [
[/* ... */]
]
]
]
I have two tables with a relation. The find method works very well without problem, i get all the fields that i want between both tables. The delete method work fine also.
But i want to add a validation before delete the record, verifiying the relation with the secondary table, if the relation exists can't delete the record.
My Tables:
invoices: id, company_id, number, invoice_date
invoice_numbers: id, company_id, number
companies: id, name
My Models:
Invoice:
class Invoice extends AppModel
{
public $belongsTo = array(
'Company',
'InvoiceNumber' => array(
'className' => 'InvoiceNumber',
'foreignKey' => false,
'conditions' => array(
'InvoiceNumber.company_id = Invoice.company_id',
'InvoiceNumber.number = Invoice.number',
),
'type' => 'left',
'fields' => array(
'FacturaNumero.id',
),
)
);
}
InvoiceNumber:
class InvoiceNumber extends AppModel
{
public $hasOne = array(
'Invoice' => array(
'className' => 'Invoice',
'foreignKey' => false,
'conditions' => array(
'Invoice.company_id = InvoiceNumber.company_id',
'Invoice.number = InvoiceNumber.number',
),
'type' => 'left',
'fields' => array(
'Invoice.invoice_date',
),
)
);
}
My Controller (delete Method):
public function delete($id)
{
$this->InvoiceNumber->id = $id;
if (!$this->InvoiceNumber->exists()) {
throw new NotFoundException('Invalid Record.');
}
/**
* I want to add a validation here if field invoide_date is not null, by example
* but this dont work, because get the first value in the invoice table.
*/
if (!empty($this->InvoiceNumber->Invoice->field('invoice_date')) {
throw new NotFoundException('I Cant Delete this record.');
} else {
$this->InvoiceNumber->delete();
}
}
The misunderstanding here is that when you write
$this->InvoiceNumber->Invoice
you are just accesing a generic Invoice Model Object and not the Invoice of that specific InvoiceNumber
I think that you just need to change your code in
$InvoiceNumber = $this->InvoiceNumber->read();
if (!empty($InvoiceNumber['Invoice'])) {
// ...
}
but maybe this is not the best way: I think that a cleaner approach could be to do the validation in the InvoiceNumber beforeDelete() method
PS: sorry for the typos. Now should be correct
I have a Product model and an Image model. They have an HABTM association.
Some Images exist but they are not linked to the product.
Now when I save a Product I would like to link it to some unlinked images using an array of images IDs (I MUST use this array).
Here's my code:
class Image extends AppModel {
public $useTable = 'images';
var $hasAndBelongToMany = array(
'Product' => array(
'className' => 'Product',
'joinTable' => 'products_images',
'foreignKey' => 'id_image',
'associationForeignKey' => 'id_product'
)
);
}
class Product extends AppModel {
public $useTable = 'products';
var $hasAndBelongToMany = array(
'Image' => array(
'className' => 'Image',
'joinTable' => 'products_images',
'foreignKey' => 'id_product',
'associationForeignKey' => 'id_image'
)
);
}
class productsController extends AppController {
public $name = 'Products';
public $uses = array('Products', 'File');
public function add() {
if (!empty($this->data)) {
$this->Product->create();
if ($this->AnnuncioImmobiliare->save($this->request->data)) {
$idProduct = $this->Product->getLastInsertID();
$this->request->data['imagesIds'] = array("1", "2", "3");
if(isset($this->request->data['imagesIds'])){
foreach($this->request->data['imagesIds'] as $imageId){
$this->Image->id = $imageId;
$this->Image->save(array('Product'=>array('id'=>$idProduct)));
}
}
}
}
}
}
This doesn't work. Where am I wrong?
1) you didn't provide the data, so we can't verify it's in the correct format.
2) you're using "save()" not "saveAll()" or "saveMany()" or "saveAssociated()". Just using "save()" will not save any associated model data.
I found a solution myself!
I observed how CakePHP handles $this->data->response array when data is passed using an input automagically created for HABTM associations and I found the array should be formatted like this:
array(
'Product' => array(
'name' => 'myProduct',
'id' => ''
),
'Image' => array(
'Image'=>array(
0 => '1',
1 => '2',
2 => '3'
)
)
)
So the correct code for the controller is
class productsController extends AppController {
public $name = 'Products';
public $uses = array('Products', 'File');
public function add() {
if (!empty($this->data)) {
$this->Product->create();
if (isset($this->request->data['imagesIds'])) {
$this->request->data['Image'] = array('Image' => $this->request->data['imagesIds']);
}
if ($this->AnnuncioImmobiliare->save($this->request->data)) {
/* success */
}
}
}
}
That's it! I hope you find this useful.
I am trying to work with HABTM association between Profiles and Qualifications tables.
Model: Profile.php
App::uses('AppModel', 'Model');
class Profile extends AppModel {
public $hasAndBelongsToMany = array(
'Qualifications' => array(
'className' => 'Qualification',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'profile_id',
'associationForeignKey' => 'qualification_id',
'unique' => 'keepExisting'
)
);
}
Model: Qualification.php
App::uses('AppModel', 'Model');
class Qualification extends AppModel {
public $hasAndBelongsToMany = array(
'Profile' => array(
'className' => 'Profile',
'joinTable' => 'profile_qualifications',
'foreignKey' => 'qualification_id',
'associationForeignKey' => 'profile_id',
'unique' => 'keepExisting',
)
);
}
Controller: ProfilesController.php
App::uses('AppController', 'Controller');
class ProfilesController extends AppController {
public function add() {
$qualifications = $this->Qualification->find('list'); /* Attempt 1 */
$qualifications = $this->Profile->Qualification->find('list'); /* Attempt 2 */
$qualifications = $this->Profile->ProfileQualification->Qualification->find('list'); /* Attempt 3 */
}
}
All three attempts mentioned as comment have given me an error saying:
Error: Call to a member function find() on a non-object
File: ~/app/Controller/ProfilesController.php
Line: xxx
I want to know how can I generate a list of all entries in Qualifications table ?
Moreover, what is the mistake in my code right now ?
In your Profile Model, the alias of your HABTM relation with Qualification is "Qualifications", so inside the controller you have to use : $qualifications = $this->Profile->Qualifications->find('list'); or remove the plural from the model.
To prevent these kind of mistakes and save your time, it's really useful to use automatic code generation with the cake bake console or an online CakePHP baking tool.
I'm trying to build on the example code at http://book.cakephp.org/2.0/en/models/saving-your-data.html in the section that starts off with "Saving Related Model Data (hasOne, hasMany, belongsTo)". However, I'm getting the following error message when I call unset:
Indirect modification of overloaded property AppModel::$MealContent has no effect [APP\Controller\MealsController.php, line 15]
Attempt to modify property of non-object [APP\Controller\MealsController.php, line 15]
Naturally, the data isn't saved either.
As you can see, my application doesn't have Companies or Accounts. Instead, I have Meals and MealContents but the relationships seem to have been set up the same. Obviously, there's a problem somewhere though so here's some code.
Meals.php:
class Meals extends Entry {
public $hasMany = 'MealContents';
public $validate = array(
'Timestamp' => array(
'validDate' => array(
'rule' => array('garbageDateChecker'),
'required' => true,
'message' => 'The date could not be understood.'),
'noTimeTravel' => array(
'rule' => array('noTimeTravel'),
'required' => true,
'message' => 'You cannot enter times in the future.')
)
);
}
MealContents.php:
class MealContents extends Entry {
public $belongsTo = 'Meals';
public $validate = array(
'Meals_ID' => array(
'notEmpty' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'This field cannot be blank')
),
'Item_ID' => array(
'notEmpty' => array(
'rule' => 'notEmpty',
'required' => true,
'message' => 'This field cannot be blank')
)
);
}
And finally, the controller's index() function:
public function index() {
$this->set('title_for_layout', "Food Log");
if ($this->request->is('post')) {
$this->Meal->create();
unset($this->Meal->MealContent->validate['Meals_ID']);
if ($this->Meal->saveAssociated($this->request->data)) {
$this->Session->setFlash('The meal was logged.');
$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash("Couldn't save the meal.");
}
}
}
Entry.php
abstract class Entry extends AppModel {
public function garbageDateChecker($dateToCheck) {
date_default_timezone_set("America/Tijuana");
$result = TRUE;
try {
new DateTime($dateToCheck['Timestamp']);
} catch (Exception $e) {
$result = FALSE;
}
return $result;
}
public function noTimeTravel($dateToCheck) {
date_default_timezone_set("America/Tijuana");
$result = TRUE;
$objectifiedDateToCheck = new DateTime($dateToCheck['Timestamp']);
$currentTime = new DateTime("now");
if ($objectifiedDateToCheck > $currentTime) {
$result = FALSE;
}
return $result;
}
}
I'm pretty sure the save() failure isn't due to the validation because even when I comment out the validation rule and the unset() line, the data isn't saved.
It would be easy to blame it on bad data or a screwed up view. However, the data looks OK:
$this->request->data
--MealContents
---[0]
-----[Food_Item_ID] = "0"
--Meals
---[Comments] = ""
---[Timestamp]
-----[day] = "12"
-----[hour] = ...
What have I missed when I read the CakePHP book?
In all of your model class declarations, you should be extending the AppModel class not "Entry". Also, you need to change your model names to singular nouns. Meal instead of Meals.
class Meal extends AppModel {
//your model's code
}
class MealContent extends AppModel {
//your model's code
}
In your controller, if you would like to skip validation on the saveAssociated call, you can pass an options array, with element "validate" set to False as the second parameter. You should never use unset on your model like that as it will affect the rest of your app.
$this->Meal->saveAssociated($this->request->data, array("validate" => false));