Skip to content

Cache::many(), Object __unserialize not working correctly with Redis and SERIALIZER_IGBINARY #56855

@SanderSander

Description

@SanderSander

Laravel Version

12.26.4

PHP Version

8.4.8

Database Driver & Version

igbinary version => 3.2.16

Description

When using Redis as the cache store and enabling SERIALIZER_IGBINARY, unserializing nested objects from the cache leads to inconsistent behavior: the unserialize process returns the wrong objects when Cache::many() is called. In the example below, instead of receiving an instance of NestedClass, an instance of RootClass is returned.

This issue does not occur when the default serializer is used; it happens only when SERIALIZER_IGBINARY is active.

I'm not certain whether this is an issue with Redis, the igbinary extension, or Laravel's Redis driver.

Example Output (Works as Expected: Default Serializer)

php artisan test-cache
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#806
  +name: "classC"
} // routes/console.php:27
"simple get" // routes/console.php:59
RootClass^ {#805
  +content: "base"
  +classC: NestedClass^ {#806
    +name: "classC"
  }
} // routes/console.php:59
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#809
  +name: "classC"
} // routes/console.php:27
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#811
  +name: "classC"
} // routes/console.php:27
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#815
  +name: "classC"
} // routes/console.php:27
array:3 [
  "instance" => RootClass^ {#805
    +content: "base"
    +classC: NestedClass^ {#809
      +name: "classC"
    }
  }
  "instance-1" => RootClass^ {#808
    +content: "base"
    +classC: NestedClass^ {#811
      +name: "classC"
    }
  }
  "instance-2" => RootClass^ {#813
    +content: "base"
    +classC: NestedClass^ {#815
      +name: "classC"
    }
  }
] // routes/console.php:63

Example Output (Error with SERIALIZER_IGBINARY)

php artisan test-cache
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#806
  +name: "classC"
} // routes/console.php:27
"simple get" // routes/console.php:59
RootClass^ {#805
  +content: "base"
  +classC: NestedClass^ {#806
    +name: "classC"
  }
} // routes/console.php:59
"loaded in unserialize" // routes/console.php:27
NestedClass^ {#807
  +name: "classC"
} // routes/console.php:27
"loaded in unserialize" // routes/console.php:27
RootClass^ {#813
  +content: "base"
  +classC: NestedClass^ {#807
    +name: "classC"
  }
} // routes/console.php:27

   TypeError 

  Cannot assign RootClass to property RootClass::$classC of type NestedClass

  at routes/console.php:28
     24▕     {
     25▕         $this->content = $data['content'];
     26▕         $classC = Cache::get('classC');
     27▕         dump('loaded in unserialize', $classC);
  ➜  28▕         $this->classC = $classC;
     29▕     }
     30▕ }
     31▕
     32▕ class NestedClass {

  1   [internal]:0
      RootClass::__unserialize()
      +7 vendor frames 

  9   routes/console.php:26
      Illuminate\Support\Facades\Facade::__callStatic()

Steps To Reproduce

class RootClass {

    public function __construct(
        public string      $content,
        public NestedClass $classC,
    ) { }

    public function __serialize(): array
    {
        return [
            'content' => $this->content,
        ];
    }

    public function __unserialize(array $data): void
    {
        $this->content = $data['content'];
        $classC = Cache::get('classC');
        dump('loaded in unserialize', $classC);
        $this->classC = $classC;
    }
}

class NestedClass {
    public function __construct(
        public string $name,
    ) {}

    public function __serialize(): array
    {
        return ['name' => $this->name];
    }

    public function __unserialize(array $data): void
    {
        $this->name = $data['name'];
    }
}


Artisan::command('test-cache', function () {
   $classC = new NestedClass('classC');
   Cache::put('classC', $classC);

   $instance = new RootClass('base', $classC);
   Cache::put('instance', $instance);
   Cache::put('instance-1', $instance);
   Cache::put('instance-2', $instance);

   // This works fine
   dump('simple get', Cache::get('instance-1'));

   // This works with the default serializer but not with SERIALIZER_IGBINARY
   $back = Cache::many(['instance', 'instance-1', 'instance-2']);
   dd($back);
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions