I am trying to invalidate a field by a condition in controller instead of Model.
$this->Model->invalidate('check_out_reason', __('Please specify check out reason.', true));
The above won't work to invalidate the field. Instead, I need the below:
$this->Model->invalidate('Model.check_out_reason', __('Please specify check out reason.', true));
However, if I wish get the error message show up in the "field" itself ($this->model->validationErrors), it needs to be "check_out_reason" instead of "Model.check_out_reason". That means, I can't get the error message to show up in the field itself if I wish to invalidate the input in controller.
May I know is this a bug in CakePHP?
i created a test controller called "Invoices", just for testing, and i developed the following function
public function index(){
if (!empty($this->request->data)) {
$this->Invoice->invalidate('nombre', __('Please specify check out reason.'));
if ($this->Invoice->validates()) {
// it validated logic
if($this->Invoice->save($this->request->data)){
# everthing ok
} else {
# not saved
}
} else {
// didn't validate logic
$errors = $this->Invoice->validationErrors;
}
}
}
i think it worked for me
Change the field "nombre" for your field called "check_out_reason" to adapt the function to your code
I found a workaround for manual invalidates from controller. Reading a lot on this issue I found out that the save() function doesn't take in consideration the invalidations set through invalidate() function called in controller, but (this is very important) if it is called directly from the model function beforeValidate() it's working perfectly.
So I recommend to go in AppModel.php file and create next public methods:
public $invalidatesFromController = array();
public function beforeValidate($options = array()) {
foreach($this->invalidatesFromController as $item){
$this->invalidate($item['fieldName'], $item['errorMessage'], true);
}
return parent::beforeValidate($options);
}
public function invalidateField($fieldName, $errorMessage){
$this->invalidatesFromController[] = array(
'fieldName' => $fieldName,
'errorMessage' => $errorMessage
);
}
After that, make sure that your model's beforeValidate() function calls the parent's one:
public function beforeValidate($options = array()) {
return parent::beforeValidate($options);
}
In your controller for invalidating a field use next line:
$this->MyModel->invalidateField('fieldName', "error message");
Hope it helps! For me it's working!
Related
The following does work:
// app/Controller/UsersController.php
$this->User->save(array('pwd'=>$new_pwd),false);
The following does not work:
// app/Controller/UsersController.php
$this->User->setPassword($new_pwd);
The User model has the beforeSave() which works and the custom method setPassword() that does not:
// app/Model/User.php
public function beforeSave($options = array()) {
if (isset($this->data[$this->alias]['pwd'])&&!empty($this->data[$this->alias]['pwd'])) {
$new_password = $this->data[$this->alias]['pwd'];
$passwordHasher = new BlowfishPasswordHasher();
$this->data[$this->alias]['pwd'] = $passwordHasher->hash($new_password);
}
return true;
}
public function setPassword($new_password) {
$passwordHasher = new BlowfishPasswordHasher();
$result = $this->save(array(
'pwd' => $passwordHasher->hash($new_password),
), false);
return $result;
}
So the setPassword() is more or less identical yet whenever I try to log in with the password saved that way, $this->Auth->login() returns false. I can see the password hash updated in the database though.
Am I missing something? Please help
setPassword() internally also calls beforeSave() via save().
It becomes pretty clear that you are hashing it twice then, making it impossible to be used anymore.
I'm working on a new project using CakePHP 3.0.
I'm using the authentication component and whenever a user logs in, I'm updating the value of the field visited.
UsersController:
public function login() {
if ($this->request->is('post')) {
$user = $this->Auth->identify();
if ($user) {
$this->Auth->setUser($user);
$this->Users->setVisited($user['id']);
return $this->redirect($this->Auth->redirectUrl());
}
$this->Flash->error('Your username or password is incorrect.');
}
}
UsersTable:
public function setVisited($id) {
$user = $this->findById($id)->first();
$user->visited = Time::now();
if($this->save($user)) {
return true;
}
return false;
}
Now, I would like to do this save without updating the value of the field modified. I've tried the approach used in previous versions of cake:
$user->modified = false;
It doesn't work though, throwing and error: Call to a member function format() on a non-object because datetime fields are now treated as objects I guess.
Any help would be greatly appreciated,
Paul
You have a couple ways of doing this. What you want is actually to avoid calling callbacks when saving the entity. For those cases you have updateAll
$this->updateAll(['visited' => Time::now()], ['id' => $id]);
You can also do the same as before, but you will need to disable the Timestamp behavior before saving:
$this->behaviors()->unload('Timestamp');
I would recommend using updateAll
I am completely new to PHP unit testing (using PHPUnit) and CakePHP(2) as a framework, and I'm coming back to PHP after 5 years away.
I've got a website up and running and am writing unit tests as I go along as best practice. However, xdebug is showing that one of my clauses is not covered when I believe I am calling it and I just can't see why. I've googled the hell out of all search terms I can think of and re-read the relevant sections of the cookbook and (while I've learned a lot of other useful things) I didn't find an answer so am hoping that a simple answer is forthcoming from someone in the know :)
Here are the relevant sections of code:
Controller:
<?php
App::uses('AppController', 'Controller');
// app/Controller/ClientsController.php
class ClientsController extends AppController {
/* other functions */
public function edit($id = null) {
if (!$id) {
$this->Session->setFlash(__('Unable to find client to edit'));
return $this->redirect(array('action'=>'index'));
}
$client = $this->Client->findById($id);
if(!$client) {
$this->Session->setFlash(__('Unable to find client to edit'));
return $this->redirect(array('action'=>'index'));
}
if ($this->request->is('post')) {
$this->Client->id = $id;
if ($this->Client->saveAll($this->request->data)) {
$this->Session->setFlash(__('Client has been updated.'));
return $this->redirect(array('action'=>'index'));
} else {
$this->Session->setFlash(__('Unable to update client'));
}
}
if (!$this->request->data) {
$this->request->data = $client;
$this->Session->setFlash(__('Loading data'));
}
}
}
Test:
<?php
// Test cases for client controller module
class ClientsControllerTest extends ControllerTestCase {
public $fixtures = array('app.client');
/* other tests */
public function testEdit() {
// Expect success (render)
$result = $this->testAction('/Clients/edit/1');
debug($result);
}
}
?>
The code executes as expected. If I browse to "/Clients/edit/1", the flash message (Loading data) I expect is displayed indicating that there was no request data, so it's loaded from the $client. The correct data displays in the edit form.
When I call from within my test, I get a success message that the test has passed but xdebug code coverage is showing the if (!$this->request->data) { .. } clause is not covered, and no errors are apparent.
This seems counter-intuitive to me, so in a hope to avoid frustration with future (more complex) unit tests - can anyone explain why the test would pass but not execute this clause when it is called during normal access of the page?
(The fixture is correct both in terms of data structure and inserting the data before I'm attempting to edit it. Calling edit() from a test case with no id or an invalid id correctly executes the relevant clauses, as does passing data that does not pass validation.)
I've got a similar problem and I solved by adding the second parameter to testAction():
$this->testAction('/Clients/edit/1', array('method' => 'get'));
Also you may want to change your
if ($this->request->is('post') {
...
}
if (!$this->request->data) {
...
}
To:
if ($this->request->is('post') {
...
} else {
...
}
Hope it helps.
I am trying to modify FormHelper's behaviour to meet my application requirements. I would like to use native FormHelper but for all input I need to add some short message providing help to the user and describing the particular field.
My idea is to create my own helper and pass a help message as an argument. This function will modify form's inputDefaults setting and call a native FormHelper input function.
For example:
class MsgFormHelper extends AppHelper {
public function input($name, $message, $options) {
$this->_View->Form->_inputDefaults['after'] .= '<div>'.$message.'</div>';
return $this->_View->Form->input($name, $options);
}
}
But this solution notices this error:
Notice (8): Indirect modification of overloaded property
FormHelper::$_inputDefaults has no effect...
Is there some way how to modify "after" value in form's inputDefaults setting?
I probably found a solution (it works but I am not sure it doesn't violate some CakePHP's principles). Thanks for your opinions.
class MsgFormHelper extends AppHelper {
public function __construct(View $view, $settings = array()) {
parent::__construct($view, $settings);
}
public function input($name, $message, $options) {
$add_message = true;
if (isset($options['after'])) {
$options['after'] = trim($options['after']);
$add_message = empty($options['after']);
}
if ($add_message) {
$options['after'] = '<div class="input-help">' . $message . '</div>' . $this->_View->Form->_inputDefaults['after'];
}
return $this->_View->Form->input($name, $options);
}
}
You should be extending the FormHelper itself not AppHelper. Then in your controller use the aliasing feature so that you still use $this->Form->input() in your views but it would actually refer to your custom helper.
public $helpers = array('Form' => array('className' => 'MsgForm'))
I'm trying to log every write operation so I'm using the afterSave and afterDelete callbacks in AppModel. Basically I need to log(for the moment): the model , the controller function, the loggedin user data and the remote ip
It seems that I was able to get all of them but I don't know how to get the controller function name.
This is the aftersave function I have now:
public function afterSave($created) {
App::uses('Folder', 'Utility');
$month = date("y-m");
if(!is_dir('../tmp/logs/'.$month)) {
$dir = new Folder('../tmp/logs/'.$month, true);
}
App::uses('CakeSession', 'Model/Datasource');
$user_id = CakeSession::read('Auth.User.username');
if($created) {
$id = 'New';
} else {
$id = $this->data[$this->alias]['id'];
}
$str = 'WRITE Action. Model: '.$this->alias.'. Controller: functon_name. ID:'.$id.'. Username: '.$user_id.'. Client IP: '.$this->getIP();
CakeLog::write($month.'/'.date("d-m-y"), $str);
}
Thanks
You're doing this on the model, which has no knowledge of the controller (and really shouldn't). I'd suggest copying the CakeRequest object to the model so you have that information. Here's one way:
//controller
function beforeFilter() {
$this->{$this->modelClass}->request = $this->request;
}
Then you can access the request object from the model. Use $this->request['params']['action'] to get the current dispatched action.
It's worth suggesting that you might want to move this to the read() method on a custom datasource, as afterSave() can possibly be skipped and therefore not logged.