CakePHP 3, Very Basic Save Not Working - cakephp-3.0

For some reason the automated conversion of $this->request->data to an entity is not working. I created a very basic save in order to test it.
$users = TableRegistry::get('VendorName/Users.Users');
$this->request->data = ['username' => 'someting', 'password' => 'somethingelse'];
$user = $users->newEntity($this->request->data);
if ($users->save($user)) { }
This is the SQL that that produces::
INSERT INTO users (created, modified, id) VALUES (:c0, :c1, :c2)
I've tried making $this->request->data as a function, eg. $this->request->data()
I've tried explicitly adding the entity value. Eg. $user->username = 'someting'; and it works when I do this. But of course, I don't want to explicitly state every single field in every single save function, so why isn't $this->request->data converting automatically?
This is the what $user looks like
object(VendorName\Users\Model\Entity\User) {
'[new]' => true,
'[accessible]' => [],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'VendorName/Users.Users'
}

The problem is that you've specified that no fields in the Entity are allowed to be mass assigned (See http://book.cakephp.org/3.0/en/orm/entities.html#mass-assignment), by having protected $_accessible = []; in your Entity class (VendorName\Users\Model\Entity\User).
It sounds like you want all properties to be mass assignable, so what you probably want is protected $_accessible = ['*' => true];.
Just note that this is dangerous as it can allow anybody to alter their form slightly and then modify any other entity. A better setting is protected $_accessible = ['id' => false, '*' => true];

Related

Yii2 save Log into Database

i have some problem with Log with Yii2.
I set Log Targets in this way:
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'flushInterval' => 1,//test
'targets' => [
[
'class' => 'common\components\SaDbTarget',
'levels' => ['error', 'warning','trace','info'],
'exportInterval' => 1,//test
//'categories' => ['application'],
/*'except' => [
'yii\db\*',
],*/
],
],
],
I create my SaDbTarget extending DbTarget Class, and this is working fine because i found in my table some log.
After that, in a controller i tried to set a log like this way
public function actionIndex(){
$searchModel = new CategorySearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
Yii::trace('trace log', __METHOD__);
Yii::warning('warning log');
// here is the rest of the code
}
I can see this 2 logs into the debug toolbar, but not in my db table.
According to the Docs
To make each log message appear immediately in the log targets, you
should set both flushInterval and exportInterval to be 1
I tried to set this values but still doesn't work.
I don't know what am I doing wrong.
UPDATE
This is my SaDbTarget
namespace common\components;
use Yii;
use yii\log\DbTarget;
use yii\log\Logger;
class SaDbTarget extends DbTarget{
//set custom table db for saving log
public $logTable = 'authlog';
//overwrite export();
public function export(){
$tableName = $this->db->quoteTableName($this->logTable);
$sql = "INSERT INTO $tableName ([[authlog_login]], [[authlog_ip]], [[authlog_area]], [[authlog_act]], [[authlog_time]], [[authlog_data]])
VALUES (:login, :ip, :area, :act, :time, :data )";
$command = $this->db->createCommand($sql);
//Get username
$user=Yii::$app->user->getId();
//Get user ip address
$ip = Yii::$app->request->getUserIP();
//Get area/controller
$controller=Yii::$app->controller->uniqueId;
//Get action
$event= Yii::$app->controller->module->requestedAction->id;
//Set timezone
$time = Yii::$app->formatter->asDate('now', 'php:Y-m-d H:i:s');
//Set Data
$data = $this->messages[0];
$command->bindValues([
':login' => $user,
':ip' => $ip,
':area' => $controller,
':act' => $event,
':time' => $time,
':data' => $data,
])->execute();
}
Could you check wether the 'log' component is set on bootstrap?
I think this could be the issue that the dispatcher is not setup and does not pickup the target.
So the bootstrap should look like
[
'bootstrap' => ['log'],
'components' => [
'log' => [
...
],
....
]
Another way would be to check in the debugger if the dispatcher on the logger is configured correctly.

About CakePHP 3 BelongsToMany issue

I have an issue with the following case :
Users Table
id, profile_id, name, created, modified
Profiles Table
id, first_name, last_name, gender
Businesses table
id, name, created, modified
I have a relation table many to many to link profiles to businesses : businesses_profiles
id, business_id, profile_id, created, modified
When i try to create a new business, I would like to link directly logged in user profile id to the business I'm creating.
in my profileTable, I've added in initialize() :
$this->belongsToMany('Businesses', [
'alias' => 'Businesses',
'foreignKey' => 'profile_id',
'targetForeignKey' => 'business_id',
'joinTable' => 'businesses_profiles'
]);
in my businessesTable, I've also put in initialize() method:
$this->belongsToMany('Profiles', [
'alias' => 'Profiles',
'foreignKey' => 'business_id',
'targetForeignKey' => 'profile_id',
'joinTable' => 'businesses_profiles'
]);
In each entities Business & Profile, I put respectively in right context :
protected $_accessible = [
'*' => true,
'id' => false,
'businesses' => true,
'_joinData' => true
];
and :
protected $_accessible = [
'name' => true,
'slug' => true,
'active' => true,
'hash' => true,
'data' => true,
'approved' => true,
'created' => true,
'modified' => true,
'profiles' => true,
'_joinData' => true
];
Nothings work about saving in my businesses_profiles table.
Thanks in advance for your help,
Best,
Laurent.
Thanks a lot for your help. I've found the solution by using the link() method provided by CakePHP.
I'll share my add function here, if it can help others :
public function add()
{
$business = $this->Businesses->newEntity();
if ($this->request->is('post')) {
$business = $this->Businesses->patchEntity($business, $this->request->getData());
$business->set('hash');
$business->set('active', 0);
$business->set('slug');
$profile = TableRegistry::getTableLocator()->get('Profiles')->get($this->_currentUser->profile_id);
if ($this->Businesses->save($business)) {
**$this->Businesses->Profiles->link($business, [$profile]);**
$this->Flash->success(__('The business has been saved.'));
return $this->redirect(['action' => 'index']);
}
else
{
debug($business->getErrors()); // debug
$this->Flash->error(__('The business could not be saved. Please, try again.'));
}
}
$this->set(compact('business'));
}

creation time is invalid when update in yii2

when I want to update form in yii2
It tells me this error
creation time is invalid -- update time is invalid
What do you think is it?
public function beforeSave($insert)
{
$date = new \DateTime();
$date->setTimestamp(time());
$date->setTimezone(new \DateTimezone('Istanbul'));
$this->update_time = $date->format('Y-m-d H:i:s');
if($this->isNewRecord)
$this->creation_time = $date->format('Y-m-d H:i:s');
return parent::beforeSave($insert);
}
My problem has been resolved
change date to safe in rules
[['creation_time', 'update_time'], 'safe'],
Your code is correct "Nader" but some time assigning the value not work so just remove the value or use this
public function behaviors()
{
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'create_time',
'updatedAtAttribute' => 'update_time',
//'value' => new Expression('NOW()'),
],
];
}
Just comment the value,
it will work.
For more detail https://www.yiiframework.com/doc/api/2.0/yii-behaviors-timestampbehavior
Just could just use the available TimestampBehavior behavior
use yii\behaviors\TimestampBehavior;
use yii\db\Expression;
use yii\db\ActiveRecord;
class Mymodel extends ActiveRecord{
public function behaviors() {
return [
[
'class' => TimestampBehavior::className(),
'createdAtAttribute' => 'creation_time',
'updatedAtAttribute' => 'update_time',
'attributes' => [
ActiveRecord::EVENT_BEFORE_INSERT => 'creation_time',
ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time',
],
'value' => new Expression('NOW()')
]
];
}
}
Remove any validation rules from your model you may have for this two attributes
creation_time and update_time

