I'm trying to save a record that belongs to another model. Yet, I get an error that the foreign key is missing. The CakePHP docs show examples of this working but I can't get it to work.
Here is the Form:
echo $this->Form->create('Message', array('action'=>'add'));
echo $this->Form->hidden('Ticket.id');
echo $this->Form->input('Message.message');
echo $this->Form->submit('Save Message');
echo $this->Form->end();
Here is what is returned to the controller upon submit:
Array(
[Ticket] => Array
(
[id] => 2
)
[Message] => Array
(
[message] => Message text
)
)
Here is my Message model:
class Message extends AppModel {
public $belongsTo = array('Ticket');
}
Here is my Ticket model:
class Ticket extends AppModel {
public $hasMany = 'Message';
}
Here is my controller logic:
$this->Message->save($this->request->data);
Here is the error message I receive:
Error: SQLSTATE[HY000]: General error: 1364 Field 'ticket_id' doesn't have a default value
The CakePHP documents are pretty clear that Cake will grab the id from the Ticket array. The only way I can get this to work is if I manually assign the ticket id to the message array.
$this->request->data['Message']['ticket_id'] = $this->request->data['Ticket']['id']
I shouldn't have to do that. Where's the magic? I've read at least 20 similar posts but none exactly like this (probably because this is so basic that nobody has this problem because it works for them).
I'm using version 2.4.2
Thanks for any help.
Don't use Model::save() in this case, the reason is that you also want to save associated data, in order for you save data for the main model along with its associated models you need to use Model::saveAssociated().
Your controller should be looking like this
$this->Message->saveAssociated($this->request->data);
Related
iam new to cake and want to set up a order process where the user could send some recommendations (friends mail adr) to get a discount. each recommendation reduces the price. so i want to have up to 5 recommendation inserts in one step.
recommendation table is like (id, order_id, email)
i extend the order model with recommendations
class Order extends AppModel {
public $hasMany = array(
'Recommendation' => array(
'className' => 'Recommendation',
)
);
in the order controller i have to use the saveall method.
now, how should the order add view look like. if i use
echo $this->Form->input('Recommendation.mail');
it will only save one recommendation, or ? But i would like to have up to 5 of them on one page...
Thank you very much,
Julius
Change your form to this
echo $this->Form->input('Recommendation.0.mail');
echo $this->Form->input('Recommendation.1.mail');
echo $this->Form->input('Recommendation.2.mail');
echo $this->Form->input('Recommendation.3.mail');
echo $this->Form->input('Recommendation.4.mail');
Then in your controller,use the saveAll method
$this->Recommendation->saveAll($this->request->data);
i am cakephp beginner.
My Employee Model,
class Employee extends AppModel {
var $belongsTo = array(
'Department'=>array(
'className'=>'Department',
'foreignKey'=>'department_id',
'conditions'=>null,
'fields'=>null
)
);
blah--
now in employee add.ctp i want to create a select box which list all the department.
i was going through official cakephp 2.1 documentation (here)
it tells me to add
$this->set('departments', $this->Employee->Department->find('list'));
in my controller..
i have no idea to put in which controller ? is it in EmployeesController or DepartmentsController? and in which action of controller?
view to create select box (in add.ctp)
echo $this->Form->input('Department');
you were almost correct - only a minor glitch:
echo $this->Form->input('department_id');
you need to name the fields as they are in the database.
and if it is a BelongsTo relation than there should be a department_id foreign key in your employees table.
PS: cake knows that if you pass down $departments that this array will need to be the options for this form field. so no additional configuration necessary!
// in your add action at the very bottom
$departments = $this->Employee->Department->find('list');
$this->set(compact('departments'));
I'm trying to implement a hasone relationship between 2 models, but I can't have the 'add' form autocomplete with the possible options in the second model (the one that belongsTo the first one). This is my code:
- model 1: item.php
<?php
class Item extends AppModel{
var $name = 'Item';
var $primaryKey = 'id';
var $hasOne = 'CoverImage';
}
?>
- model 2: cover_image.php
<?php
class CoverImage extends AppModel{
var $name = 'CoverImage';
var $primaryKey = 'id';
var $belongsTo = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id'
));
}
?>
- add view of model 2: add.ctp
<?php echo $this->Form->create('CoverImage',array('url' => array('controller' => 'admins', 'action' => 'add')));?>
<fieldset>
<legend><?php __('Info'); ?></legend>
<?php
echo $this->Form->input('item_id');
echo $this->Form->input('description');
?>
</fieldset>
<?php echo $this->Form->end(__('Create', true));?>
For what I see in Cake's documentation, with this relationship, in the add view I should see a dropdown list in the item_id field to be able to select to which item does this CoverImage belongs to, but the dropdown is empty (and yes, I have some items in the items table already).
Maybe I'm missing something or I've done something wrong, but I can't figure it out. Thanks so much in advance for any clues!
EDIT
One think I've just realized is that if I do this:
echo $this->Form->input('item_id', array('type'=>'text'));
instead of this:
echo $this->Form->input('item_id');
I can add/edit the *item_id* field, I can see its value in the text box. However, if I leave the other one, I just see an empty dropbox and when I try to add/edit a CoverImage, it doesn't work, it just shows an empty white page, not even with errors...
Maybe this is a lead to something...
In order for that to work you have to create a list of possible options in the controller. That does not happen automatically.
public function add() {
$items = $this->CoverImage->Item->find('list');
$this->set(compact('items'));
}
The FormHelper only automatically infers that the field item_id should be populated by the options in the variable $items (plural, no _id).
Do be careful that Items that already haveOne CoverImage should not be part of that list. find('list', array('conditions' => array('CoverItem.id' => null))) will probably* take care of that, but you'll need to recheck just before saving as well, or you need to rethink your associations.
* Not sure off the top of my head whether that'll work for 'list' searches.
EXCELLENT QUESTION. You've run afoul of a disingenuous feature of Cake's associations:
Considering you defined the relationship as hasOne? Guessing at the trace but Cake probably even correctly inferred your preference for list functionality. You got your automagic list...
...of One.
$hasOne is pretty exclusive like that. It "uses up" those "has" relationships (it's makes the relationship a de facto Singleton - so Users only have 1 Profile <-> Profile only has 1 User). Consider - Database can have many configurations, but Dbo will only ever have one Connection at a time and Connection will only have one Dbo. Thus -> hasOne is for marrying two Models til die() do they part.
-- So it doesn't get used nearly as much as hasMany and belongsTo.
For your purpose, you probably want to change to a different association.
Adding an additional $this->Item->find doesn't really fix what's wrong (and I wouldn't recommend it, unless you're mostly done with both models/controllers, or you actively want things to start getting weird fast.)
Also, changing how you call the Form Helper methods - if you return a 'list' type fetch from a find, Cake will automatically produce an option list out of it. What's actually happening is, you're sneaking around your Model on a very thin margin of View functionality. That's why specifying the input type to "break the magic" tends to be discouraged (which, you totally can if you want. Just understand what's actually happening, or: see, weird, fast.)
But you might want to rethink how you've associated your models - wouldn't it also be correct to say, each Item belongsTo a CoverImage (same as each CoverImage belongs to an Item) -- because you have a form expressly permitting a CoverImage to select an Item, any Item, to be displayed with? You'll probably get better results.
HTH. :)
I have the following Model and Controller files, and when i visit this url, http://....../pois/index i get this error:
Notice (8): Undefined property: PoisController::$Poi [APP/controllers/pois_controller.php, line 5]
Fatal error: Call to a member function find() on a non-object in /home/joecoyle/public_html/app/controllers/pois_controller.php on line 5
The Model is this, called poi.php:
<?php
class Poi extends AppModel {
}
?>
And the controller is this, named pois_controller.php
<?php
class PoisController extends AppController {
function index(){
$this->set('pois',$this->Poi->find('all'));
}
}
?>
As i am new to CakePHP i am not sure what is causing this error, as everything seems to be named, right, and i am following the tutorial on the CakePHP site...
Thanks
You need add var $name = "Poi"; to initialize your class in the controller.
And I've tested that in PHP5.It seems that this is necessary.
Edit:
controller file name:pois_controller.php,code:
<?php
class PoisController extends AppController
{
var $name = "Poi";
function index()
{
debug($this->Poi);
exit;
}
}
?>
database name:pois.Structure:id ,name
And using /pois/ will got:
Poi Object
(
[name] => Poi
[useDbConfig] => default
[useTable] => pois
[displayField] => name
[id] =>
[data] => Array
(
)
[table] => pois
[primaryKey] => id
[_schema] => Array
(
[id] => Array
(
[type] => integer
[null] =>
[default] =>
[length] => 11
[key] => primary
)
[name] => Array
(
[type] => integer
[null] =>
[default] =>
[length] => 11
)
...etc
If SpawnCxy's solution doesn't do the job (my own controllers set the name property to the pluralized version rather than the singular variation that the model takes), take a look at the inflection. "Poi" isn't a "common" word and a quick test tells me that CakePHP 1.2.6 doesn't handle this word the way you're thinking it will:
echo '<p>' . Inflector::singularize( 'Pois' ) . '</p>'; # prints "Pois"
echo '<p>' . Inflector::pluralize( 'Poi' ) . '</p>'; # prints "Pois"
The point of this, of course, is that Cake may not be making the correct association between the PoisController (plural) and the Poi model (singular) the way it does for most common English names.
An alternative to adapting your code to Cake's pluralising/singularising rules is the converse: adapting Cake's rules to your code:
In app/config/inflections.php, find the $irregularPlural line, and change it to:
$irregularPlural = array('poi'=>'pois');
This will instruct Cake to treat the singular of "Pois" as "Poi."
This is a good choice when changing the inflection rules creates better readability/comprehensibility of the rest of your code. For example, by default, Cake treats the singular of "News" as "New". However, it made more sense to find news items with $this->News->find than $this->New->find, so I tweaked the inflection rules.
It's a problem because $this->Poi has not been initialized as an object. I'm not familiar with CakePHP, but in your init function in the PoisController or in contructor you should call $this->Poi = new Poi(); so in the index action when you try to call find(), the method will be called on an instance of Poi model.
I'm having trouble with my HABTM relationship in CakePHP.
I have two models like so: Department HABTM Location. One large company has many buildings, and each building provides a limited number of services. Each building also has its own webpage, so in addition to the HABTM relationship itself, each HABTM row also has a url field where the user can visit to find additional information about the service they're interested and how it operates at the building they're interested in.
I've set up the models like so:
<?php
class Location extends AppModel {
var $name = 'Location';
var $hasAndBelongsToMany = array(
'Department' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class Department extends AppModel {
var $name = 'Department';
var $hasAndBelongsToMany = array(
'Location' => array(
'with' => 'DepartmentsLocation',
'unique' => true
)
);
}
?>
<?php
class DepartmentsLocation extends AppModel {
var $name = 'DepartmentsLocation';
var $belongsTo = array(
'Department',
'Location'
);
// I'm pretty sure this method is unrelated. It's not being called when this error
// occurs. Its purpose is to prevent having two HABTM rows with the same location
// and department.
function beforeSave() {
// kill any existing rows with same associations
$this->log(__FILE__ . ": killing existing HABTM rows", LOG_DEBUG);
$result = $this->find('all', array("conditions" =>
array("location_id" => $this->data['DepartmentsLocation']['location_id'],
"department_id" => $this->data['DepartmentsLocation']['department_id'])));
foreach($result as $row) {
$this->delete($row['DepartmentsLocation']['id']);
}
return true;
}
}
?>
The controllers are completely uninteresting.
The problem:
If I edit the name of a Location, all of the DepartmentsLocations that were linked to that Location are re-created with empty URLs. Since the models specify that unique is true, this also causes all of the newer rows to overwrite the older rows, which essentially destroys all of the URLs.
I would like to know two things:
Can I stop this? If so, how?
And, on a less technical and more whiney note: Why does this even happen? It seems bizarre to me that editing a field through Cake should cause so much trouble, when I can easily go through phpMyAdmin, edit the Location name there, and get exactly the result I would expect. Why does CakePHP touch the HABTM data when I'm just editing a field on a row? It's not even a foreign key!
From the CookBook the 1st problem is:
By default when saving a
HasAndBelongsToMany relationship, Cake
will delete all rows on the join table
before saving new ones.
I am not quite sure why Cake is trying to save the HABTM data even though you don't have a foreign key in your data, but there is an easy solution for that. Simply destroy the association for the save call:
$this->Location->unbindModel(
array('hasAndBelongsToMany' => array('Department'))
);
I'm thinking of one reason why this might be happening. When you retrieve Location, you also retrieve locations_departments data. And when you do a save($this->data) it looks for models in the array and saves them.
A way to solve this is setting the recursive attribute (of a model) to -1 or 0 (try, I'm not sure, just print out the data to see what comes out). You can set it in the model: var $recursive = -1; or in the controller method (action): $this->ModelName->recursive = -1;
More about recursive: http://book.cakephp.org/view/439/recursive
It's really similar to what harpax suggested, just if you don't need that data, tell it to Cake, so that it won't fetch it.
Trouble is that when saving your Location, you gave the save method an array containing all the DepartmentsLocations too. Thus CakePHP destroys everything and try to recreate it.
This is a common mistake with cake since it will often pull far too many results for you.
Be sure to pass only the data that needs to be saved, or better to fetch only the datas you need.