th3n3rd/cartesian-product
Memory-efficient Cartesian Product generator for PHP. Uses iterators to yield one tuple at a time, letting you handle very large combinations without big memory usage. Build products via fluent with() calls or CartesianProduct::of(), iterate or toArray().
Installation:
composer require th3n3rd/cartesian-product
No additional configuration is required—Laravel’s autoloader will handle the package automatically.
First Use Case: Dynamic Query Builder Clauses: Generate all possible combinations of Eloquent query scopes or where clauses for a complex search API.
use Nerd\CartesianProduct\CartesianProduct;
$scopes = [
['active' => true],
['published' => true],
['category_id' => [1, 2, 3]],
];
$cartesian = CartesianProduct::of($scopes);
foreach ($cartesian as $combination) {
$query = User::query();
foreach ($combination as $key => $value) {
if (is_array($value)) {
$query->whereIn($key, $value);
} else {
$query->where($key, $value);
}
}
// Process each unique query combination (e.g., log, cache, or execute)
}
Where to Look First:
CartesianProduct::of() and CartesianProduct::empty()->with() methods for basic usage.src/CartesianProduct.php for iterator logic if extending functionality.Lazy Evaluation (Iterator Pattern): Use iterators for memory efficiency, especially with large datasets (e.g., >1000 combinations).
$combinations = CartesianProduct::of([
['size' => ['S', 'M', 'L']],
['color' => ['red', 'blue']],
['material' => ['cotton', 'polyester']],
]);
foreach ($combinations as $combination) {
// Process one combination at a time (e.g., generate product variants)
ProductVariant::create($combination);
}
Chaining for Dynamic Inputs:
Build combinations incrementally using the fluent with() method.
$cartesian = CartesianProduct::empty();
if ($request->has('filters')) {
$cartesian->with($request->filters);
}
if ($request->has('advanced_filters')) {
$cartesian->with($request->advanced_filters);
}
Integration with Laravel Collections:
Convert to a Collection for further processing (e.g., filtering, mapping).
$collection = collect(iterator_to_array(CartesianProduct::of($arrays)));
$filtered = $collection->filter(fn ($item) => $item['price'] > 100);
Async Processing with Queues: Offload large Cartesian products to background jobs to avoid timeouts.
class GenerateCombinationsJob implements ShouldQueue {
public function handle() {
$combinations = CartesianProduct::of($this->arrays);
foreach ($combinations as $combination) {
// Process or store each combination
}
}
}
Streaming API Responses: Stream combinations directly to HTTP responses for memory efficiency.
return response()->stream(function () use ($combinations) {
foreach ($combinations as $combination) {
echo json_encode($combination) . PHP_EOL;
}
}, 200, ['Content-Type' => 'application/x-ndjson']);
Rule Engine: Generate all possible rule permutations for a dynamic workflow system.
$rules = [
['action' => 'notify', 'condition' => 'high_priority'],
['action' => 'escalate', 'condition' => 'unresolved'],
];
$ruleCombinations = CartesianProduct::of($rules);
Test Data Generation: Create edge-case test inputs for API validation.
$testInputs = CartesianProduct::of([
['user_id' => [1, null]],
['role' => ['admin', 'editor']],
['status' => ['active', 'suspended']],
]);
Recommendation Engine: Generate "frequently bought together" product bundles.
$productPairs = CartesianProduct::of([
['product_id' => [101, 102, 103]],
['bundle_id' => [201, 202]],
]);
Laravel Service Container: Bind the CartesianProduct class for dependency injection.
$this->app->bind(CartesianProduct::class, function () {
return new CartesianProduct();
});
Collection Macros: Extend Laravel Collections with Cartesian functionality.
Collect::macro('cartesian', function ($arrays) {
return $this->pipe(fn ($collection) => CartesianProduct::of($arrays));
});
// Usage:
$combinations = collect([])->cartesian($arrays);
Eloquent Relationships: Dynamically generate relationship combinations for polymorphic queries.
$relationships = [
['type' => 'posts', 'id' => [1, 2]],
['type' => 'comments', 'id' => [10, 20]],
];
$query = User::query();
foreach (CartesianProduct::of($relationships) as $rel) {
$query->orWhereHas($rel['type'], fn ($q) => $q->where('id', $rel['id']));
}
Caching:
Cache toArray() results for repeated computations (e.g., precomputed product bundles).
$key = 'product_bundles_' . md5(serialize($arrays));
$bundles = Cache::remember($key, now()->addHours(1), function () use ($arrays) {
return CartesianProduct::of($arrays)->toArray();
});
Memory Spikes with toArray():
toArray() on large datasets can exhaust memory.// Avoid for large datasets:
$result = $cartesian->toArray(); // Risky!
// Prefer:
foreach ($cartesian as $item) { ... }
Empty Input Handling:
CartesianProduct::of([]) or CartesianProduct::empty() returns an empty iterator, which may not match expectations.$arrays = $request->input('arrays', [['default' => 'value']]);
$cartesian = CartesianProduct::of($arrays);
Non-Array Inputs:
foreach ($arrays as $array) {
if (!is_array($array)) {
throw new \InvalidArgumentException('All inputs must be arrays.');
}
}
Thread Safety with toArray():
toArray() may cause race conditions or memory issues.toArray() to single-threaded contexts or use iterators in async environments.
// Safe for async:
foreach ($cartesian as $item) { ... }
// Avoid in async:
$result = $cartesian->toArray(); // Not thread-safe!
Performance Overhead for Small Datasets:
array_product or nested loops for small use cases.
if (count($arrays) < 3 && array_reduce($arrays, fn ($carry, $item) => $carry * count($item), 1) < 100) {
// Use native solution for small datasets
}
Iterator Debugging:
iterator_count() to check the total number of combinations (if supported by your PHP version).
$count = iterator_count($cartesian);
foreach ($cartesian as $index => $tuple) {
Log::debug("Tuple {$index}: " . json_encode($tuple));
}
Memory Profiling:
$startMemory = memory_get_usage();
foreach ($cartesian as $tuple) {
// Process tuple
}
Log::debug("Memory used: " . (memory_get_usage() - $startMemory) . " bytes");
How can I help you explore Laravel packages today?