Encrypted Array Won't Save in Eloquent Model - laravel

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.

Related

store and update an array as json in mysql database laravel 9

I have values that I want to store in the database, I have declared the type as json and cast it as an array:
protected $casts = [
'favourites' => 'array'
];
however, I'm not sure how to add and update and read the values inside, any ideas?
controller function:
public function addtofav()
{
$id = request()->get('id');
$user = auth()->user();
json_decode($user->favourites,true);
array_push($user->favourites,$id);
dump('hello');
json_encode($user->favourites);
$user->save();
return response(['id'=> $id],200);
}
Quoting from the Laravel documenations:
adding the array cast to that attribute will automatically deserialize
the attribute to a PHP array when you access it on your Eloquent
model.
therefore, no need to make any encoding or decoding to your values before any update or create, just keep it as an array and Eloquent will take care of that, since you have the $cast array as below:
protected $casts = [
'options' => 'array',
];
Actually you should use many to many but if you want to use array, you don't encode or decode.
public function addtofav()
{
$id = request()->get('id');
$user = auth()->user();
$favourites = $user->favourites;
array_push($favourites,$id);
$user->favourites = $favourites
$user->save();
return response(['id'=> $id],200);
}

Call to a member function toBase() on array

I am trying to use resource collection and I am receiving this error " Call to a member function toBase() on array"
The following code is in my resource :
class dataCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'data' => $this->collection
];
}
}
and here is code from the controller:
$projects = count(Project::all());
$services = count(Service::all());
$users = count(User::all());
$technologies = count(Technology::all());
$customers = count(Customer::all());
$data = [
'projects' => $projects,
'services' => $services,
'users' => $users,
'technologies' => $technologies,
'customers' => $customers
];
return new dataCollection($data);
Can someone help me please?
I'm not sure what your end goal is, but it appears the problem is that, as the error says, you are sending the wrong type of data into a collection. The resource collection method in this case is looking for a set of objects, and you are sending an array.
By the manual, its looking for something like this with a collection of User objects:
UserResource::collection(User::all()); // User collection, not the raw data array
If you are trying to send data back to someone via some API or json, I think you already have what you need - perhaps skip the whole resource collection method if you can? So try this:
$data = [
'projects' => Project::count(),
'services' => Service::count(),
'users' => User::count(),
'technologies' => Technology::count(),
'customers' => Customer::count()
];
return json_encode($data);
Note I changed the code a little to make it more efficient for you - you don't need to assign into variables first - and by calling ::all() plus count(), it is going to make much heavier database queries
The toArray() method on the resource would actually re-convert objects into the type of array you have above - so I suggest you test by skipping the circular process.

Yii2 ActiveRecord

