CakePHP 3 - beforeSave callback not working on edit - cakephp

I have a beforeSave-callback which is called just fine whenever I create a new entity. However when I am editing, it's not called at all.
Could not find anything in the documentation that could be helpful.
Here's my edit function:
public function edit($id = null) {
if (!$id) {
throw new NotFoundException(__('Invalid article'));
}
$article = $this->Articles->get($id);
if ($this->request->is(['post', 'put'])) {
$this->Articles->patchEntity($article, $this->request->data);
if ($this->Articles->save($article)) {
$this->Flash->success(__('Your article has been updated.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to update your article.'));
}
$this->set('article', $article);
}

The beforeSave function will be triggered only if the data you post/edit is modified.
//triggers only if an entity has been modified
public function beforeSave(Event $event, Entity $entity)
{
if($entity->isNew()) {
//on create
} else {
//on update
}
}

I also had the problem that the tutorial got stuck on this point, but I used the bin/cake bake command to autogenerate the ArticlesTable code and it added this validator:
$validator
->scalar('slug')
->maxLength('slug', 191)
/*->requirePresence('slug', 'create')*/
->notEmptyString('slug')
->add('slug', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
When I commented the requirePresence() it solved this issue for me. If you have requirePresence('fieldName', 'create') for validation, you will get an error if you don't have that field on a post when creating the new Article entity.

Yes, your need use Event and Entity object:
check this example:
// src/Model/Table/ArticlesTable.php
use Cake\Event\Event;
use Cake\ORM\Entity;
public function beforeSave(Event $event, Entity $entity) {
if ($entity->isNew()) {
return true;
}
// edit code
}

Another way to write the same code:
public function beforeSave(\Cake\Event\Event $event, \Cake\ORM\Entity $entity, \ArrayObject $options){
if($entity->isNew()) {
// on create
} else {
// on update
}
}

Related

CakePHP access uploaded file after validation

I have a field 'screenshot' that when I try to access in beforeSave callback the field it's empty.
The thing I do is access the $data on the beforeMarshal callback and store the array
into a model setting, then I can access that in the beforeSave and set 'screenshot' field to filename.ext if move_uploaded_file is true.
This is the current code:
Model
// Using CakePHP 3.8.5
public function validationDefault(Validator $validator)
{
$validator
->allowEmptyFile('screenshot', 'update')
->uploadedFile('screenshot' , [
'types' => ['image/jpeg', 'image/jpg', 'image/pjpeg'],
'maxSize' => 1000000 // 1MB
]);
return $validator;
}
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
if (isset($data['screenshot']) && $data['screenshot']['error'] === UPLOAD_ERR_OK) {
$this->config([ 'file_array' => $data['screenshot'] ])
}
}
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
...
$file = $this->config([ 'file_array');
if (move_uploaded_file($file['tmp_name'], $file_path)) {
return true;
} else {
throw new Exception(__('Unable to move...'));
}
}
Form
<?= $this->Form->create($project, ['type' => 'file']) ?>
<?= $this->Form->control('screenshot', ['type' => 'file', 'class' => 'form-control-file']) ?>
<?= $this->Form->button(__('Submit'), ['class' => 'btn btn-primary col-md-3 offset-md-9']) ?>
<?= $this->Form->end() ?>
The code I expected to work
public function validationDefault(Validator $validator)
{
$validator
->allowEmptyFile('screenshot', 'update')
->uploadedFile('screenshot' , [
'types' => ['image/jpeg', 'image/jpg', 'image/pjpeg'],
'maxSize' => 1000000 // 1MB
]);
return $validator;
}
public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options)
{
...
$file = $entity->screenshot; // this is empty
if (move_uploaded_file($file['tmp_name'], $file_path)) {
return true;
} else {
throw new Exception(__('Unable to move...'));
}
}
Why is $entity->screenshot empty on BeforeSave?
Is this the correct way to do this?
At marshalling time (when patching/creating entities), CakePHP will cast/convert the input according to the database types mapped for the fields.
For your database field screenshot, which is VARCHAR, that would be \Cake\Database\Type\StringType, which returns an empty string for arrays. The reasoning being that the marshalling stage shouldn't cause "crashes", but ideally create entities with data compatible to the respective database types, which can finally be validated via application rules if necessary, for which the errors can be as easily presented to the user as the ones for the validation rules.
The two most popular ways to handle this IMHO are:
Using a different field name for the upload, one that isn't mapped to an existing database column, eg something like screenshot_upload
Using a custom database type for the field, one that doesn't transform the array
Personally I prefer the former.

Cakephp 3.x redirect not working in the same controller using another method in same function

