Migration - How to set default value for json? (MySQL) - laravel

I have the problem that I need some values to be already set for my settings json column.
Let us say I have this in my user migration file:
$table->json('settings');
My goal is to set let us say these values as default:
'settings' => json_encode([
'mail' => [
'hasNewsletter' => false
],
'time' => [
'timezone' => ''
]
])
How would you do this?
My first approach was to set the values in my UserObserver in the created event after the User was created.
This creates the problem, that my UserFactory is not working correctly. Because a User is created but the settings values are overwritten by the UserObserver again...

Following solution works with Eloquent Model.
For default JSON data you can do something like this in your Model
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
protected $attributes = [
'settings' => '{
"mail": {
"hasNewsletter" : false
},
"time": {
"timezone" : ""
}
}'
];
}
Then the default value will be {"mail": {"hasNewsletter" : false},"time": {"timezone" :""} in your DB if your input is null. However the existing values in DB will be unchanged and will have to change manually if you need.
If you want to keep your existing DB values null (and/or when null) but want to get as the above default json by Eloquent, you can add the following method in the Model:
protected function castAttribute($key, $value)
{
if ($this->getCastType($key) == 'array' && is_null($value)) {
return '{
"mail":{
"hasNewsletter":false
},
"time":{
"timezone":""
}
}';
}
return parent::castAttribute($key, $value);
}
Note: above castAttribute method will return this same json/data for all null json column of your model. It's better to set empty array here.
Tested in Laravel 5.8.
Reference: https://laravel.com/docs/eloquent#default-attribute-values

Related

Hidden input value does not update model property value yii2

Im using activeform. In my User model, i initialized a model property Public formType;, set its rule to safe and i am trying to use this property with hiddeninput to create condition in the user controller. But i am getting that the activeform doesn't update the value of the property. Ive read this but i am still unclear whats the workaround of updating the property while still using activeform.
Form
<?= $form->field($model, 'formType')->hiddenInput(['value' => 'userRowUpdate'])->label(false) ?>
User Controller
public function actionUpdate($id) {
$model = $this->findModel($id);
if ($model->load(Yii::$app->request->post())) {
$model->scannedFile = \yii\web\UploadedFile::getInstance($model, 'scannedFile');
$type = Yii::$app->request->post('formType');
if ($model->processAndSave()) {
FlashHandler::success("User profile updated success!");
if ($type == "userDetailView") {
return $this->redirect(['view', 'id' => $model->id]);
} else if ($type == "userRowUpdate") {
return $this->redirect(Yii::$app->request->referrer);
}
} else {
FlashHandler::err("User profile updated FAIL!");
}
}
return $this->render('update', [
'model' => $model,
]);
}
Replace the hiddeninput from using activeform to
<?=Html::hiddenInput('name', $value);?>
Reasons:-
code in question is probably not the right approach since its creating a attribute/model property just for the condition in action controller
html input wouldnt affect the model but the data is still readable and be used in the actioncontroller as a condition
You can use
Html::activeHiddenInput($model, 'formType')
to avoid hiding the label and it's shorter.
if the value is not being updated, check your rules in the model. That attribute at least must have a safe rule to be able to be assigned on the $model->load(Yii::$pp->request->post()) call

Laravel 8.6 firstOrCreate on variable model from URL

