CakePHP: Triggering Custom Event in Behaviour - events

Because of how my application is built, I need to create event handlers for beforeSaveAssociated and afterSaveAssociated. To allow for this, I've updated AppModel.php to contain the following relevant code:
public function saveAssociated(array $data = null, array $options = array()) {
$this->after_save_options = NULL;
$event = new CakeEvent('Model.beforeSaveAssociated', $this, array(&$data, &$options));
$this->after_save_options = NULL;
$this->getEventManager()->dispatch($event);
if (parent::saveAssociated($data, $options)) {
if (is_array($this->after_save_options)) {
$curData = $this->data;
$this->data = $this->_tempData;
$event = new CakeEvent('Model.afterSaveAssociated', $this, $this->after_save_options);
$this->after_save_options = NULL;
$this->getEventManager()->dispatch($event);
$this->data = $curData;
}
if ($this->_tempData) {
$this->_tempData = FALSE;
}
return TRUE;
}
return FALSE;
}
public function implementedEvents() {
return array_merge(parent::implementedEvents(), array(
'Model.beforeSaveAssociated' => array(
'callable' => 'beforeSaveAssociated',
'passParams' => TRUE,
),
'Model.afterSaveAssociated' => array(
'callable' => 'afterSaveAssociated',
'passParams' => TRUE,
),
));
}
Although this works fine for any beforeSaveAssociated defined within a model class, whenever I define it in a behaviour, it doesn't get triggered. If I update saveAssociated above to trigger Model.beforeSave (a built-in event), it does work, so as far as I can tell, it's not an issue with the behaviour not being properly attached.
Any help is greatly appreciated,

I think this is because the BehaviorCollection is just listening to these events (taken from the class):
public function implementedEvents() {
return array(
'Model.beforeFind' => 'trigger',
'Model.afterFind' => 'trigger',
'Model.beforeValidate' => 'trigger',
'Model.afterValidate' => 'trigger',
'Model.beforeSave' => 'trigger',
'Model.afterSave' => 'trigger',
'Model.beforeDelete' => 'trigger',
'Model.afterDelete' => 'trigger'
);
}
Not the behaviors listen to the events but the collection and triggers them on behaviors. Not 100% sure about that without looking it up but I think that's how it works.
What you could try to do is to make the behavior that needs to receive these events directly to listen the event.

I think the problem is caused because the behaviors extend ModelBehavior and those classes doesn't know about the new methods you created in your AppModel

Related

Drupal 8 Form alter the ajax callback is not working

I am getting my ajax callback in normal custom form, but on form alter its not working.
function sample_ajax_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
if ($form_id === 'node_sampleajax_form' || $form_id === 'node_sampleajax_edit_form') {
$form['field_nametrain']= array(
'#title' => t('training name'),
'#type' => 'select',
'#options' => _load_training(),
'#required' => FALSE,
'#ajax' => [
'callback' => [$this, 'changeOptionsAjax'],
// 'callback' => '::changeOptionsAjax',
'wrapper' => 'second_field_wrapper',
],
);
$form['field_namedomain'] = [
'#type' => 'select',
'#title' => t('Domain program'),
'#options' => $this->getOptions($form_state),
'#prefix' => '<div id="second_field_wrapper">',
'#suffix' => '</div>',
];
return $form;
}
}
function _load_training() {
$training = array('- Select domain training -');
$query = db_select("node__field_trainingname", "a");
$query->fields("a", array('field_trainingname_value', 'entity_id'));
$query->orderBy("a.field_trainingname_value");
$result = $query->execute();
while($row = $result->fetchObject()){
$training[$row->entity_id] = $row->field_trainingname_value;
}
return $training;
}
function changeOptionsAjax(array &$form, FormStateInterface $form_state) {
return $form['field_namedomain'];
}
function getOptions(array &$form, FormStateInterface $form_state) {
$cvvv = $form_state->getValue('field_nametrain');
<!-- return ["shgsh", $form_state->get(['field_nametrain'])]; -->
$options = array('- Select subdomain category -');
$query = db_select("node__field_trainingname", "a");
$query->fields("a", array('field_trainingname_value', 'entity_id'));
$query = db_select("node__field_cms", "b");
$query->fields("b", array('field_cms_value', 'entity_id'));
$query->join("node__field_trainingname", "b", "b.entity_id=a.entity_id");
$query->condition("a.entity_id", $cvvv);
$result = $query->execute();
while($row = $result->fetchObject()){
$options[$row->entity_id] = $row->field_cms_value;
}
return $options;
}
On using $this->getOptions($form_state) it represent the error log it is not an object and throws website encounter error in front end. But on custom form no error came only in formalter it throws error.
Kindly suggest me ideas to apply in form_alter of Drupal 8
The .module file, where your form alter hook is located, is not a class, therefore there is no $this. Your custom form however is a class (usually in your_module/src/Form/YourForm.php), that's why it works there but not in the .module file.
Further reading: http://www.php.net/manual/en/language.oop5.basic.php
and What does the variable $this mean in PHP?
In your case you should be able to just call
'#options' => getOptions($form, $form_state),
And more on a side note: I would strongly recommend to do some code refactoring.
In your custom submit handler, firt get the form object from the form state.
$formObj = $formState->getFormObject();
then call submitForm() on the form object and pass the form and form state variables.
$formObj->submitForm($form, $formState);
and finally, you just need to simply trigger the save() function on the object.
$formObj->save($form, $formState);
So the whole solution is something like
function YOR_CUSTOM_SUBMIT_HANLDLER(array $form, FormStateInterface $form_state) {
/** #var Drupal\user\RegisterForm $entity */
$formObj = $form_state->getFormObject();
$formObj->submitForm($form, $form_state);
$formObj->save($form, $form_state);
}

