CakePHP 3 hasMany update strange behaviour - cakephp

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.

Related

CakePHP pagination, how do I sort by the count of a contained model?

Using CakePHP 2.3, I'm retrieving data using a paginator. So, say my models are Countries having many Cities, and in my CountryController I have...
$this->Paginator->settings = [
'fields' => [
'id'
'country_name'
],
'contain' => [
'City' => [
'id'
'city_name',
'conditions' => [
'population >' => 1000000;
]
]
]
];
...which gets me a list of all counties with each row containing a list of any populous cities.
In the view I am obviously able to iterate foreach ($cities as $city) and echo $country['country_name'] etc. and also if I wish I can show a count of the contained cities by echoing count($country['City']).
Using the paginator I can sort the country results by passing back a field name in the query string, e.g. sort=country_name, but how can I get the results to sort by the count of the contained cities?
It is unfortunately not possible to sort by the count of the hasMany Table using the custom Cakephp Pagination. Your best bet is to use counterCache as described in the Docs.
You will need to have a field in country table, named as city_count. This field will be updated in the Country Table automatically by Cakephp whenever there is a save operation on city table.
Since you only want to count the cities with population > 100K. You can specify the condition in counterScope which will only update the column when condition is met.
This can be defined in your City Model as below:
class City extends AppModel {
public $belongsTo = array(
'Country' => array(
'counterCache' => true,
'counterScope' => array(
'City.population > ' => 1000000
)
)
);
}

Form elements not being submitted

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.

Validation array

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'
]

Paginate and Sort bug(s)?

In my controller I have my pagination set to order by 2 fields.
public $paginate = [
'limit' => 50,
'order' => ['first_name', 'last_name']
];
$contacts = $this->paginate($this->Contacts);
This works fine on the first page, but since I left out the default direction => 'ASC' the Paginator links don't work at all:
/contacts?page=2&sort=0&direction=first_name
When I add in the direction, it works, but of course only sorts by the first field, messing up the sort order.
/contacts?page=2&sort=Contacts.first_name&direction=ASC
Should the default direction be explicitly required?
Is there a method to maintain both fields for sorting during pagination?
Sorting by virtual fields (e.g. full_name => first_name . ' ' . last_name) doesn't work as it did in 2.x
Solved both issues with the following:
Set the default sort order to be the same as the virtual field:
public $paginate = [
'order' => ['first_name', 'last_name']
];
Then just add the following to the View to prevent the paginator from overriding the default order unless specified by the user:
if (empty($_GET['direction'])) { $this->Paginator->options(['url' => ['direction' => null, 'sort' => null]]); }

cakephp 3 bootstrap-ui change prev/next text

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'
]
);

Resources