anourvalar/eloquent-serialize
Serialize and restore Laravel Eloquent QueryBuilder instances. Save complex queries (with relations, where clauses, limits, etc.) to an array/package and later unserialize back into a builder to run the query again. Supports Laravel 6–12.
Minimal Steps to First Use:
composer require anourvalar/eloquent-serialize
use EloquentSerialize;
$serialized = EloquentSerialize::serialize(
\App\User::query()->where('active', true)->limit(10)
);
$builder = EloquentSerialize::unserialize($serialized);
$users = $builder->get(); // Returns active users
Where to Look First:
EloquentSerialize::serialize() and EloquentSerialize::unserialize() in the README.with(), where(), orderBy(), and pagination.First Use Case: Cache a paginated API response:
// Cache the serialized query for 1 hour
$cacheKey = 'users:active:paginated';
$serialized = EloquentSerialize::serialize(
\App\User::query()->where('active', true)->paginate(15)
);
// Later, retrieve and execute
$cachedData = cache()->get($cacheKey);
if ($cachedData) {
$builder = EloquentSerialize::unserialize($cachedData);
return $builder->get();
}
Core Workflows:
Query Caching for APIs
// Controller method
public function index(Request $request)
{
$query = \App\User::query()
->when($request->filled('search'), fn($q) => $q->where('name', 'like', '%'.$request->search.'%'))
->with('posts');
$cacheKey = 'users:'.md5($request->search);
$serialized = EloquentSerialize::serialize($query);
cache()->put($cacheKey, $serialized, now()->addMinutes(5));
return EloquentSerialize::unserialize($serialized)->get();
}
Background Job with Serialized Queries
// Job class
public function handle()
{
$serialized = $this->serializedQuery; // From job payload
$builder = EloquentSerialize::unserialize($serialized);
// Process results (e.g., send emails, generate reports)
foreach ($builder->get() as $user) {
// ...
}
}
// Dispatch job
SerializeQueryJob::dispatch(
EloquentSerialize::serialize(\App\User::query()->where('is_admin', true))
);
Multi-Tenant Query Serialization
// Middleware to attach tenant ID
public function handle($request, Closure $next)
{
$request->merge(['tenant_id' => auth()->tenant()->id]);
return $next($request);
}
// Controller
public function tenantDashboard()
{
$query = \App\User::query()
->where('tenant_id', request('tenant_id'))
->with(['orders' => function($q) {
$q->where('status', 'completed');
}]);
$serialized = EloquentSerialize::serialize($query);
cache()->put("tenant:{$request->tenant_id}:dashboard", $serialized, now()->addHours(1));
}
Integration Tips:
Validation Layer: Always validate unserialized queries against the current schema:
$builder = EloquentSerialize::unserialize($serialized);
$builder->getQuery()->from; // Check table exists
$builder->getQuery()->columns; // Verify columns
Error Handling:
try {
$builder = EloquentSerialize::unserialize($serialized);
} catch (\AnourValar\EloquentSerialize\Exceptions\InvalidQueryException $e) {
Log::error("Failed to unserialize query: " . $e->getMessage());
return response()->json(['error' => 'Query invalid'], 500);
}
Testing:
Use Laravel’s Mockery to test serialization:
$mockQuery = Mockery::mock(\Illuminate\Database\Eloquent\Builder::class);
$mockQuery->shouldReceive('getQuery')->andReturn(new \Illuminate\Database\Query\Builder);
$serialized = EloquentSerialize::serialize($mockQuery);
$unserialized = EloquentSerialize::unserialize($serialized);
Custom Serialization: Extend the serializer for unsupported features:
use AnourValar\EloquentSerialize\Serializer;
class CustomSerializer extends Serializer
{
protected function serializeWhereRaw($whereRaw)
{
// Custom logic for raw SQL
return parent::serializeWhereRaw($whereRaw);
}
}
// Register in AppServiceProvider
$this->app->bind('EloquentSerialize', function () {
return new CustomSerializer();
});
Pitfalls:
Schema Drift:
$builder = EloquentSerialize::unserialize($serialized);
$query = $builder->getQuery();
if (!Schema::hasColumn($query->from, 'column_name')) {
throw new \RuntimeException("Schema mismatch for column 'column_name'");
}
Global Scopes:
SoftDeletes) are not serialized by default.$builder = \App\User::query()->withoutGlobalScopes();
$serialized = EloquentSerialize::serialize($builder);
Complex Relationships:
with() or polymorphic relationships may fail.// Example: Add support for polymorphic relations
protected function serializeWith($with)
{
foreach ($with as $relation) {
if (str_contains($relation, 'morphTo')) {
// Custom logic
}
}
return parent::serializeWith($with);
}
Performance Overhead:
$serialized = cache()->remember("query:{$queryHash}", now()->addHours(1), function () {
return EloquentSerialize::serialize(\App\User::query()->where(...));
});
Laravel Version Quirks:
composer require anourvalar/eloquent-serialize:^1.0
Debugging Tips:
Log Serialized Queries:
$serialized = EloquentSerialize::serialize($query);
Log::debug('Serialized query:', ['data' => $serialized]);
Inspect Unserialized Builders:
$builder = EloquentSerialize::unserialize($serialized);
Log::debug('Unserialized query:', [
'toSql' => $builder->toSql(),
'bindings' => $builder->getBindings(),
'with' => $builder->getEagerLoads(),
]);
Common Errors:
InvalidQueryException: Query structure is unsupported (e.g., raw SQL).RuntimeException: Schema mismatch (e.g., missing table/column).BindingResolutionException: Model not found (e.g., App\User not autoloaded).Extension Points:
Custom Serializer:
Override methods in AnourValar\EloquentSerialize\Serializer:
class AppSerializer extends Serializer
{
protected function serializeWhere($where)
{
// Add custom logic for your `where` clauses
return parent::serializeWhere($where);
}
}
Middleware for Validation:
public function handle($request, Closure $next)
{
if ($request->has('serialized_query')) {
try {
EloquentSerialize::unserialize($request->serialized_query);
} catch (\Exception $e) {
return response()->json(['error' => 'Invalid query'], 400);
}
}
return $next($request);
}
Queue Payloads: Serialize queries for delayed execution:
// Job payload
$job = new ProcessUsersJob(
EloquentSerialize::serialize(\App\User
How can I help you explore Laravel packages today?