framework: [5.6] Caching collections with database (PostgreSQL) cache driver doesn't work
- Laravel Version: 5.6.38
- PHP Version: 7.2.5
- Database Driver & Version: PostgreSQL 10.3
Description:
Caching collections when using the database driver doesn’t work when using PostgreSQL. The issue seems to be that in PostgreSQL null bytes can’t be stored in columns of the type text.
This is what ends up being saved into the database:
O:39:"Illuminate\Database\Eloquent\Collection":1:{s:8:"
This string was serialized but it’s cut off and therefore corrupted, and it can not be unserialized. When trying to read it from cache, this exception is thrown:
unserialize(): Error at offset 52 of 55 bytes
This is an example SQL query that ends up saving a corrupted serialized string into the database:
[2018-09-05 14:38:34] local.INFO: array (
'query' => 'insert into "cache" ("key", "value", "expiration") values (?, ?, ?)',
'bindings' =>
array (
0 => 'app_name_cachespatie.permission.cache',
1 => 'O:39:"Illuminate\\Database\\Eloquent\\Collection":1:{s:8:"' . "\0" . '*' . "\0" . 'items";a:1:{i:0;O:35:"Spatie\\Permission\\Models\\Permission":28:{s:7:"guarded";a:1:{i:0;s:2:"id";}s:13:"' . "\0" . '*' . "\0" . 'connection";s:5:"pgsql";s:8:"' . "\0" . '*' . "\0" . 'table";s:11:"permissions";s:13:"' . "\0" . '*' . "\0" . 'primaryKey";s:2:"id";s:10:"' . "\0" . '*' . "\0" . 'keyType";s:3:"int";s:12:"incrementing";b:1;s:7:"' . "\0" . '*' . "\0" . 'with";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'withCount";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'perPage";i:15;s:6:"exists";b:1;s:18:"wasRecentlyCreated";b:0;s:13:"' . "\0" . '*' . "\0" . 'attributes";a:5:{s:2:"id";i:19;s:4:"name";s:6:"q2Yk6f";s:10:"guard_name";s:3:"web";s:10:"created_at";s:19:"2018-09-05 14:36:59";s:10:"updated_at";s:19:"2018-09-05 14:36:59";}s:11:"' . "\0" . '*' . "\0" . 'original";a:5:{s:2:"id";i:19;s:4:"name";s:6:"q2Yk6f";s:10:"guard_name";s:3:"web";s:10:"created_at";s:19:"2018-09-05 14:36:59";s:10:"updated_at";s:19:"2018-09-05 14:36:59";}s:10:"' . "\0" . '*' . "\0" . 'changes";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'casts";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'dates";a:0:{}s:13:"' . "\0" . '*' . "\0" . 'dateFormat";N;s:10:"' . "\0" . '*' . "\0" . 'appends";a:0:{}s:19:"' . "\0" . '*' . "\0" . 'dispatchesEvents";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'observables";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'relations";a:1:{s:5:"roles";O:39:"Illuminate\\Database\\Eloquent\\Collection":1:{s:8:"' . "\0" . '*' . "\0" . 'items";a:1:{i:0;O:29:"Spatie\\Permission\\Models\\Role":27:{s:7:"guarded";a:1:{i:0;s:2:"id";}s:13:"' . "\0" . '*' . "\0" . 'connection";s:5:"pgsql";s:8:"' . "\0" . '*' . "\0" . 'table";s:5:"roles";s:13:"' . "\0" . '*' . "\0" . 'primaryKey";s:2:"id";s:10:"' . "\0" . '*' . "\0" . 'keyType";s:3:"int";s:12:"incrementing";b:1;s:7:"' . "\0" . '*' . "\0" . 'with";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'withCount";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'perPage";i:15;s:6:"exists";b:1;s:18:"wasRecentlyCreated";b:0;s:13:"' . "\0" . '*' . "\0" . 'attributes";a:5:{s:2:"id";i:19;s:4:"name";s:6:"DWZhv1";s:10:"guard_name";s:3:"web";s:10:"created_at";s:19:"2018-09-05 14:36:59";s:10:"updated_at";s:19:"2018-09-05 14:36:59";}s:11:"' . "\0" . '*' . "\0" . 'original";a:7:{s:2:"id";i:19;s:4:"name";s:6:"DWZhv1";s:10:"guard_name";s:3:"web";s:10:"created_at";s:19:"2018-09-05 14:36:59";s:10:"updated_at";s:19:"2018-09-05 14:36:59";s:19:"pivot_permission_id";i:19;s:13:"pivot_role_id";i:19;}s:10:"' . "\0" . '*' . "\0" . 'changes";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'casts";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'dates";a:0:{}s:13:"' . "\0" . '*' . "\0" . 'dateFormat";N;s:10:"' . "\0" . '*' . "\0" . 'appends";a:0:{}s:19:"' . "\0" . '*' . "\0" . 'dispatchesEvents";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'observables";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'relations";a:1:{s:5:"pivot";O:44:"Illuminate\\Database\\Eloquent\\Relations\\Pivot":29:{s:11:"pivotParent";O:35:"Spatie\\Permission\\Models\\Permission":28:{s:7:"guarded";a:1:{i:0;s:2:"id";}s:13:"' . "\0" . '*' . "\0" . 'connection";N;s:8:"' . "\0" . '*' . "\0" . 'table";s:11:"permissions";s:13:"' . "\0" . '*' . "\0" . 'primaryKey";s:2:"id";s:10:"' . "\0" . '*' . "\0" . 'keyType";s:3:"int";s:12:"incrementing";b:1;s:7:"' . "\0" . '*' . "\0" . 'with";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'withCount";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'perPage";i:15;s:6:"exists";b:0;s:18:"wasRecentlyCreated";b:0;s:13:"' . "\0" . '*' . "\0" . 'attributes";a:1:{s:10:"guard_name";s:3:"web";}s:11:"' . "\0" . '*' . "\0" . 'original";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'changes";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'casts";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'dates";a:0:{}s:13:"' . "\0" . '*' . "\0" . 'dateFormat";N;s:10:"' . "\0" . '*' . "\0" . 'appends";a:0:{}s:19:"' . "\0" . '*' . "\0" . 'dispatchesEvents";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'observables";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'relations";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'touches";a:0:{}s:10:"timestamps";b:1;s:9:"' . "\0" . '*' . "\0" . 'hidden";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'visible";a:0:{}s:11:"' . "\0" . '*' . "\0" . 'fillable";a:0:{}s:46:"' . "\0" . 'Spatie\\Permission\\Models\\Permission' . "\0" . 'roleClass";N;s:52:"' . "\0" . 'Spatie\\Permission\\Models\\Permission' . "\0" . 'permissionClass";N;}s:13:"' . "\0" . '*' . "\0" . 'foreignKey";s:13:"permission_id";s:13:"' . "\0" . '*' . "\0" . 'relatedKey";s:7:"role_id";s:10:"' . "\0" . '*' . "\0" . 'guarded";a:0:{}s:13:"' . "\0" . '*' . "\0" . 'connection";N;s:8:"' . "\0" . '*' . "\0" . 'table";s:20:"role_has_permissions";s:13:"' . "\0" . '*' . "\0" . 'primaryKey";s:2:"id";s:10:"' . "\0" . '*' . "\0" . 'keyType";s:3:"int";s:12:"incrementing";b:1;s:7:"' . "\0" . '*' . "\0" . 'with";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'withCount";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'perPage";i:15;s:6:"exists";b:1;s:18:"wasRecentlyCreated";b:0;s:13:"' . "\0" . '*' . "\0" . 'attributes";a:2:{s:13:"permission_id";i:19;s:7:"role_id";i:19;}s:11:"' . "\0" . '*' . "\0" . 'original";a:2:{s:13:"permission_id";i:19;s:7:"role_id";i:19;}s:10:"' . "\0" . '*' . "\0" . 'changes";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'casts";a:0:{}s:8:"' . "\0" . '*' . "\0" . 'dates";a:0:{}s:13:"' . "\0" . '*' . "\0" . 'dateFormat";N;s:10:"' . "\0" . '*' . "\0" . 'appends";a:0:{}s:19:"' . "\0" . '*' . "\0" . 'dispatchesEvents";a:0:{}s:14:"' . "\0" . '*' . "\0" . 'observables";a:0:{}s:12:"' . "\0" . '*' . "\0" . 'relations";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'touches";a:0:{}s:10:"timestamps";b:0;s:9:"' . "\0" . '*' . "\0" . 'hidden";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'visible";a:0:{}s:11:"' . "\0" . '*' . "\0" . 'fillable";a:0:{}}}s:10:"' . "\0" . '*' . "\0" . 'touches";a:0:{}s:10:"timestamps";b:1;s:9:"' . "\0" . '*' . "\0" . 'hidden";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'visible";a:0:{}s:11:"' . "\0" . '*' . "\0" . 'fillable";a:0:{}s:46:"' . "\0" . 'Spatie\\Permission\\Models\\Role' . "\0" . 'permissionClass";N;}}}}s:10:"' . "\0" . '*' . "\0" . 'touches";a:0:{}s:10:"timestamps";b:1;s:9:"' . "\0" . '*' . "\0" . 'hidden";a:0:{}s:10:"' . "\0" . '*' . "\0" . 'visible";a:0:{}s:11:"' . "\0" . '*' . "\0" . 'fillable";a:0:{}s:46:"' . "\0" . 'Spatie\\Permission\\Models\\Permission' . "\0" . 'roleClass";N;s:52:"' . "\0" . 'Spatie\\Permission\\Models\\Permission' . "\0" . 'permissionClass";N;}}}',
2 => 1536237514,
),
'time' => 1.31,
)
Steps To Reproduce:
- Configure the
config/database.phpwith the PostgreSQL driver and theconfig/cache.phpwith the database driver - Create the
cachetable withphp artisan cache:tableand migrate it - Get any collection from the database, e.g
$collection = User::limit(3)->get() - Cache the collection, e.g
Cache::put('test', $collection, 5) - Try to read the the value from cache, e.g
Cache::get('test')
In step 4, the corrupted string will be successfully saved into the database, without any exception or warning. In step 5, an exception will be thrown when trying to unserialize the corrupted string.
About this issue
- Original URL
- State: closed
- Created 6 years ago
- Comments: 21 (12 by maintainers)
It’s not possible to convert the column with
$table->binary('value')->change()(“Datatype mismatch”), it requires raw SQL.Using a binary column for PostgreSQL also requires changes to the
DatabaseStoreclass. Both inserting and retrieving don’t work with the current implementation.So the
DatabaseStoreclass has to be adjusted in both cases. But with Base64 encoding, the user doesn’t have to change the migration/database. I think this is more relevant than the increased payload size.