order not working with sortWhitelist

Using CakePHP 3.5.3
Hi,
The below code works works as it should and displays the result set and orders it by the due_date asc.
public $paginate = [
'limit' => 5,
'order' => [
'Activities.due_date' => 'asc'
]
];
public function index()
{
$session = $this->request->session();
// Declare client id 1
$cidOne = null;
$cidOne = $session->read('Cid.one');
// NOTE* DON'T USE ORDER HERE BECAUSE THE SORTS WILL NOT WORK
$query = $this->Activities->find('all')
->where(['cid_1' => $cidOne])
->andWhere(['status' => 1]);
// Send the query to the view.
$this->set('activities', $this->paginate($query));
}
But when I add sortWhitelist as below the initial page load is not sorted by the due_date and no sort arrow is displayed.
public $paginate = [
'sortWhitelist' => [
'due_date', 'related_to', 'subject', 'post_code', 'type', 'priority'
],
'limit' => 5,
'order' => [
'Activities.due_date' => 'asc'
]
];
Thanks for any help. Z.
There must be consistency between the sortWhitelist array, the order array and the paginator link
so if your field is Activities.due_date your code becomes:
public $paginate = [
'sortWhitelist' => [
'Activities.due_date', 'related_to', 'subject', 'post_code', 'type', 'priority'
],
'limit' => 5,
'order' => [
'Activities.due_date' => 'asc'
]
];
and in your view
<?= $this->Paginator->sort('Activities.due_date', __('Due Date')) ?>
If there is no ambiguity in the fields names you can omit the Model name and simply use due_date as column name

beforeMarshal does not modify request data when validation fails