I'm new to Yii and struggling to get my head around a few things.
I have model with this function:
public static function findNonGeo()
{
$query = new CallerIdentityQuery(get_called_class());
$query->select([
'cidref', 'caller_id', 'expiry', 'conf_call',
'type', 'redirect', 'destination', 'status', 'start_date',
'statusDesc' => new \yii\db\Expression("CASE status
WHEN 0 THEN 'Deactivated'
WHEN 1 THEN 'Active'
WHEN 2 THEN 'Inactive'
WHEN 3 THEN 'Unregistered'
ELSE 'Permanently Deleted' END")])
->where(['status' => '1'])
->andWhere(['type' => 'N'])
->andWhere(['custref' => Yii::$app->user->identity->typedIdentity->getId()]);
return $query;
}
And in my controller I call this method like so:
$model = CallerIdentity::findNonGeo()->where(['cidref' => $id])->one();
but when the data is returned its like its just ignoring the
->andWhere(['type' => 'N'])
because I can view records that aren't of type 'N'.
so I'm having to do in my controller, maybe I'm doing something wrong or just not understanding it but I don't get it:
$model = CallerIdentity::findNonGeo()->where(['cidref' => $id])->andWhere(['type'=>'N'])->one();
another thing when using findOne($id):
$model = CallerIdentity::findOne($id);
null is returned.
Would appreciate any form of explanation/info.
When using where you're setting the where part of the query, not adding to it. So in the findNonGeo function you're building the required where parts. But with CallerIdentity::findNonGeo()->where(['cidref' => $id]) you're removing the already added where parts.
Try like this:
CallerIdentity::findNonGeo()->andWhere(['cidref' => $id])->one();
by using andWhere you'll keep the other parts and just add another one to it.

Laravel convert an array to a model

i have an array as follows
'topic' =>
array (
'id' => 13,
'title' => 'Macros',
'content' => '<p>Macros. This is the updated content.</p>
',
'created_at' => '2014-02-28 18:36:55',
'updated_at' => '2014-05-14 16:42:14',
'category_id' => '5',
'tags' => 'tags',
'referUrl' => '',
'user_id' => 3,
'videoUrl' => '',
'useDefaultVideoOverlay' => 'true',
'positive' => 0,
'negative' => 1,
'context' => 'macros',
'viewcount' => 60,
'deleted_at' => NULL,
)
I would like to use this array and convert/cast it into the Topic Model . Is there a way this can be done.
thanks
Try creating a new object and passing the array into the constructor
$topic = new Topic($array['topic']);
For creating models from a single item array:
$Topic = new Topic();
$Topic->fill($array);
For creating a collection from an array of items:
$Topic::hydrate($result);
Here is a generic way to do it, not sure if there is a Laravel-specific method -- but this is pretty simple to implement.
You have your Topic class with its properties, and a constructor that will create a new Topic object and assign values to its properties based on an array of $data passed as a parameter.
class Topic
{
public $id;
public $title;
public function __construct(array $data = array())
{
foreach($data as $key => $value) {
$this->$key = $value;
}
}
}
Use it like this:
$Topic = new Topic(array(
'id' => 13,
'title' => 'Marcos',
));
Output:
object(Topic)#1 (2) {
["id"]=>
int(13)
["title"]=>
string(6) "Marcos"
}
It seems that you have data of an existing model there, so:
First, you can use that array to fill only fillable (or not guarded) properties on your model. Mind that if there is no fillable or guarded array on the Topic model you'll get MassAssignmentException.
Then manually assign the rest of the properties if needed.
Finally use newInstance with 2nd param set to true to let Eloquent know it's existing model, not instantiate a new object as it would, again, throw an exception upon saving (due to unique indexes constraints, primary key for a start).
.
$topic = with(new Topic)->newInstance($yourArray, true);
$topic->someProperty = $array['someProperty']; // do that for each attribute that is not fillable (or guarded)
...
$topic->save();
To sum up, it's cumbersome and probably you shouldn't be doing that at all, so the question is: Why you'd like to do that anyway?
Look at these two available methods in L5 newInstance and newFromBuilder
e.g with(new static)->newInstance( $attributes , true ) ;
I would likely create the new instance of the object and then build it that way, then you can actually split some useful reusable things or defaults into the model otherwise what's the point in pushing an array into a model and doing nothing with it - very little besides for normalization.
What I mean is:
$topic = new Topic();
$topic->id = 3489;
$topic->name = 'Test';
And the model would simply be a class with public $id;. You can also set defaults so if you had like resync_topic or whatever property, you can set it as 0 in the model rather than setting 0 in your array.
I came across this question looking for something else. Noticed it was a bit outdated and I have another way that I go about handling the OPs issue. This might be a known way of handling the creation of a model from an array with more recent versions of Laravel.
I add a generic constructor to my class/model
public function __construct(array $attributes = [])
{
parent::__construct($attributes);
}
Then when I want to create a new instance of the model from an array I make a call like this
$topic = new Topic($attrs);
// Then save it if applicable
$topic->save(); // or $topic->saveOrFail();
I hope someone finds this helpful.

codeigniter datamapper get_rules not called after save()

I'm using Datamapper ORM for CodeIgniter I have rules 'serialized' and get_rules 'unserialized' for a field in my model. This field will store serialized data and when I retrieve back, get_rules will unserialize it.
However, after calling save(), I'm trying to re-access the field, but it still return serialized string, instead of array.
Is there any way to re-call or refresh my object so that the get_rules is called again and the field now return array?
Here's my model:
class User extends DataMapper{
public $validation = array(
'password' => array(
'label' => 'Password',
'rules' => array('encrypt')
),
'preferences' => array(
'rules' => array('serialize'),
'get_rules'=> array('unserialize')
)
);
function __construct($id = NULL)
{
parent::__construct($id);
}
function post_model_init($from_cache = FALSE)
{
}
public function _encrypt($field)
{
if (!empty($this->{$field}))
{
$this->{$field} = md5($this->{$field});
}
}
}
Datamapper ORM, afaik, will only use the get_rules when actually performing a get(). A few things you could try:
Given the following
$a = new Fruit();
$a->name = 'grapes';
$a->colors = serialize(array("purple","green"));
$a->save();
1. Create a new datamapper object and re-fetch:
$b = new Fruit();
$b->where('id', $a->id)->get();
$colors = $b->colors;
2. unserialize() the field yourself...
$colors = unserialize($a->colors);
3. You might even be able to use get_clone()
//not tested...
$b = $a->get_clone();
$colors = $b->colors;
This has been fixed here: https://bitbucket.org/wanwizard/datamapper/commits/db6ad5f2e10650b0c00c8ef9b7176d49a8e85163
Get the latest Datamapper library from bitbucket.

Resources