How with predis to write data in tree structure? - laravel

The bounty expires in 5 days. Answers to this question are eligible for a +50 reputation bounty.
mstdmstd wants to draw more attention to this question:
Maybe I have to use other libs/tools
Reading some manuals on redis I see that tree data are possible, like root object and nested inserted objects.
But when in laravel 9.4 app with predis/predis 2.1 I write data with
Redis::set('key', serialize( $data ) );
method and in phpRedisAdmin I see that all written data are written (with redis=>prefix parameter from config/database.php) on the same level,
but not root object and nested inserted objects.
Which methods have I to use for data wring and tools to view data in tree structure ?
UPDATED BLOCK 1:
I copypasted sample code into my app :
$data = [ 'name' => 'John Doe', 'email' => 'johndoe#example.com', 'address' => [ 'street' => '123 Main St', 'city' => 'Anytown', 'state' => 'CA', 'zip' => '12345' ]
];
Redis::hmset('user:1', $data); // Error is pointing to this 173 line
but I got error :
php artisan job:dispatchNow ArticlesCachingJob
ErrorException
Array to string conversion
at vendor/predis/predis/src/Connection/StreamConnection.php:365
361▕
362▕ foreach ($arguments as $argument) {
363▕ \Log::info( ' $argument::');
364▕ \Log::info( $argument);
➜ 365▕ $arglen = strlen(strval($argument));
366▕ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
367▕ }
368▕
369▕ $this->write($buffer);
+8 vendor frames
9 app/Library/Services/ArticlesRedisCaching.php:173
Illuminate\Support\Facades\Facade::__callStatic()
I tried to debug code and added 2 logging lines into vendor/predis/predis/src/Connection/StreamConnection.php file:
public function writeRequest(CommandInterface $command) // line 352
{
$commandID = $command->getId();
$arguments = $command->getArguments();
$cmdlen = strlen($commandID);
$reqlen = count($arguments) + 1;
$buffer = "*{$reqlen}\r\n\${$cmdlen}\r\n{$commandID}\r\n";
foreach ($arguments as $argument) {
\Log::info( ' $argument::');
\Log::info( $argument);
$arglen = strlen(strval($argument));
$buffer .= "\${$arglen}\r\n{$argument}\r\n";
}
$this->write($buffer);
}
2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: _votes_db_user:1
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: name
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: John Doe
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: email
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: johndoe#example.com
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: address
[2023-02-20 07:27:04] local.INFO: $argument::
[2023-02-20 07:27:04] local.INFO: array (
'street' => '123 Main St',
'city' => 'Anytown',
'state' => 'CA',
'zip' => '12345',
)
[2023-02-20 07:27:04] local.ERROR: Array to string conversion {"exception":"[object] (ErrorException(code: 0): Array to string conversion at /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/predis/predis/src/Connection/StreamConnection.php:365)
[stacktrace]
#0 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/laravel/framework/src/Illuminate/Foundation/Bootstrap/HandleExceptions.php(266): Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError()
#1 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->Illuminate\\Foundation\\Bootstrap\\{closure}()
#2 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/predis/predis/src/Connection/StreamConnection.php(365): strval()
#3 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/predis/predis/src/Connection/AbstractConnection.php(110): Predis\\Connection\\StreamConnection->writeRequest()
#4 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/predis/predis/src/Client.php(318): Predis\\Connection\\AbstractConnection->executeCommand()
#5 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/predis/predis/src/Client.php(301): Predis\\Client->executeCommand()
#6 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/laravel/framework/src/Illuminate/Redis/Connections/Connection.php(116): Predis\\Client->__call()
#7 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/laravel/framework/src/Illuminate/Redis/Connections/Connection.php(216): Illuminate\\Redis\\Connections\\Connection->command()
#8 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/laravel/framework/src/Illuminate/Redis/RedisManager.php(276): Illuminate\\Redis\\Connections\\Connection->__call()
#9 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php(338): Illuminate\\Redis\\RedisManager->__call()
#10 /mnt/_work_sdb8/wwwroot/lar/MS/MS_Votes/app/Library/Services/ArticlesRedisCaching.php(173): Illuminate\\Support\\Facades\\Facade::__callStatic()
As you see strval can not work with array.
I wrapped 'address' array with serialize method - it worked ok and that is what I see in phpRedisAdmin : https://prnt.sc/BMnRev9HDwZz
Have I to wrap all subarray with serialize method ?
Searching in net I found some hints that HMSET is considered deprecated. I tried hset, but it raised the same Array to string conversion error
in both cases with serialize( or without it
I have
Redis server v=5.0.7 sha=00000000:0 malloc=jemalloc-5.2.1 bits=64 build=66bd62
under kubuntu 20.04
UPDATED BLOCK 2:
I tried 2 ways :
$data = [ 'name' => 'John Doe', 'email' => 'johndoe#example.com', 'address' => (object)[ 'street' => '123 Main St', 'city' => 'Anytown', 'state' => 'CA', 'zip' => '12345' ]
];
Redis::hset('user:1', $data);
and I got :
Array to string conversion
at vendor/predis/predis/src/Connection/StreamConnection.php:365
361▕
362▕ foreach ($arguments as $argument) {
363▕ //\Log::info( ' $argument::');
364▕ //\Log::info( $argument);
➜ 365▕ $arglen = strlen(strval($argument));
366▕ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
367▕ }
368▕
369▕ $this->write($buffer);
+8 vendor frames
9 app/Library/Services/ArticlesRedisCaching.php:176
Illuminate\Support\Facades\Facade::__callStatic()
10 app/Jobs/ArticlesCachingJob.php:52
App\Library\Services\ArticlesRedisCaching::addArticleToCaching()
and second :
$data = [ 'name' => 'John Doe', 'email' => 'johndoe#example.com', 'address' => [ 'street' => '123 Main St', 'city' => 'Anytown', 'state' => 'CA', 'zip' => '12345' ]
];
Redis::hset('user:1', (object)$data);
and error again :
Object of class stdClass could not be converted to string
at vendor/predis/predis/src/Connection/StreamConnection.php:365
361▕
362▕ foreach ($arguments as $argument) {
363▕ //\Log::info( ' $argument::');
364▕ //\Log::info( $argument);
➜ 365▕ $arglen = strlen(strval($argument));
366▕ $buffer .= "\${$arglen}\r\n{$argument}\r\n";
367▕ }
368▕
369▕ $this->write($buffer);
+8 vendor frames
9 app/Library/Services/ArticlesRedisCaching.php:176
Illuminate\Support\Facades\Facade::__callStatic()
looks like any value must be string(or serialized string) ?
Thank you!

To store nested data in Redis as a tree-like structure, you can use Redis' built-in data structure called "hashes". A Redis hash is a collection of key-value pairs where the keys are strings and the values can be any Redis data type, including other hashes. Here's an example of how to use Redis hashes to store nested data:
$data = [ 'name' => 'John Doe', 'email' => 'johndoe#example.com', 'address' => [ 'street' => '123 Main St', 'city' => 'Anytown', 'state' => 'CA', 'zip' => '12345' ]
];
Redis::hMset('user:1', $data);
In this example, we're using the hMset method to store the nested data as a Redis hash with the key "user:1". The hMset method takes two arguments: the key for the hash and an array of key-value pairs for the hash. We're passing in the $data array, which contains nested data for the user.
To view the data in a tree structure, you can use Redis' built-in command called HGETALL to retrieve the entire hash. You can also use a Redis GUI tool like RedisInsight or Redsmin to view the data in a tree-like structure.
$data = Redis::hGetAll('user:1');
print_r($data);
will return
Array
(
[name] => John Doe
[email] => johndoe#example.com
[address] => Array
(
[street] => 123 Main St
[city] => Anytown
[state] => CA
[zip] => 12345
)
)
As you can see, the nested data is stored as an array within the hash value for the "address" key.
Note that you don't need to serialize the data before storing it in Redis. Redis supports a variety of data types including strings, hashes, lists, sets, and more, so you can store complex data structures directly without having to serialize them.

Related

Laravel: How can I assertJson an array

I am creating a feature test for a Seminar. Everything is working great; I am trying to update my feature test to account for the seminar dates.
Each Seminar can have one or many dates, so I am saving these values as a json field:
// migration:
...
$table->json('dates');
...
Here is what my Seminar model looks like:
// Seminar.php
protected $casts = [
'dates' => 'array',
];
When saving the seminar, I am returning a json resource:
if ($seminar->save()) {
return response()->json(new SeminarResource($seminar), 200);
...
Using Postman, my Seminar looks a like this:
...
"capacity": 100,
"dates": [
"2020-10-15",
"2020-10-16"
],
...
So far so good!
In my test, I am testing that a seminar can be created.
$http->assertStatus(201)
->assertJson([
'type' => 'seminars',
'id' => (string)$response->id,
'attributes' => [
'dates' => $response->attributes->dates, // General error: 25 column index out of range
I've tried to convert the array to a string, or json_encode the value in the resource. I don't think that's the correct way since I am already casting the value as an array in the model.
How can I assert that my dates is returning an array?
+"dates": array:2 [
0 => "2020-10-15"
1 => "2020-10-16"
]
Thank you for your suggestions!
EDIT
When I dd($response->attributes->dates); this is what I'm getting (which is correct).
array:2 [
0 => "2020-10-15"
1 => "2020-10-16"
]
What I'm not sure is how to assert an array like that. Since I'm using faker to generate the date, I don't really know (or care) what the date is, just want to assert that it is in fact an array.
I've tried something like:
'dates' => ['*'],
However, that just adds another element to the array.
EDIT 2
If I make the array a string,
'dates' => json_encode($response->attributes->dates),
I'll get an error like this:
--- Expected
+++ Actual
## ##
- 'dates' => '["2020-10-15","2020-10-16"]',
+ 'dates' =>
+ array (
+ 0 => '2020-10-15',
+ 1 => '2020-10-16',
+ ),
In my database, the values are stored like this:
["2020-10-15","2020-10-16"]
My actual test looks like this:
$http->assertStatus(201)
->assertJsonStructure([
'type', 'id', 'attributes' => [
'name', 'venue', 'dates', 'description', 'created_at', 'updated_at',
],
])
->assertJson([
'type' => 'workshops',
'id' => (string)$response->id,
'attributes' => [
'name' => $response->attributes->name,
'venue' => $response->attributes->venue,
'dates' => $response->attributes->dates,
'description' => $response->attributes->description,
'created_at' => (string)$response->attributes->created_at,
'updated_at' => (string)$response->attributes->updated_at,
],
]);
$this->assertDatabaseHas('workshops', [
'id' => $response->id,
'name' => $response->attributes->name,
'venue' => $response->attributes->venue,
'dates' => $response->attributes->dates,
'description' => $response->attributes->description,
]);

Laravel unique validation Ignore Rule for update

I have a name field on my Client model which must be unique. For the store method I have the following rules:
array (
'token' => 'string|max:250',
'directory' => 'max:250',
'name' => 'required|string|max:250|unique:clients',
)
For the update method, I have amended this ruleset to ignore the current ID to avoid a duplicate being identified:
$rules['name'] = $rules['name'] . ',id,' . $id;
This produces the following ruleset (for record ID 105):
array (
'token' => 'string|max:250',
'directory' => 'max:250',
'name' => 'required|string|max:250|unique:clients,id,105',
)
When the update method is executed, the The name has already been taken. response is returned during the validation.
I realise this is a well-discussed topic, but believe I am conforming to the correct approach. Can someone point out the error in my logic, please?
Note: This is running on Laravel 5.8.
array(
'token' => 'string|max:250',
'directory' => 'max:250',
'name' => [
'required', 'string', 'max:250',
\Illuminate\Validation\Rule::unique(Client::class, 'email')->ignore($this->id)
]
)

How to access data from an array when it's inside an std object? Error: Illegal string offset 'id'

How to access this array object in Laravel 6, using Eloquent?
[line_items] => Array
(
[0] => stdClass Object
(
[id] => 4088662196333
[variant_id] => 29653605285997
$external = DB::table('orders')->pluck('import_order_data');
...
foreach ($external as $key => $val) {
...
Cart::updateOrCreate([
'line_item_id' => ['line_item_id' => $val['id']],
],
ErrorException
Illegal string offset 'id'
If I change it to:
'line_item_id' => ['line_item_id' => $val->id],
I get error:
ErrorException
Trying to get property 'id' of non-object
If I change it to:
'line_item_id' => ['line_item_id' => $val['line_items']->id],
I get error:
Illegal string offset 'line_items'
EDIT:
The problem was:
protected $casts = [
'import_order_data' => 'array',
];
Now I can access it like this:
dd($val['line_items'][0]['id']);
Which provides:
4092309209197
or
dd($val['line_items']);
which provides:
array:1 [▼
0 => array:26 [▼
"id" => 4092309209197
"sku" => "1605"
"name" => "Printer Ink"
Any better options on accessing the data?
EDIT:
Answer:
foreach ($val['line_items'] as $index => $lineItem) {
dd($lineItem['id']);
Which provides:
4092309209197
Is this a reasonable way to do it?
If I understand correctly, your array looks like this:
$arr = [
[
"id" => 4092309209197,
"sku" => "1605",
"name" => "Printer Ink",
....etc.....
]
];
Fine. First, since the outer array only has one item, you can do this:
$innerArr = $arr[0];
Now you've got this:
$innerArr = [
"id" => 4092309209197,
"sku" => "1605",
"name" => "Printer Ink",
....etc.....
]
And you can access it like this:
echo $innerArr["id"];
echo $innerArr["sku"];
....etc....
Or like this:
foreach($innerArr as $key => $val){
echo $key.": ".$val."\r\n";
}

displaying particular data from entire json using laravel

I have an issue in printing the data from the below json in laravel. Find below the json data and help me with this.
DarthSoup\Cart\Item Object ( [rowId] => 76cc22e6f533b8ef3d08e6eca0f110b4 [id] => 61XJrpB7CgiYSRCX6hR78wCEABhjLI7jNLzPyDrkYA3LyIVisz [name] => linux [quantity] => 1 [price] => 109 [options] => DarthSoup\Cart\CartItemOptions Object ( [items:protected] => Array ( [package] => Super Lite ) ) [subItems] => DarthSoup\Cart\SubItemCollection Object ( [items:protected] => Array ( ) ) [created_at:protected] => Carbon\Carbon Object ( [date] => 2018-06-22 05:41:18.674548 [timezone_type] => 3 [timezone] => UTC ) [updated_at:protected] => Carbon\Carbon Object ( [date] => 2018-06-22 05:41:18.674574 [timezone_type] => 3 [timezone] => UTC ) [associatedModel:protected] => [taxRate:protected] => 19 ) 1
From the above json how to display the "price=>109" and "package=>super Lite"...
I'm getting an error as "Cannot use object of type DarthSoup\Cart\Item as array"
Find below the controller Code:
public function addCart(Request $request)
{
$name = $request->input();
$hosting=$name['hosting'];
$package=$name['package'];
$price=$name['price'];
$response=Cart::add(['id'=>str_random(50) ,'name' => $hosting, 'quantity' => 1, 'price' => $price, 'options' => ['package' => $package]]);
//dd($response);
return Cart::content();
return view('main.cart',Cart::content());
}
Blade file is given below:
#foreach($response->options as $option){
{{$option['name']}}
}
#endforeach
In my output I need to display the value of price,name and package...please help me to solve this error...
First this is not json, it is php object
if your variable name is $item then you can try this
$item->rowId;
$item->id;
$item->name;
$item->price;
foreach($item->options as $option){
echo $option->package;
}

Laravel 5 - Return Full Object From Model Relationship Query

I have a model that looks like:
class User extends Model{
public function projects(){
$this->hasMany('Project', 'project_id')
}
}
When I use the above method by calling $user->projects()->get() after initializing the class, I get a collection of only info related to the project(s):
Object [attributes:protected] => array(
[project_name] => 'my project',
[project_description] => 'my other project',
[created_at] => '11-22-2015 04:23:12'
)
How do I get a collection that has data for both the project(s) and the user? I am looking for something like:
Object [attributes:protected] => array(
[user_name] => 'macdadd',
[user_type] => 4,
[created_at] => '11-22-2015 04:23:12',
[project] => array(
array(
[project_name] => 'my project',
[project_description] => 'my other project',
[created_at] => '11-22-2015 04:23:12'
)
)
)
For this you should be able to use eager loading:
$user->with('projects')->get()
see: https://laravel.com/docs/5.3/eloquent-relationships#eager-loading

Resources