How to exclude a rule from a rule set

I have a question about picking validation rules.
I stick my validation rules in the model like so
public $validate = array(
'sentence_fields'=> array(
'select_chapter' => array(
'field'=>'select_chapter',
'label'=>'Select chapter',
'rules'=>'required|integer'
),
'source_sentence' => array(
'field'=>'source_sentence',
'label'=>'Source',
'rules'=>'trim|required|max_length[500]'
),
'translated_sentence' => array(
'field'=>'translated_sentence',
'label'=>'Translation',
'rules'=>'trim|required|max_length[500]'
),
'translated_translation' => array(
'field'=>'translated_translation[]',
'label'=>'Select another translation',
'rules'=>'trim|max_length[500]'
)
)
);
Then call it in the controller like so
$validate = $this->sentence_model->validate['sentence_fields'];
$this->form_validation->set_rules($validate);
That was for the create method, but I have an update method which doesn't require the select_chapter set of rules.
Is there a simple way to call this rule set (sentence_fields), but exclude select_chapter for my update method?
Thanks.
If you want to exclude select_chapter for my update method.Just use array's unset() method like this..
$validate = $this->sentence_model->validate['sentence_fields'];
unset($validate['sentence_fields']['select_chapter ']);//unsets your array
$this->form_validation->set_rules($validate);
As the above method works, I would recommend making it a function to allow better readability and ease of use. To do so have a look at the following
public $validate = array(
'sentence_fields'=> array(
'select_chapter' => array(
'field'=>'select_chapter',
'label'=>'Select chapter',
'rules'=>'required|integer'
),
'source_sentence' => array(
'field'=>'source_sentence',
'label'=>'Source',
'rules'=>'trim|required|max_length[500]'
),
'translated_sentence' => array(
'field'=>'translated_sentence',
'label'=>'Translation',
'rules'=>'trim|required|max_length[500]'
),
'translated_translation' => array(
'field'=>'translated_translation[]',
'label'=>'Select another translation',
'rules'=>'trim|max_length[500]'
)
)
);
public function formValidationRules($validation, $unset = array()) {
if($unset) {
return $this->unsetValidation($unset);
} else {
return $this->validate[$validation];
}
}
private function ($unset) {
$validations = $this->validate[$validation];
foreach($unset as $key)
{
unset($validations[$key]);
}
return $validations;
}
This way you can do your validation as follow:
$validate = $this->sentence_model->formValidationRules('sentence_fields', ['select_chapter']);
$this->form_validation->set_rules($validate);

Adding custom message to Zend Framework 2 Callback Validator

