laravelredispredis

How with predis to write data in tree structure?


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.

  1. I wrapped 'address' array with serialize method - it worked ok and that is what I see in phpRedisAdmin : https://prnt.sc/BMnRev9HDwZz

  2. Have I to wrap all subarray with serialize method ?

  3. 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

  4. 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!


Solution

  • After some tests I found a valid way top save/read structured data with redis as :

    $articleArray = self::serializeArray($article->toArray());
    Redis::hmset($redisUniqueKey, $articleArray);
    ...
    
    
    protected static function serializeArray(?array $data): array
    {
        foreach ($data as $fieldName => $fieldValue) {
            if (is_array($fieldValue)) {
                $data[$fieldName] = serialize($fieldValue);
            }
        }
    
        return $data;
    }
    

    and reading :

    $cachedArticle = self::unserializeArray(Redis::HGETALL($redisUniqueKey));
    ...
    protected static function unserializeArray(?array $data): array
    {
        foreach ($data as $fieldName => $fieldValue) {
            if (isSerialized($fieldValue)) {
                $data[$fieldName] = unserialize($fieldValue);
            }
        }
    
        return $data;
    }
    

    That works for me with data I test.