Cakephp - saveAll and beforeSave - php

Since CakePHP is updated to 2.3.5, I have problem to save data.
I don't want to save datas who contains no price.
My method beforeSave in my model looks like this :
public function beforeSave($options = array()){
if(empty($this->data['MyModel']['price'])){
unset($this->data['MyModel']);
}
return true;
}
Since my update, i've found this in /lib/Model/Model.php (l. 1751)
if ($success && $count === 0) {
$success = false;
}
If i comment this 3 lines my problem is solved. Do you know a method in my beforeSave who don't block my saving ?
If i use data validation, all my datas are not saved with my saveAll.
Exemple model "Option" validator:
public $validate = array(
'prix' => array(
'rule' => 'notEmpty'
)
);
Exemple datas to save :
array(
'Commande' => array(
'nature_commande' => '0',
'base_id' => '1',
'nom' => 'Test',
'chef_id' => '531',
),
'Option' => array(
(int) 0 => array(
'prix' => '5456'
),
(int) 1 => array(
'prix' => '45645'
),
(int) 3 => array(
'prix' => ''
)
)
saveAll in my Controller "Commande" (return false):
debug($this->Commande->saveAll($this->request->data, array('validate' => 'first')));
I would like my datas be saved out of the last row in my Model "Options" => 3.
I can use foreach in my controler to delete the blank row but can i make it better ?

Have you thought about using CakePHP's built-in data validation? This would allow you to specify that you don't want to save unless the 'price' is a certain amount, greater than zero, between a range...etc etc etc.
Basically, you set up rules for your fields, and any time you try to save a row, it makes sure each field passes whatever validation you set up.
It has a ton of validation types including valid email, number ranges, phone numbers, regex and more.

Related

Why only last item is saved in database?

I have problem, becouse only last item from loop is saved in database.
Im using CakePhp 2.x
Controller:
for ($x=1; $x <= count($this->request->data['Goodsandoffer'])/3;$x++){
$promID = $this->request->data['Goodsandoffer']['promotionaloffer_id_'.$x];
if($this->request->data['Goodsandoffer']['cenaPromocyjna_'.$x] != ''){
$helperReqestTable3 = array('promotionaloffer_id'=>$this->request->data['Goodsandoffer']['promotionaloffer_id_'.$x],'good_id'=>$this->request->data['Goodsandoffer']['good_id_'.$x],'cenaPromocyjna'=>$this->request->data['Goodsandoffer']['cenaPromocyjna_'.$x]);
$helperReqestTable['Goodsandoffer']=$helperReqestTable3;
debug($helperReqestTable);
$this->Goodsandoffer->save($helperReqestTable);
}
}
Here is how look my debug in loop:
array(
'Goodsandoffer' => array(
'promotionaloffer_id' => '7',
'good_id' => '18',
'cenaPromocyjna' => '1'
)
)
And in next interation:
array(
'Goodsandoffer' => array(
'promotionaloffer_id' => '7',
'good_id' => '19',
'cenaPromocyjna' => '2'
)
)
In database is created only one row with last item.
Model:
class Goodsandoffer extends AppModel {
public $displayField = 'id';
public $belongsTo = array(
'Promotionaloffer' => array(
'className' => 'Promotionaloffer',
'foreignKey' => 'promotionaloffer_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'Good' => array(
'className' => 'Good',
'foreignKey' => 'good_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
}
Have you tried calling $this->Goodsandoffer->create() before you call save? That way you're definitely telling Cake to create a new record each time.
The general process of creating and saving data in Cake is:
$this->Model->create()
$this->Model->set($data_array)
$this->Model->save()
You can also eliminate step 2 above by passing you $data_array to the save() function:
$this->Model->create();
$this->Model->save($data_array);
NOTE: from the manual (if you aren't using create()):
When calling save in a loop, don’t forget to call clear().
Another way you could create new data would be to make sure the primary key for your model is null in the data set you pass into save, although this is a little less obvious and probably best to stick to the create/[set/]save flow:
$data = array('id' => null, 'somefield' => 'foobar');
$this->Model->save($data); // new record created
In cakephp 2.0 $this->Model->create() create work fine. But if you are using cakephp version 3 or greater then 3. Then follow the below code
$saveData['itemId'] = 1;
$saveData['qty'] = 2;
$saveData['type'] = '0';
$saveData['status'] = 'active';
$saveData = $this->Model->newEntity($saveData);
$this->Model->save($materialmismatch);
In normal case we use patchEntity
$this->Model->patchEntity($saveData, $this->request->data);
It will only save last values of array so you have to use newEntity() with data

Optimizing query with large result set

I have a CakePHP model, let's call it Thing which has an associated model called ItemView. ItemView represents one page view of the Thing item. I want to display how many times Thing has been viewed, so I do the following in my view:
<?php echo count($thing['ItemView']); ?>
This works, however as time goes on the result set of this query is going to get huge, as it's currently being returned like so:
array(
'Thing' => array(
'id' => '1',
'thing' => 'something'
),
'ItemView' => array(
(int) 0 => array(
'id' => '1',
'thing_id' => 1,
'created' => '2013-09-21 19:25:39',
'ip_address' => '127.0.0.1'
),
(int) 1 => array(
'id' => '1',
'thing_id' => 1,
'created' => '2013-09-21 19:25:41',
'ip_address' => '127.0.0.1'
),
// etc...
)
)
How can I adapt the model find() to retrieve something like so:
array(
'Thing' => array(
'id' => '1',
'thing' => 'something',
'views' => 2
)
)
without loading the entire ItemView relation into memory?
Thanks!
So it's pretty straight forward, we can make use of countercache - Cake does the counting for you whenever a record is added into/deleted fromItemView:
Nothing to change in your Thing.php model
Add a new INT column views in your things table.
In your ItemView.php model, add counterCache like this:
public $belongsTo = array(
'Thing' => array(
'counterCache' => 'views'
)
);
Then next time when you do addition/deletion via ItemView, Cake will automatically recalculate the counting and cache into views for you, so the next time when you do the query, you also need to make sure you specify recursive = -1 as what #Paco Car has suggested in his answer:
$this->Thing->recursive = -1;
$this->Thing->find(...); //this will returns array of Thing + the field "views"
// --- OR ---
$this->Thing->find(array(
'conditions' => array(
//... your usual conditions here
),
//... fields, order... etc
//this will make sure the recursive applies to this call, once only.
'recursive' => -1
);

How do I create a CGridView with custom columns from a CArrayDataProvider?

I have an array of models that looks something like this. The models are extensions of the base Yii model class (BazClass), so that's a little bit of a custom solution, but I don't see why it shouldn't work.
$list = array
(
0 => FooClass#1
(
[BazClass:_attributes] => array
(
'FOO_ATTRIBUTE' => '4567'
'BAZ_ATTRIBUTE' => '1234'
'NAME' => 'FOO BAR'
)
[BazClass:_related] => array()
[_md] => null
[CModel:_errors] => array()
[CModel:_validators] => null
[CModel:_scenario] => ''
[CComponent:_e] => null
[CComponent:_m] => null
),
)
I made this a data provider by doing so:
$dataProvider = new CArrayDataProvider($list, array(
'pagination'=>array(
'pageSize'=>10,
),
));
$dataProvider->setData($list);
And try to render it in the view like so. Basically I'm just trying to show a list of the names, with the column named "Name".
$this->widget('zii.widgets.grid.CGridView', array(
'dataProvider'=>$dataProvider,
'columns'=>array(
array(
'header' => 'Name',
'value' => $data->NAME,
),
),
));
The examples in the CGridView documentation make it look like that is possible, but the error I get is:
Either "name" or "value" must be specified for CDataColumn.
Well, I did specify a value, obviously, but it seems to be null. I also tried $data['NAME'] (because I somewhere read that the CArrayDataProvider doesn't return models), but it still evaluates to null.
I also checked that $dataProvider->getData() returns the same list as I passed it.
What gives?
you should place quotes around your value otherwise it gets interpreted in the wrong context
array( 'header' => 'Name', 'value' => '$data->NAME', ),

CakePHP empty field versus not empty field validations

I have the follow validation rule for a file:
modelFile.php
public $validate = array(
'image' => array(
'maxWidth' => array(
'rule' => array('maxWidth', 2000),
),
'maxHeight' => array(
'rule' => array('maxHeight', 2000),
),
'extension' => array(
'rule' => array('extension', array('gif', 'jpg', 'png', 'jpeg')),
),
'filesize' => array(
'rule' => array('filesize', 5120000),
)
)
);
Have a way to skip validations, if image are empty?
You may have to adjust how you check if the image is empty/not uploaded - I'm not sure if what I have is correct. But the idea is to check and unset the validation rule.
public function beforeValidate($options = array()) {
if (empty($this->data[$this->alias]['image']['name'])) {
unset($this->validate['image']);
}
return true;
}
See below URL
cakePHP optional validation for file upload
Or try it
"I assign $this->data['Catalog']['image'] = $this->data['Catalog']['imageupload']['name'];"
So by the time you save your data array, it looks something like this I assume:
array(
'image' => 'foobar',
'imageupload' => array(
'name' => 'foobar',
'size' => 1234567,
'error' => 0,
...
)
)
Which means, the imageupload validation rule is trying to work on this data:
array(
'name' => 'foobar',
'size' => 1234567,
'error' => 0,
...
)
I.e. the value it's trying to validate is an array of stuff, not just a string. And that is unlikely to pass the specified validation rule. It's also probably never "empty".
Either you create a custom validation rule that can handle this array, or you need to do some more processing in the controller before you try to validate it
Ok, as far as I know there is no such code to set this in your $validate variable. So what you are going to have to do is:
In the beforeValidate of the corresponding model add the following piece of code:
<?php
# Check if the image is set. If not, unbind the validation rule
# Please note the answer of Abid Hussain below. He says the ['image'] will probably
# never be empty. So perhaps you should make use of a different way to check the variable
if (empty($this->data[$this->alias]['image'])){
unset($this->validate['image']);
}
I used http://bakery.cakephp.org/articles/kiger/2008/12/29/simple-way-to-unbind-validation-set-remaining-rules-to-required as my main article. But this function doesn't seem to be a default cake variable. The code above should work.

cakePHP build a select box from custom array

So we have a variety of search pages each with different search criteria, so I decided to write a component which will get parameters passed to it from the controller, collect the neccesary data and return an array which I could then set to be used within my view file to populate drop down boxes to filter the criteria.
I have managed to get everything write up to where I must use the cakePHP helper to build a dynamical select box. I am convinced that I am doing something wrong and if there is an easier way to do this and still keep it somewhat dynamic please assist where you can:
// COMPONENT METHOD:
public function filterQueries($parameters) {
// Get defaults from the user:
$criteria = $parameters["custom"];
$defaults = $parameters["defaults"];
// Validate the defaults the user may want and assign them to the return array:
if($defaults != "false") {
foreach($defaults as $key => $value) {
if(array_key_exists($value, $this->defaults)) {
$this->returnArray["defaults"][$value] = $this->defaults[$value];
}
}
}
// Get all data for the custom requested form fields:
if($criteria != false) {
foreach($criteria as $model => $arguments) {
$fields = $arguments["fields"];
$conditions = $arguments["conditions"];
$recursive = $arguments["recursive"];
if(!in_array($model,$this->uses)) {
$useModel = ClassRegistry::init($model);
} else {
$useModel = $this->$$model;
}
$array = $useModel->find("all",array("conditions" => $conditions, "fields" => $fields, "recursive" => $recursive));
$this->returnArray["custom"][$model] = $array;
}
}
return $this->returnArray;
}
The above function will get an array which breaks down either custom searches or defaults (not included above but it all works, it returns the array exactly as I would have expected it.
// The code within my action to get the content from above:
// Load the Filters component to search data:
$search = $this->Components->load("Filter");
// Tell search what you want:
$searchBoxes = array(
"defaults" => array("statuses", "survey_type"),
"custom" => array(
"User" => array(
"fields" => array("User.id","User.first_name", "User.last_name"),
"conditions" => array("User.user_group_id" => "4f847c63-1840-446e-88be-3e4d29566cf0"),
"recursive" => -1
)
)
);
$filterResults = $search->filterQueries($searchBoxes);
$this->set("filters",$filterResults);
So now I get this multi-associative array within my view file (all still fine), but I want to now build example a drop down list of the users based on the array created above, but the outcome is nothing like what I expected:
echo $this->Form->input('user_id',
array(
"type" => "select",
"options" => $filters["custom"]["User"]
)
);
The HTML output is broken and displays it like this:
<option value="last_name">Doe</option>
<option value="first_name">Jihn</option>
<optgroup label="User"> </optgroup>
<optgroup label="1"> </optgroup>
<option value="last_name">Marilyn</option>
<option value="first_name">Monroe</option>
I acknowledge that I do not have a lot of cake experience but cannot understand how to just get the results to :
<option value='USERID'>NAME</option> // Yes I know the names and surnames must be concatinated still
Any advise help or guidance on how to do it, and do it the right way, would greatly be appreciated :)
VARDUMP ON $filters['custom']['users']
array
0 =>
array
'User' =>
array
'id' => string '4f84840e-cda8-4704-8fdf-210729566cf0' (length=36)
'first_name' => string 'Name' (length=4)
'last_name' => string 'Surname' (length=11)
1 =>
array
'User' =>
array
'id' => string '4f8488cb-53e0-4f72-af73-3de229566cf0' (length=36)
'first_name' => string 'Name' (length=6)
'last_name' => string 'Surname' (length=6)
You can enhance your output by doing as follows:
1) for combining two fields of a table, you can use "virtualfields" in the model, as follows: For example if you have the user model, you can define as follows:
public $virtualFields = array(
'full_name' => 'CONCAT(first_name, " ",last_name)'
);
So now the "full_name" field will be got whenever you call the find method of the User model.
2) For getting the data from the table for a select box, you can use the find('list') method. For example for the User model if you need the id,full_name (last and first name combined using the virtual fields) of the table,it can be done as follows :
$this->User->find('list',array('fields'=>array('id','full_name'),'conditions'=>$conditions))
I hope this helps..
Well I guess what you want to do is actually create another array with formatted options.
foreach ($filters["custom"]["User"] as $arr)
{
$options[$arr['id']] = $arr['first_name'] . ' ' . $arr['last_name'];
}
then
echo $this->Form->select('user_id', $options);
I think you need something like this:
$result = Set::combine($filters["custom"]["User"],
'{n}.User.id', // this is the value of select box
array(
'{0} {1}',
'{n}.User.first_name',
'{n}.User.last_name'
) // this is the text of select box
);
pr($result);
$this->form->input('inputname', array('label' => false, 'options' => array('Select optionstype','a','b'), 'class'=>array('form-a', 'b'), 'id' => 'bacid', 'style' => 'width:280px;','value'=>$abc['model']['array_attribute']));
On cakePHP 3, I can't use "virtualFields" yet, but I can use as follows:
//In PostsController
$users = $this->Posts->Users->find('list',
['keyField' => 'id',
'valueField' => 'full_name', //Don't forget to call concatened field here
'conditions' => $conditions,
'order' => $orderBy
]);
//here the magic happens
$concat = $users->func()->concat(
['Users.first_name' => 'identifier',
' ',
'Users.last_name' => 'identifier'
]);
//selecting fields
$users->select(['id', 'full_name' => $concat]);
//sending to view
$this->set('users', $users->toArray());
I hope this helps CakePHP 3 developers

Categories