I would like to add a custom error message to my Callback Validator below ("Zip Code is required" for example), how would I go about doing this?
$zip = new \Zend\InputFilter\Input('zip');
$zip->setRequired(false);
$zip->getValidatorChain()
->attach(new \Zend\Validator\Callback(function ($value, $context) {
if($context['location_type_id'] == \Application\Model\ProjectModel::$LOCATION_TYPE_ID_AT_AN_ADDRESS)
{
return (isset($value)&&($value!= NULL))? $value: false;
}
return true;
}));
If you need more information, let me know and I will update.
Thanks for your help!
Abor
Just to throw in my two cents, a custom message can also be set via configuration. I often use this when using a factory type approach like so:
'name' => array(
...
'validators' => array(
new \Zend\Validator\Callback(
array(
'messages' => array(\Zend\Validator\Callback::INVALID_VALUE => '%value% can only be Foo'),
'callback' => function($value){
return $value == 'Foo';
}))
)
),
This produces a message like "Bar can only be Foo".
Look closely at the \Zend\Validator\Callback::INVALID_VALUE key, this is a constant defined in \Zend\Validator\Callback:
const INVALID_VALUE = 'callbackValue';
Which is used in that class to set the messages used by the validator:
protected $messageTemplates = array(
self::INVALID_VALUE => "The input is not valid",
self::INVALID_CALLBACK => "An exception has been raised within the callback",
);
Which means you can safely use \Zend\Validator\Callback::INVALID_VALUE => 'Custom message'
I'm not sure whether this breaks a coding principle, somebody please correct me if it does.
You can do it that way :
$callback = new \Zend\Validator\Callback(function ($value) {
// Your validation logic
}
);
$callback->setMessage('Zip Code is required');
$zip = new \Zend\InputFilter\Input('zip');
$zip->setRequired(false);
$zip->getValidatorChain()->attach($callback);
Thanks to jchampion for his help.
$zip = new \Zend\InputFilter\Input('zip');
$zip->setRequired(false);
$callback = new \Zend\Validator\Callback(function ($value, $context) {
if($context['location_type_id'] == \Application\Model\ProjectModel::$LOCATION_TYPE_ID_AT_AN_ADDRESS)
{
return (isset($value)&&($value!= NULL))? true: false;
}
return true;
});
$callback->setMessage('Zip Code is required');
$zip->getValidatorChain()->attach(new \Zend\Validator\NotEmpty(\Zend\Validator\NotEmpty::NULL));
$zip->getValidatorChain()->attach($callback);

Cakephp validation issue

Hey guys got an issue with Cakephp validation..
I want to know why is partytwo validation going straight to false?
Here is my Relationship model:
<?php
class Relationship extends AppModel{
var $name='Relationship';
public $useTable = 'relationships_users';
public $primaryKey = 'id';
var $validate = array(
'date' => array(
'rule' => array('datevalidation', 'systemDate'),
'message' => 'Current Date and System Date is mismatched'
),
'partytwo'=>array(
'partytwoExists'=>array(
'rule'=> 'userExists',
'message'=>'That username doesnt exist.'
)
)
);
function datevalidation( $field=array(), $compare_field=null ) {
if ($field['date'] > $compare_field)
return TRUE;
else
return FALSE;
}
function userExists($check) {
$userExists= $this->find('count', array('conditions'=>$check));
if($userExists == 1) {
return TRUE;
}else{
return FALSE;
}
}
...
According to the CakePHP book Adding Your Own Validation Methods section, a custom rule that wrote like this
'rule' => array('datevalidation', 'systemDate')
means Cake will runs your datevalidation method like this:
$valid = $Relationships->datevalidation(array(
'date' => 'some user input value'
), 'systemDate');
With the same fashion,
'rule' => array('userExists')
causes Cake to run
$valid = $Relationships->userExists(array(
'partytwo' => 'some user input value'
));
(Simulated calls. Actual calling is using dispatchMethod at line 3155 of Model.php)
So you are most probably need to rewrite your datevalidation method. Furthermore, your code
$userExists= $this->find('count', array('conditions'=>$check));
$userExists can return you a number larger or equal to 0. Your logic is wrong if it returns 2 or more. Consider Model::hasAny instead. It could be the reason why it is always validated as false for your case.

Setting an Error Message located in language file

In a form I have a callback function that checks if a number is_available.
If it returns TRUE it shows an error message.
The callback is working but it displays : lang:shortcodes.not_unique instead of the content given in a separate file.
I can't figure out what is wrong and didn't find about that in the user guide.
Thank you for your help.
public function __construct()
{
parent::__construct();
// Load all the required classes
$this->load->model('shortcodes_m');
$this->load->library('form_validation');
$this->lang->load('shortcodes');
// Set the validation rules
$this->item_validation_rules = array(
array(
'field' => 'number',
'label' => 'lang:shortcodes.number',
'rules' => 'trim|max_length[100]|required|numeric'
),
array(
'field' => 'name',
'label' => 'lang:shortcodes.name',
'rules' => 'trim|max_length[100]|required|callback_shortcodes_check'
)
);
}
public function shortcodes_check($str)
{
if($this->shortcodes_m->is_available($str) == TRUE)
{
$this->form_validation->set_message('shortcodes_check','lang:shortcodes.not_unique');
return FALSE;
}
else
{
return TRUE;
}
}
You need to fetch the line from the language file. The docs don't make any mention of being able to use field name translation with the set_message() method. Use:
$this->lang->line('not_unique');

Resources