In a URL i pass the table name with the where parameters en map this using a JSON. The problem is now is. I use DB::table($table) but this doesn't allow firstOrCreate so i wanted to try it by model, but $table::were doesn't see $table as a model name. Even if i change it from 'establisments' to 'Estalishment'. Maybe i need to put App\ before it? Because the model is not in use. How can i get the object using a model from model names as in the URL?
URL:
&spec_code=CHI&department=surgery&department_code=SUR
JSON:
{
"establishments": { // $table name, but want it to be model)
"from1": "spec_code", // from url
"to1": "abc_id" // to column in database
},
"departments": {
"from1": "department",
"to1": "name",
"from2": "department_code",
"to2": "abc_id"
},
}
Controller:
foreach(JSON as $table => $field){
if(isset($field['from2'])){
$output[$table] = DB::table($table)->where($field['to1'], $request->input($field['from1']))->where($field['to2'], $request->input($field['from2']))->first();
}elseif(isset($field['from1'])){
$output[$table] = DB::table($table)->where($field['to1'], $request->input($field['from1']))->first();
// $output[$table] = $table::where($field['to1'], $request->input($field['from1']))->firstOrCreate(); //i want this.
// $output[$table] = "\App\\".$table::where($field['to1'], $request->input($field['from1']))->firstOrCreate();
}else{
$output[$table] = null;
}
}
If someone knows how i can get it to use the model so i can firstOrCreate(), that would make my code a lot cleaner.
Kind regards,
Jeff
There are many ways to do this.
Create an array using which you can get model
$models = [
'establishments' => \App\Models\Establishment::class,
'departments' => \App\Models\Department::class,
]
Then within loop you can get the model from the array using
foreach(JSON as $table => $field){
$models[$table]::firstOrCreate(/* */)
}
As i said there are many ways to fulfill your requirement. this is one way i would do.
By Default laravel does't have out of the box method to get the Model FQN based on the table name.
Because you can have model under different namespace.
But however you can do a workaround
$tableClassToFind = 'users';
$models = [
\App\Models\User::class,
//add your model(s) here
];
$modelClass = collect($models)
->map(function($eachModelFqn){
return [
'modelClass' => $eachModelFqn,
'tableName' => app($eachModelFqn)->getTable(),
];
})
->mapWithKeys(fn($value) => [$value['tableName'] => $value['modelClass']])
->get($tableClassToFind);
dd($modelClass);
Basically it will get all the table name along with the Model FQN and changing the value of $tableClassToFind will get the appropriate Model class.
Please all the models to $models variable.

Encrypted Array Won't Save in Eloquent Model

I'm having an odd error with saving an encrypted array in Laravel. The model never updates even when save() is called.
There are no console or SQL errors.
When the encryption is disabled, there are no errors and the model updates successfully.
In a Controller, I'm calling the model like so:
$userData = UserData::where('user_id', $user_id)->first();
I then pull the array:
$encryptedData = $userData->app_data;
And I want to add to this array e.g.
$encryptedData['new'] = 'axy';
$encryptedData['time'] = time();
I then update the model and save it:
$userData->app_data = $encryptedData;
$userData->save();
However, here is where the problem starts. The model does not update. It remains as if nothing happens. Hence if I refresh(), I get the same data as if I had never added the two new entries. When I log it, it looks like this:
Array
(
[token] => xyz
[access_token] => abc
)
After the addition of two new entries:
Array
(
[token] => xyz
[access_token] => abc
[new] => 'axy'
[time] => 1234
)
And after the save() and refresh():
Array
(
[token] => xyz
[access_token] => abc
)
The model looks like this:
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
class UserData extends Model
{
protected $fillable = [
'user_id', 'app_data'
];
protected $casts = [
'user_id' => 'int',
'app_data' => 'array'
];
public function getAppDataAttribute($value)
{
try {
return decrypt($value);
}
catch (DecryptException $e) {
return $value;
}
}
public function setAppDataAttribute($value)
{
$this->attributes['app_data'] = encrypt($value);
}
}
Why are my additions to the array not being saved?
Edit: The strangeness continues
If I call:
UserData::where('id', $userData->id)->update(['app_data' => $encryptedData]);
Then the model does update and does not encrypt, HOWEVER, when I refresh and log the new 'app_data' field, it is returned as a JSON string and not an array as before. I need to cast/decode it to an array each time I want to use it.
Couple of things to look for.
1) The Laravel encrypter uses the app key. Make sure you have one in your .env file. If not, run php artisan key:generate
2) I assume the array is correctly formatted like this:
Array
(
'token' => 'xyz', // You have a = here and no commas after any other value
'access_token' => 'abc'
)
3) Depending on what you are storing this as, you can test by serializing the array before encrypting it:
$arr = serialize($encryptedData); // After you have added new data to the array
$userData->app_data = $arr;
$userData->save();
This is automatic in Laravel, but may give you a help hunting the bug. Test with your mutator using encryptString() and manually unserialize / decryptString() to see if any odd behavior by stepping through the values as they are mutated.

SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘proposal_db.userlogs’ doesn’t exist