Bug or Feature? If I change request data with beforeMarshal and there is a validation error, the request data will not be given back modified.
This question may be related to How to use Trim() before validation NotEmpty?.
Modifying Request Data Before Building Entities
If you need to modify request data before it is converted into entities, you can use the Model.beforeMarshal event. This event lets you manipulate the request data just before entities are created. Source: CakePHP 3 Documentation
According to the book I would expect the request data is always changed, no matter if there is a validation error or not.
Example or test case:
// /src/Model/Table/UsersTable.php
namespace App\Model\Table;
use Cake\ORM\Table;
// Required for beforeMarshal event:
use Cake\Event\Event;
use ArrayObject;
// Required for Validation:
use Cake\Validation\Validator;
class UsersTable extends Table {
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options) {
$data['firstname'] = trim($data['firstname']);
}
public function validationDefault(Validator $validator) {
$validator
->add('firstname', [
'minLength' => [ 'rule' => ['minLength', 2], 'message' => 'Too short.' ],
])
;
return $validator;
}
}
If I enter " d" (Space-d) the validation error is shown, but the space itself is not removed in the form. I would expact the form showing only "d" because the space is removed from the request data with the beforeMarshal event. So... bug or feature?
My solution would be to use the trim()-function in the controller instead of the beforeMarshal event:
// /src/Controller/UsersController.php
// ...
public function add() {
$user = $this->Users->newEntity();
if ($this->request->is('post')) {
// Use trim() here instead of beforeMarshal?
$this->request->data['firstname'] = trim($this->request->data['firstname']);
$user = $this->Users->patchEntity($user, $this->request->data );
if ( $this->Users->save($user) ) {
$this->Flash->succeed('Saved');
return $this->redirect(['controller' => 'Users', 'action' => 'index']);
} else {
$this->Flash->error('Error');
}
}
$this->set('user', $user);
}
This way the space will be removed even if there is a validation error. Or did I miss another function similar to beforeMarshal which is really modifying the request data?
The main purpose of beforeMarshal is to assist the users to pass the validation process when simple mistakes can be automatically resolved, or when data needs to be restructured so it can be put into the right columns.
The beforMarshal event is triggered just at the start of the validation process, one of the reasons is that beforeMarshal is allowed to change the validation rules and the saving options, such as the field whitelist. Validation is triggered just after this event is finished.
As documentation explains, if a field does not pass validation it will automatically removed from the data array and not be copied into the entity. This is to prevent having inconsistent data in the entity object.
More over, the data in beforeMarshal is a copy of the request. This is because it is important to preserve the original user input, as it may be used elsewhere.
If you need to trim your columns and display the result of the trimming to your user, I recommend doing it in the controller:
$this->request->data = array_map(function ($d) {
return is_string($d) ? trim($d) : $d;
}, $this->request->data);
Not work. This is my beforeMarshal :
public function beforeMarshal(Event $event, ArrayObject $data, ArrayObject $options)
{
$schema = $this->schema();
foreach($schema->columns() as $idx => $field ) {
$sc = $schema->getColumn($field);
if (isset($data[$field]) && $data[$field] != null) {
if ($sc['type'] == 'date') {
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d');
}
if ($sc['type'] == 'datetime') {
debug($data[$field]);
$date = DateTime::createFromFormat('d/m/Y',$data[$field]);
if ($date)
$data[$field] = $date->format('Y-m-d H:i:s');
}
}
}
debug($data);
}
The date commission_approved_date is correctly modified in beforeMarshal:
/src/Model/Table/AccountsTable.php (line 265)
object(ArrayObject) {
_referer => 'http://localhost/gessin/Accounts/edit/ODc?filter=eyJBY2NvdW50cy51c2VyX2lkIjoiMTA4NSIsIjAiOiJNT05USChBY2NvdW50cy5jb21taXNzaW9uX2RhdGUpID4gMCIsIllFQVIoQWNjb3VudHMuY29tbWlzc2lvbl9kYXRlKSI6IjIwMjAifQ=='
description => 'Provvigione su attivazione prodotto vod002'
notes => 'asd'
totalpaid => '0'
commission_approved_date => '2020-02-23 18:34:22'
}
But the same date is not, after patchEntity:
/src/Controller/AccountsController.php (line 203)
object(App\Model\Entity\Account) {
'id' => (int) 87,
'identifier' => null,
'company_id' => null,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 14:01:50.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'modified' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-29 18:30:24.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'notes' => 'asd',
'total' => null,
'totaltax' => null,
'invoice_id' => null,
'description' => 'Provvigione su attivazione prodotto vod002',
'in_out' => null,
'is_refund' => null,
'client_id' => null,
'contract_id' => (int) 32,
'totalpaid' => (float) 0,
'user_id' => (int) 1085,
'commission_date' => object(Cake\I18n\FrozenTime) {
'time' => '2020-02-04 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'commission_approved_date' => object(Cake\I18n\FrozenTime) {
'time' => '2028-08-12 00:00:00.000000+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},