I am using cakephp 3.x and i have an edit function in my controller in which i am checking whether id is in the query string or not as well as whether is it exists in the database record or not. Here below is my code which is working perfectly fine.
UsersController.php
public function edit($id = null)
{
// first checking whether id sent or not ..
if(empty($this->request->params['pass']))
{
$this->Flash->error('Invalid Action');
return $this->redirect(['action' => 'index']);
}
// Now checking whether this user id exists or
$check_user = $this->Users
->find()
->where(['user_id' => $id])
->toArray();
if(!$check_user)
{
$this->Flash->error('Invalid Id, User not found');
return $this->redirect(['action' => 'index']);
}
$user = $this->Users->get($id);
// And so on
}
The thing is, i am using this same code in many other functions to check the same thing so i had comeup creating a common function in the same controller and use it in multiple functions like below.
UsersController.php (Updated)
public function checkId($id)
{
// first checking whether id sent or not ..
if(empty($this->request->params['pass']))
{
$this->Flash->error('Invalid Action');
return $this->redirect(['action' => 'index']);
}
// Now checking whether this user id exists or
$check_user = $this->Users
->find()
->where(['user_id' => $id])
->toArray();
if(!$check_user)
{
$this->Flash->error('Invalid Id, User not found');
return $this->redirect(['action' => 'index']);
}
}
public function edit($id = null)
{
$this->checkId($id);
}
Now if i execute the url in my browser http://localhost/5p_group/users/edit/ , i get this error saying Record not found in table "users" with primary key [NULL]
Can someone guide me how to fullfil both these 2 conditions (check id in the url or not as well as is it valid id or not) using common function which i have created above .. it is working absolutely fine if i put that code inside my same edit() function.
Any help or suggestion will be highly appreciated.
Thanks
In your code you forgot to add function parameter $id, you already use it in the query
public function checkId()
change to
public function checkId($id)
[Update]
Here is also a function return issue
if(empty($id))
{
$this->Flash->error('Invalid Action');
return $this->redirect(['action' => 'index']);
}
change to >>
if(empty($id))
{
$this->Flash->error('Invalid Action');
$this->response = $this->redirect(['action' => 'index']) ;
$this->response->send () ;
die () ;
}

Cakephp 3 : Error while creating and saving multiple entities from file input

I have a problem with my cakephp3 app. I try to import some subscribers from a CSV file.
I use a Behavior to import my file (https://github.com/ProLoser/CakePHP-CSV). I haven't manage to load the plugin, so I have put the Behavior file on my project under src/Model/Behavior.
I have added the behavior to my SubscribersTable.
I have created a new view (import/export) and I have added this method to my SubscribersController :
public function importExport()
{
if ($this->request->is('post'))
{
$data = $this->request->data;
if(!empty($data['import_file']['name']))
{
$file = $data['import_file'];
if ((isset($file['tmp_name']) &&($file['error'] == UPLOAD_ERR_OK)))
{
$subscribersData = $this->Subscribers->importCsv($file['tmp_name']);
$entities = $this->Subscribers->newEntities($subscribersData);
$table = $this->Subscribers;
$table->connection()->transactional(function () use ($table, $entities) {
foreach ($entities as $entity) {
$table->save($entity, ['atomic' => false]);
}
});
}
}
}
}
The data I get seems to be good, as the entities created, but, when I call $table-save(), I have this error :
Table "App\Model\Table\SubscribersTable" is not associated with "request"
I have read some other questions on stackoverflow, but I don't understand why I have this error. I am new to cakephp so I don't understand everything...
If can someone help me...
Thanks !!
EDIT : It seems during the debug, the behavior is strange. Maybe this error has nothing to see with my real problem.
Here is the debug timeline :
$table->save() call:
if ($options['atomic']) {
$success = $connection->transactional(function () use ($entity, $options) {
return $this->_processSave($entity, $options);
});
} else {
===> $success = $this->_processSave($entity, $options);
}
...
$this->_processSave call :
$data = $entity->extract($this->schema()->columns(), true);
and during the extract, no column is retrieved because
public function extract(array $properties, $onlyDirty = false)
{
$result = [];
foreach ($properties as $property) {
if (!$onlyDirty || $this->dirty($property)) {
$result[$property] = $this->get($property);
}
}
return $result;
}
$onlyDirty = true and $this->dirty($property)) return false.
So, when this function is called
$success = $this->_insert($entity, $data);
as the data is null, nothing is saved.
I don't really understant the concept of dirty. In the doc, it seems it is usefull when working with BelongToMany, but this element has no link with other tables, so if someone can clarify this concept ?
SubscribersTable :
<?php
namespace App\Model\Table;
use App\Model\Entity\Subscriber;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Subscribers Model
*
*/
class SubscribersTable extends Table
{
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config)
{
parent::initialize($config);
$this->table('subscribers');
$this->displayField('email');
$this->primaryKey('email');
$options = array(
// Refer to php.net fgetcsv for more information
'length' => 0,
'delimiter' => ',',
'enclosure' => '"',
'escape' => '\\',
// Generates a Model.field headings row from the csv file
'headers' => true,
// If true, String $content is the data, not a path to the file
'text' => false,
);
$this->addBehavior('Csv', $options);
}
/**
* Default validation rules.
*
* #param \Cake\Validation\Validator $validator Validator instance.
* #return \Cake\Validation\Validator
*/
public function validationDefault(Validator $validator)
{
$validator
->add('email', 'valid', ['rule' => 'email'])
->allowEmpty('email', 'create');
$validator
->allowEmpty('contact');
$validator
->allowEmpty('company');
$validator
->allowEmpty('postal_code');
$validator
->add('vip', 'valid', ['rule' => 'boolean'])
->allowEmpty('vip');
$validator
->add('indoor', 'valid', ['rule' => 'boolean'])
->allowEmpty('indoor');
$validator
->add('live', 'valid', ['rule' => 'boolean'])
->allowEmpty('live');
$validator
->add('prod', 'valid', ['rule' => 'boolean'])
->allowEmpty('prod');
$validator
->allowEmpty('localisation');
$validator
->allowEmpty('family');
return $validator;
}
/**
* Returns a rules checker object that will be used for validating
* application integrity.
*
* #param \Cake\ORM\RulesChecker $rules The rules object to be modified.
* #return \Cake\ORM\RulesChecker
*/
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
return $rules;
}
}
This error likely comes from inside your SubscribersTable.php.
If you have $this->request ... in that file, it will throw that error because it's attempting to find request as an associated model, since it's not a valid method to call from a model.
Or, if it's in your controller, you likely have $this->Subscribers->request...
It could also be from inside another Model if you refer to it like this: $this->Subscribers->request ...
Ok.
As I thought, the problem wasn't this error (probably due to the use of the debugger), but it was the structure of my input file.
The input file must be formatted as indicated in this document :
http://book.cakephp.org/3.0/en/orm/saving-data.html#converting-multiple-records
The problem has been resolved !
Thanks again for you help !