I’m doing some customization inside the CakeDC/users plug-in. I created a table with name “user_logs” which consist of foreign key relationship with the actual “users” table provided by CakeDC/users.
I baked the “user_logs” model using command:
bin\cake bake model UserLogs --plugin CakeDC/Users
After user gets login I’m just generating log transaction inside the “user_logs” table. I added the following line inside the “/vendor/cakedc/users/src/Controller/Traits/LoginTrait.php” file under _afterIdentifyUser function:
$this->activity_log(‘Login’, ‘Login’, $user[‘id’]);
And activity_log function is added inside the src/Controller/AppController.php file:
function activity_log($page, $action, $id=null){
$this->loadModel(‘CakeDC/Users.Userlogs’);
$dataUserLog = $this->Userlogs->newEntity();
$dataUserLog['user_id'] = $this->request->session()->read('Auth.User.id');
if(!empty($id)){
$dataUserLog['reference_id'] = $id;
} else {
$dataUserLog['reference_id'] = 0;
}
$dataUserLog['activity_timestamp'] = date('Y-m-d H:i:s');
$dataUserLog['page'] = $page;
$dataUserLog['action'] = $action;
$this->Userlogs->save($dataUserLog);
}
vendor/cakedc/users/src/Model/Entity/UserLog.php file code:
namespace CakeDC\Users\Model\Entity;
use Cake\ORM\Entity;
class UserLog extends Entity
{
protected $_accessible = [
‘user_id’ => true,
‘reference_id’ => true,
‘activity_timestamp’ => true,
‘page’ => true,
‘action’ => true,
‘user’ => true
];
}
vendor/cakedc/users/src/Model/Table/UserLogsTable.php file code:
namespace CakeDC\Users\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class UserLogsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('user_logs');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'className' => 'CakeDC/Users.Users'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', null, 'create');
$validator
->dateTime('activity_timestamp')
->allowEmptyDateTime('activity_timestamp');
$validator
->scalar('page')
->maxLength('page', 255)
->allowEmptyString('page');
$validator
->scalar('action')
->maxLength('action', 255)
->allowEmptyString('action');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['user_id'], 'Users'));
return $rules;
}
}
The surprise part is! this works on localhost but when I’m uploading code on a server it’s not working. On localhost I’ve PHP v7.3.4 and on server I’ve PHP v5.6.40. Can any one suggest what’s wrong with this why it’s working on localhost and not on server? Everything is same I’ve done almost everything cleared model cache on server as well but no luck. Please help.
Not really sure why CAKEPHP is looking for table “proposal_db.userlogs” on server whereas I created “user_logs” table on both local and server. Please suggest?

Auth::attempt() in laravel 4.2 proper usage

im trying to use Auth::attempt($credentials) in my controller and here is my sample code
$credentials= [
'SystemUserName' => Input::get('username'),
'SystemUserPassword' => Input::get('password')
];
then im having a error saying Undefined Index: Password only to know that i can use any field for username but i can't do that for the password because it should be 'password'. the thing is SystemUserName and SystemUserPassword are column names in my database. How can i do this properly? any ideas?
Unfortunately you can use only the 'password' column name with the included database and eloquent drivers, because the column name is hardcoded in the user provider. So your only bet is to create your own custom driver by extending the Eloquent one. This can be done in four easy steps as explained below:
1. Create your custom driver class file in app/extensions/CustomUserProvider.php file with the following contents:
use Illuminate\Auth\UserInterface;
use Illuminate\Auth\EloquentUserProvider;
use Illuminate\Auth\UserProviderInterface;
class MyCustomUserProvider extends EloquentUserProvider implements UserProviderInterface {
public function retrieveByCredentials(array $credentials)
{
$query = $this->createModel()->newQuery();
foreach ($credentials as $key => $value) {
if ($key != 'SystemUserPassword') $query->where($key, $value);
}
return $query->first();
}
public function validateCredentials(UserInterface $user, array $credentials)
{
return $this->hasher->check($credentials['SystemUserPassword'], $user->SystemUserPassword);
}
}
2. Add "app/extensions" to your composer.json in the classmap section:
"autoload": {
"classmap": [
"app/commands",
"app/controllers",
"app/models",
"app/database/migrations",
"app/database/seeds",
"app/tests/TestCase.php",
"app/extensions" // <-- add this line here
]
},
Then run a php composer dump-autoload.
3. In your app/start/global.php file add this to register your custom driver:
Auth::extend('mydriver', function($app)
{
return new MyCustomUserProvider($app['hash'], Config::get('auth.model'));
});
4. Then just set the driver in your app/config/auth.php to this:
'driver' => 'mydriver',
This was tested and it works just fine.
Mind you, this will work assuming your user passwords were hashed with the Hash::make() method Laravel offers and stored that way in the database. If not, then you need to adjust the validateCredentials method in MyCustomUserProvider with your own compare method between the plain and hashed password.

Resources