So I've got a weird problem that I'm having a hard time figuring out. I've got a simple form with a few elements that are not being submitted, all of these elements have only one thing in common, they're select elements:
echo $this->Form->control("spirit_type_id", [
"label" => false,
"type" => "select",
"options" => $spirit_types,
"empty" => "Spirit Type"
]);
echo $this->Form->control("country_id", [
"label" => false,
"type" => "select",
"options" => $countries,
"empty" => "Country"
]);
echo $this->Form->control("region_id", [
"label" => false,
"type" => "select",
"options" => $regions,
"empty" => "Region"
]);
And in my controller I have:
public function add() {
$spirit = $this->Spirits->newEntity();
$spirit_types = $this->Spirits->SpiritTypes->find("list");
$countries = $this->Spirits->Countries->find("list");
$regions = $this->Spirits->Regions->find("list");
if ($this->request->is("post")) {
debug($this->request->getData());
die();
$spirit = $this->Spirits->patchEntity($spirit, $this->request->getData());
$spirit->user_id = $this->Auth->user("id");
if ($this->Spirits->save($spirit)) {
$this->Flash->success("Your spirit was successfully saved.");
$this->redirect(["action" => "index"]);
} else {
$this->Flash->error("Your spirit could not be saved.");
}
}
$this->set(compact("spirit", "spirit_types", "countries", "regions"));
}
The important part is that debug statement. It shows this when I insert data using the form.
[
'name' => 'Longrow Peated',
'image' => 'imageLocation',
'brand' => 'Springbank',
'age' => '',
'cost' => '55'
]
Those are all text and/or number elements in my form, and they all come out just fine. It gets a little weirder though. I have validation in my table to require those id fields:
public function validationDefault(Validator $validator) {
$validator->requirePresence(
"name", "brand", "spirit_type_id", "country_id", "region_id", "age", "cost", "image"
)
->notEmpty("name", "We require a name")
->notEmpty("brand", "We require a brand or distillery")
->notEmpty("spirit_type_id", "We require a type of alchohol")
->notEmpty("country_id", "We require a country of origin")
But this doesn't ever seem to get triggered when I insert the data using patchEntity, it's only caught when I actually call the save function and I try inserting into the database.
If $this->request->getData() is not showing all of your fields, the most likely cause would be some sort of problem with your form; there are not a lot of ways for CakePHP to discard your data from here. You can narrow it down by using browser tools (built into most of them now) to inspect the data actually being sent from your browser in the page request.
If it turns out that the fields really aren't being sent across at all, the problem is almost certainly in your form. For example, you might be closing it early, or there might be HTML errors that confuse the browser. Make sure that all of your input tags are between the <form> and </form>, and if they are then try an HTML validator to check your code. There are lots of options online, and even the inspectors built into browsers can often help you spot these sorts of issues.
This is the most common problem:
If you check debug($this->request->getData()); before $spirit = $this->Spirits->newEntity(); you then see all submitted data!
Next go to Spirit Entity and double check if your fields "spirit_type_id,.." accessible!
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'*' => true, // quick fix
'id' => false,
];
or better way:
protected $_accessible = [
'spirit_type_id' => true,
'country_id' => true,
// etc ...
];
Edit
debug
$spirit = $this->Spirits->patchEntity($spirit, $this->request->getData());
debug($spirit); exit();
see if any errors.
Related
I have doctor and specialization table, and have doctor_specialization_pivot table. In my pivot table I have the following columns:
| doctor_id | additional_data | specialization_id |
additional_data is from the doctor model along with the doctor_id.
In my doctor model file, I have this relationship:
public $belongsToMany = [
'specialization' => [
'path\to\specialization\model',
'table' => 'doctor_specialization_pivot',
'parentKey' => 'doctor_id',
'otherKey' => 'specialization_id',
]
];
Now during submit of form, I'm getting this error:
SQLSTATE[HY000]: General error: 1364 Field 'additional_data' doesn't have a default value (SQL: insert into doctor_specialization_pivot (doctor_id, specializations_id) values (1, 3))"
I tried adding to my relationship 'pivot' => ['additional_data']. But still getting the same error.
I checked the submitted data and additional_data is not empty. I checked from OctoberCMS forums but not getting straight forward answers such as this and this.
Okay. I found the answer to my own question.
I'll answer in detail to help everyone. After digging and blind shooting. According to this documentation here, we can use the method attach() to attach a role to a user by inserting a record in the intermediate table that joins the models.
What confuse me in the documentation is that it uses a $roleId variable and I didn't understand where the $roleId came from. If it's the id of the parent table or the id of other table.
Sample from the link:
$user = User::find(1);
$user->roles()->attach($roleId);
So what I did in my doctor model, I hook to the event beforeSave, use the relationship ($this->specialization) as the first parameter instead of the id in the docs. The $this->specialization() is the relationship too defined in belongsToMany.
Answer:
public function beforeSave()
{
$this->specialization()->attach($this->specialization,['additional_data' => 'additional data from doctor table']);
}
The implementation is pretty much like this video from Watch Learn (Ivan). You can learn a lot about OctoberCMS just by watching his guide on it. Here is the documentation on it as well. This is the example info that I have done.
WARNING Another known flaw is you can't apply this to a model record that isn't created yet. Unlike the standard relation widget which waits until it is saved before attaching records this attaches records in a separate overlay form.
Here is my model.php relationship:
public $belongsToMany = [
'equipments' => [
'Brandon\Pixelrpg\Models\Equipments',
'table' => 'brandon_pixelrpg_equipment_inventory',
'key' => 'inventory',
'otherKey' => 'equipment',
'pivot' => ['quantity']
]
];
Here is my controller.php:
public $implement = [
'Backend\Behaviors\ListController',
'Backend\Behaviors\FormController',
'Backend\Behaviors\ReorderController',
'Backend\Behaviors\RelationController'
];
public $listConfig = 'config_list.yaml';
public $formConfig = 'config_form.yaml';
public $reorderConfig = 'config_reorder.yaml';
public $relationConfig = 'config_relation.yaml';
Here is my config_relation.yaml:
equipments:
label: Equipments
view:
list:
columns:
id:
label: ID
type: number
searchable: true
sortable: true
name:
label: Name
type: text
searchable: true
sortable: true
value:
label: Value
type: number
searchable: true
sortable: true
updated_at:
label: Updated
type: datetime
searchable: true
sortable: true
pivot[quantity]:
label: Quantity
type: number
pivot:
form:
fields:
pivot[quantity]:
label: Quantity
type: number
default: 0
I am just going to make a new answer and assume is what you need because you have yet to show any code on how your form works. This is how I would update the pivot information from a frontend form.
Relationship in model.php:
public $belongsToMany = [
'specialization' => [
'path\to\specialization\model',
'table' => 'doctor_specialization_pivot',
'parentKey' => 'doctor_id',
'otherKey' => 'specialization_id',
'pivot' => ['additional_data'] //This is required
]
];
Then in some php code lets call it onAddSpecialization():
public function onAddSpecialization() {
//Calling a function to get the doctor id maybe from the signed in user
$doctor = Doctors::find($this->$doctorId());
//We get our Specialization from an input
$specialization = Specialization::find(Input::get('specialization_id'));
//We get our additional data from an input
$additional_data = Input::get('additional_data');
//Now we are going to attach the information
$doctor->specialization()->attach($specialization, ['additional_data' => $additional_data]);
}
Now an example of updating our additional data:
public function onUpdateAdditionalData() {
//Calling a function to get the doctor id maybe from the signed in user
$doctor = Doctors::find($this->$doctorId());
//If you get specialization by id from an input. I believe you need to go through the relationship in order to access the correct pivot information.
$specialization = $doctor->specialization->where('id', Input::get('specialization_id'))->first();
//Insert the new pivot information
$specialization->pivot->additional_data = $new_additional_data;
//Save
$specialization->pivot->save();
}
I have a tags in my project but tags is created by me and user can checked what he want. It works correctly but I have a problem with Validation. In my store function I have an array and I want to validate any single element. I wrote this validate rule:
$validator = Validator::make($request->tags, [
'id' => 'integer|max:15'
]);
It doesn't work. Why?
You may use this
[
'tags' => 'array',
'tags.*' => 'integer|max:15'
]
I have JobsTable:
This is relation definition:
$this->hasMany( 'JobContracts', [
'foreignKey' => 'job_id'
] );
Saving code:
$entity = $this->patchEntity( $entity, $toSave, [
'fieldList' => ['notes],
'associated' => [
'JobContracts' => ['fieldList' => ['id', 'checked']]
]
] );
And now:
if I put this notes in fieldList then JobContracts are NOT saved properly.
If I remove fieldList, then I am able to save it properly.
Question is Why? I need to control base model fields also. Any suggestions?
Ive already checked: http://book.cakephp.org/3.0/en/orm/saving-data.html#avoiding-property-mass-assignment-attacks
You need to allow assigning the association property too, not only notes. If you don't, then the associated data is never going to be set on the resulting entity, and consequently is not going to be saved.
Check the docs that you've linked again, the tags example shows exactly that:
// Only allow changing the title and tags
// and the tag name is the only column that can be set
$entity = $this->patchEntity($entity, $data, [
'fieldList' => ['title', 'tags'],
'associated' => ['Tags' => ['fieldList' => ['name']]]
]);
$this->save($entity);
http://book.cakephp.org/3.0/en/orm/saving-data.html#avoiding-property-mass-assignment-attacks
So, add job_contracts to the field list, and you should be good.
I have FriendsofCake Bootstrap-ui plugin. I see in the source that it accepts text for the pagination prev and next labels.
I am not sure how to exactly set the config option though.
PaginatorHelper.php
if (isset($options['next'])) {
if ($options['next'] === true) {
$options['next'] = $this->config('labels.next');
}
$options['after'] = $this->next($options['next'], ['escape' => false]) . $options['after'];
}
I was trying this below in the bootstrap.php but no effect
Configure::write('friendsofcake.PaginatorHelper.labels.prev', 'previous');
But I see they are also set in the __construct
Answer
With the help from drmonkeyninja here is the exact code needed to configure the labels in the AppView.php
$this->loadHelper(
'Paginator',
[
'className' => 'BootstrapUI.Paginator',
'labels' => [
'prev' => 'previous',
'next' => 'next',
]
]
);
This appears to be badly documented, but to configure any of the settings for a helper you need to pass them as an array when you load it. So for example, if you are loading the Paginator helper inside your AppView you would pass prevlike this:-
$this->loadHelper(
'Paginator',
[
'className' => 'BootstrapUI.Paginator',
'prev' => 'previous'
]
);
I'm attempting to customize the error output on the CakePHP 2.0 form helper. Currently, the form renders error messages below the input and applies an 'error' class to the input's label.
I have found that I can either disable error reporting altogether for an input, or output the error class and message.
I would like the error class to be applied to the label of the offending inputs WITHOUT any message below.
How do you turn off the error message outputting for a form, BUT still apply error classes to offending labels?
FormHelper::input() has a format option. It is a
format template for element order. Any element that is not in the array, will not be in the output.
Default input format order: array('before', 'label', 'between', 'input', 'after', 'error')
You can pass the default format, leaving out the 'error':
echo $this->Form->input(
'some_field',
array('format' => array('before', 'label', 'between', 'input', 'after'))
);
That should produce the input markup without the error message.
If you want to apply this to multiple inputs in your form, you should keep it DRY:
$format = array('before', 'label', 'between', 'input', 'after');
echo $this->Form->input(
'some_field',
array('format' => $format)
);
echo $this->Form->input(
'some_other_field',
array('format' => $format)
);
It is also possible to set the default format for all inputs of a form by passing the format to FormHelper::create() as inputDefaults:
$this->Form->create(
'MyModel',
array(
'inputDefaults' => array(
'format' => array('before', 'label', 'between', 'input', 'after')
)
)
);
You'll have to do some of this manually. First turn off the validations, and label generation on the Form Helper.
echo $this->Form->input('myfield', array('error' => false, 'label' => false));
Then to add the class to the create the label and add the error class if validations have failed. To find out which validations failed check the invalidFields array like so:
$error = null;
if (isset($this->invalidFields['Model']['myfield'])) {
$error = 'error';
}
echo $this->Form->label('myfield', 'My Field', array('class' => $error));
You can always make use of Form->error('field_name') which should return nothing if there is no errors.
$error = $this->Form->error('field_name');
echo $this->Html->input('field_name', array(
'class' => !empty($error) ? 'error' : null,
'error' => false
));
You now have the $error with the usual markup for errors that could be displayed in another location.
There is no way to get around without checks, the Form->input() method is a convinience method that does all these things like errors, divs, labels automatically which can be done through Form->label(), Form->checkbox(), Form->select() etc which is the basic elements only.
One of the options that can be passed to Form->create() is inputDefaults which you can use to set defaults for all the other form elements. This will not help much as you are doing field by field. ('error' => false would help a bit)
The other thing you can do is make your own form helper, extending the core FormHelper and customise the input method to do this all automatically. You can use aliasing to load your custom helper into $this->Form to be used as normal. See the bottom of this section.
You can also overload the input method in AppHelper but that is not a good place for it.