Download the PHP package rgalstyan/symfony-aggregated-queries without Composer
On this page you can find all versions of the php package rgalstyan/symfony-aggregated-queries. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download rgalstyan/symfony-aggregated-queries
More information about rgalstyan/symfony-aggregated-queries
Files in rgalstyan/symfony-aggregated-queries
Package symfony-aggregated-queries
Short Description Reduce multi-relation Doctrine queries to a single SQL statement using JSON aggregation. Solves Doctrine's N+1 problem.
License MIT
Homepage https://github.com/rgalstyan/symfony-aggregated-queries
Informations about the package symfony-aggregated-queries
Symfony Aggregated Queries
Reduce multi-relation Doctrine queries to a single optimized SQL statement using JSON aggregation.
Solves Doctrine's documented N+1 problem (Issue #4762) where fetch="EAGER" still generates multiple queries for OneToMany/ManyToMany relations.
Perfect for read-heavy APIs, dashboards, and admin panels where traditional Doctrine eager loading generates too many queries.
π₯ The Problem
When querying Doctrine entities, relations are loaded either lazily (classic N+1) or eagerly using separate queries for collections.
Example with traditional Doctrine entity loading:
Still produces 3β4 separate queries:
While this is not a fetch join, it still results in multiple database round-trips, heavy Doctrine hydration, increased memory usage, and slower response times.
Fetch joins can reduce the number of queries to one, but introduce row explosion, break pagination, and do not scale when multiple collections are involved.
Doctrine's Known Issue:
Even with fetch="EAGER", OneToMany and ManyToMany relations cause N+1 queries. This is a documented limitation that has existed since 2015.
Complex pages easily generate 5β15 queries, increasing:
- Database round-trips
- Doctrine hydration overhead
- Response time
- Memory usage
- Server load
β¨ The Solution
Transform multiple queries into one optimized SQL statement using JSON aggregation:
Generates a single query:
Result:
- β 1 database round-trip instead of 4
- β No Doctrine hydration overhead (uses DBAL directly)
- β Up to 7x faster response time
- β 90%+ less memory usage
- β Consistent array output
π Performance
Real-world benchmark using a Symfony application with a realistic e-commerce dataset (products, images, reviews).
Dataset context:
- ~100,000 products
- ~300,000 images
- ~500,000 reviews
Example run (limit = 1000):
| Method | Time | Memory | Queries |
|---|---|---|---|
| Traditional Eloquent / Doctrine-style loading | 167.49ms | 24.37 MB | 23 |
| Aggregated Query | 28.17ms | 6.91 MB | 1 |
| Improvement | β‘ 83.2% faster | πΎ 71.6% less | π’ 22 fewer |
These numbers depend on hardware and DB state, but the overall trend is consistent. The primary gains come from reduced SQL round-trips and avoiding ORM entity hydration.
Reproducible benchmark project
A standalone Symfony-based benchmark with full setup instructions and fixtures is available here:
The benchmark focuses on read-only, DTO-style queries and compares traditional ORM loading versus aggregated SQL queries in real-world conditions.
π Requirements
| Component | Version |
|---|---|
| PHP | ^8.1 |
| Symfony | ^6.0 | ^7.0 |
| Doctrine ORM | ^2.14 | ^3.0 |
| MySQL | ^8.0 |
| PostgreSQL | ^12.0 |
π¦ Installation
1. Install via Composer
2. Enable the Bundle
If you're using Symfony Flex, the bundle is automatically registered.
Otherwise, add to config/bundles.php:
3. (Optional) Configure
Create config/packages/aggregated_queries.yaml:
π Quick Start
1. Add trait to your repository
2. Define your entities
3. Use in your service
4. Use in controller
5. Response structure (guaranteed)
π‘ Advanced Usage
Direct Service Usage
Inject AggregatedQueryBuilder directly when you need more flexibility:
Filtering and Sorting
Collections (OneToMany)
Multiple Counts
π API Reference
Loading Relations
Query Filters
Execution
Debugging
β When to Use
Perfect for:
- β API endpoints with multiple relations
- β Admin dashboards with complex data views
- β Mobile backends where latency matters
- β Listing pages with 3β10 relations per row
- β Read-heavy services (90%+ reads)
- β High-traffic applications needing DB optimization
- β Replacing Doctrine's N+1 problem
β οΈ Not suitable for:
- β Write operations (use standard Doctrine)
- β Doctrine lifecycle events (results are arrays by default)
- β Deep nested relations like
profile.company.country(not yet supported in v1.0) - β Polymorphic relations (not in v1.0)
- β ManyToMany relations (planned for v1.1)
π Important Constraints
Read-Only by Design
Results are arrays, not Doctrine entities (by default).
This means:
- β No Doctrine lifecycle events (
postLoad,preUpdate, etc.) - β No entity listeners
- β No lazy loading
- β Cannot call
persist(),flush(),remove()
Use for read operations only. For writes, use standard Doctrine.
Data Shape Guarantees
| Feature | Always Returns |
|---|---|
withJsonRelation() |
array or null |
withJsonCollection() |
array (empty [] if no records) |
withCount() |
integer |
No surprises. No null collections. Consistent types across MySQL and PostgreSQL.
π¦ Batch Processing
For large exports or background jobs:
Do NOT use limit(5000) without batching!
βοΈ Configuration Reference
β οΈ Limitations (v1.0)
Currently not supported (planned for future versions):
- Nested relations (
profile.company.country) - ManyToMany (
belongsToMany) - Polymorphic relations (not common in Doctrine)
- Doctrine Query Language (DQL) integration
- Callbacks in relations
- Automatic result caching
See Roadmap for planned features.
π Comparison: Traditional vs Aggregated
Traditional Doctrine
Aggregated Queries
π§ͺ Testing
π§ Troubleshooting
"AggregatedQueryBuilder not injected"
Cause: Repository not configured as a service with auto-wiring.
Solution: Ensure repositories are in services.yaml:
"Relation 'xyz' not found"
Cause: Typo in relation name or relation not defined in entity.
Solution: Check entity's #[ORM\ManyToOne], #[ORM\OneToMany], etc. attributes.
Slow performance
Causes:
- Missing database indexes on foreign keys
- Too many relations (>10)
- Large collections without LIMIT
Solutions:
- Limit collections using filters (future feature)
- Use
->limit()on main query - Use batching for large datasets
π Examples
See the /examples directory for complete working examples:
| Example | Description |
|---|---|
basic-usage.php |
Simple queries with 2-3 relations |
multiple-relations.php |
Complex relations and collections |
with-filters.php |
Filtering, sorting, and pagination |
service-usage.php |
Service layer integration |
batch-processing.php |
Large dataset handling |
π€ Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Add tests for new features
- Ensure tests pass (
composer test) - Check code style (
composer cs:check) - Run static analysis (
composer phpstan) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
See CONTRIBUTING.md for detailed guidelines.
π Security
If you discover a security vulnerability, please email:
π§ [email protected]
Do not create public issues for security vulnerabilities.
All security vulnerabilities will be promptly addressed.
π Changelog
See CHANGELOG.md for release history and migration guides.
πΊοΈ Roadmap
v1.1 (Q1 2025)
- β¨ ManyToMany support (
belongsToMany) - β¨ Nested relations (
profile.company.country) - β¨ Query result caching (Redis, Memcached)
- β¨ Conditional relation loading
- β¨ Relation callbacks support
v2.0 (Q2-Q3 2025)
- β¨ GraphQL-like query syntax
- β¨ Polymorphic relation support
- β¨ Advanced filtering DSL
- β¨ Performance monitoring integration
- β¨ DQL integration
- β¨ Automatic query optimization
Want a feature? Open an issue or vote on existing ones!
π License
The MIT License (MIT). See LICENSE for details.
π¨βπ» Credits
Author: Razmik Galstyan
GitHub: @rgalstyan
Email: [email protected]
LinkedIn: Razmik Galstyan
Inspired by Laravel Aggregated Queries and built to solve Doctrine's N+1 problem.
Built with β€οΈ for the Symfony community.
π Related Projects
- Laravel Aggregated Queries - Laravel version of this package
- Doctrine Issue #4762 - The N+1 problem we solve
- Doctrine ORM - The amazing ORM this bundle extends
π¬ Support
- β Star the repo if you find it useful
- π Report bugs via GitHub Issues
- π‘ Request features via GitHub Issues
- π Improve docs via Pull Requests
- π¬ Ask questions in GitHub Discussions
- π£ Share with your team and on social media
π― Why This Bundle Exists
Doctrine's ORM is powerful but has a well-known N+1 problem with collections that fetch="EAGER" doesn't solve (documented since 2015).
This bundle provides a clean, performant solution using modern SQL's JSON aggregation capabilities:
- β Reduces queries from 5β15 down to 1
- β Up to 7x performance improvement
- β 90% less memory usage
- β Zero configuration needed
- β Works with existing Doctrine entities
Perfect for: APIs, dashboards, mobile backends, and any read-heavy Symfony application.
π Show Your Support
If this bundle saves you time and improves your app's performance, please:
- β Star the project on GitHub
- π£ Share it with your team
- π¬ Write about your experience
- π€ Contribute improvements
Every star and contribution helps the project grow!
All versions of symfony-aggregated-queries with dependencies
doctrine/dbal Version ^3.6 || ^4.0
doctrine/orm Version ^2.14 || ^3.0
symfony/config Version ^6.0 || ^7.0
symfony/dependency-injection Version ^6.0 || ^7.0
symfony/http-kernel Version ^6.0 || ^7.0
symfony/yaml Version ^6.0 || ^7.0