CakePHP 3 created and modified not working

I have a model (Addresses) with the following fields:
id: char(36) #using uuid
name: varchar(150)
#city, state, etc here
created: timestamp
modified: timestamp
In my AddressesTable class I have:
public function initialize(array $config)
{
$this->table('addresses');
$this->addBehavior('Timestamp');
}
In my controller, I have this in the edit method:
public function edit($id = null) {
$addressesTable = TableRegistry::get('Addresses');
$address = $addressesTable->get($id);
if ($this->request->is(array('post','put'))) {
$address = $addressesTable->patchEntity($address, $this->request->data);
if ($addressesTable->save($address)) {
$this->Flash->success('The address has been saved.');
return $this->redirect(['action' => 'addresses']);
} else {
$this->Flash->error('The address could not be saved. Please, try again.');
}
}
$this->set(compact('address'));
}
The problem I'm running into is this. According to everything I've read, this should 'update' the record (which it does), and update the 'modified' field in the DB to the current timestamp (which it also does). However, in addition, it also updates the created timestamp (which, it should NOT do).
What am I missing here?
I need this to update ONLY the modified column and NOT the created column on save.
I had the same problem using CakePHP 3.4. I solved using this in my Model/Table:
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
}
More informations here: https://book.cakephp.org/3.0/en/tutorials-and-examples/blog/part-two.html

which method of model to update the row in cakephp

i have trouble in creating method in controller of cakephp to update the my existing row in a table, can anyone suggest me appropriate model method to update the row in table
<?php
class UsersController extends AppController
{
public function update($id)
{
if(isset($_REQUEST['update']))
{
// method of model to update the row
}
else
$this->set('user',$this->User->find('first',array('conditions'=>array('id'=>$id))));
}
}
?>
http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-save-array-data-null-boolean-validate-true-array-fieldlist-array
$this->User->id = $id;
$this->User->save($this->request->data);
Try the code.......
<?php
class UsersController extends AppController {
public function update($id = null) {
if ($id) {
if ($this->request->is('post')) {
$this->User->id = $id;
$this->User->save($this->request->data);
} else {
$this->set('user', $this->User->find('first', array('conditions' => array('id' => $id))));
}
}
}
}
?>
#burzum after reading the tutorial which link provide by you i found the solution to the my problem, in model updateAll() method available in model by using this i have update row of the table.
public function update($id)
{
if(isset($_REQUEST['update']))
{
$this->User->id=$id;
if($this->User->updateAll(array('User.fname'=>"'".$_REQUEST['fname']."'",'User.lname'=>"'".$_REQUEST['lname']."'",'User.email'=>"'".$_REQUEST['email']."'"),array('id'=>$id)))
echo '<script>alert("update successfully")</script>';
else
echo '<script>alert("failes to update ")</script>';
}
else
$this->set('user',$this->User->find('first',array('conditions'=>array('id'=>$id))));
}

Resources